mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 17:10:43 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4effa7a785 |
No files matched your search
@@ -37,7 +37,7 @@ try {
|
||||
const one = await open("");
|
||||
const tree = await one.evaluate(() => ({
|
||||
folders: [...document.querySelectorAll(".workbench-change-folder")].length,
|
||||
rows: [...document.querySelectorAll('.workbench-change-row[data-action="workbench.diff"]')].length,
|
||||
rows: [...document.querySelectorAll('.workbench-change-row[data-action-click="workbench.diff"]')].length,
|
||||
overflow: document.querySelector(".workbench-explorer").scrollWidth > document.querySelector(".workbench-explorer").clientWidth + 1,
|
||||
}));
|
||||
check(`${tag}: the changes are a folder tree`, tree.folders >= 2 && tree.rows === 4, JSON.stringify(tree));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState, type ReactNode } from "react";
|
||||
import { useMemo, useState, type MouseEvent, type ReactNode } from "react";
|
||||
import { t } from "../i18n";
|
||||
import { reason } from "../i18n/kernel";
|
||||
import type { AgentPort, StageRequest, WorkspaceChange } from "../port/port";
|
||||
@@ -15,6 +15,8 @@ interface Props {
|
||||
// The path whose diff is on screen, "" when none is.
|
||||
current: string;
|
||||
onOpen: (path: string) => void;
|
||||
// A right-click or the context-menu key on a row; the path is workspace-relative.
|
||||
onMenu?: (e: MouseEvent<HTMLElement>, path: string, folder: boolean) => void;
|
||||
// Asked for when staging or discarding changed the tree. Absent means this
|
||||
// surface cannot refresh the list, so it offers neither.
|
||||
onChanged?: () => void;
|
||||
@@ -37,7 +39,7 @@ const withRepo = (repo: string | undefined, req: StageRequest): StageRequest =>
|
||||
// What differs as a folder tree, without opening anything, and which of it the next
|
||||
// commit will record: the index is what a commit reads, so it is shown and changed
|
||||
// here, a folder at a time or a file at a time. A change can also be thrown away.
|
||||
export function WorkbenchChanges({ port, workspaceRoot, changes, repo, current, onOpen, onChanged, commit, actions, below }: Props) {
|
||||
export function WorkbenchChanges({ port, workspaceRoot, changes, repo, current, onOpen, onMenu, onChanged, commit, actions, below }: Props) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [failure, setFailure] = useState("");
|
||||
const [shut, setShut] = useState<Set<string>>(new Set());
|
||||
@@ -84,8 +86,9 @@ export function WorkbenchChanges({ port, workspaceRoot, changes, repo, current,
|
||||
<StudioIcon name="check" aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
<button type="button" className="workbench-change-row workbench-folder-row" style={inset} data-action="workbench.change-folder" data-target={node.path}
|
||||
<button type="button" className="workbench-change-row workbench-folder-row" style={inset} data-action-click="workbench.change-folder" data-target={node.path}
|
||||
{...workspaceDrag(workspaceRoot, repo ? `${repo}/${node.path}` : node.path)}
|
||||
data-action-contextmenu={onMenu ? "workbench.menu" : undefined} onContextMenu={onMenu && ((e) => onMenu(e, repo ? `${repo}/${node.path}` : node.path, true))}
|
||||
aria-expanded={open} onClick={() => fold(node.path)}>
|
||||
<StudioIcon name={open ? "down" : "chevron"} aria-hidden="true" />
|
||||
<span>{node.name}</span>
|
||||
@@ -110,8 +113,9 @@ export function WorkbenchChanges({ port, workspaceRoot, changes, repo, current,
|
||||
<StudioIcon name="check" aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
<button className="workbench-change-row" style={inset} data-action="workbench.diff" data-target={item.path} title={item.path}
|
||||
<button className="workbench-change-row" style={inset} data-action-click="workbench.diff" data-target={item.path} title={item.path}
|
||||
{...workspaceDrag(workspaceRoot, repo ? `${repo}/${item.path}` : item.path)}
|
||||
data-action-contextmenu={onMenu ? "workbench.menu" : undefined} onContextMenu={onMenu && ((e) => onMenu(e, repo ? `${repo}/${item.path}` : item.path, false))}
|
||||
aria-current={current === item.path ? "page" : undefined} onClick={() => onOpen(item.path)}>
|
||||
<em data-status={item.status}>{item.status || "M"}</em>
|
||||
<span>{node.name}</span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useState, type MouseEvent } from "react";
|
||||
import { t } from "../i18n";
|
||||
import type { AgentPort, WorkspaceChange } from "../port/port";
|
||||
import type { GitRepoRow } from "../port/workspace";
|
||||
@@ -24,6 +24,7 @@ interface Props {
|
||||
// The workspace-relative path whose diff is on screen, "" when none is.
|
||||
current: string;
|
||||
onOpen: (path: string) => void;
|
||||
onMenu?: (e: MouseEvent<HTMLElement>, path: string, folder: boolean) => void;
|
||||
onChanged?: () => void;
|
||||
remote: boolean;
|
||||
}
|
||||
@@ -34,7 +35,7 @@ const join = (repo: string, path: string) => (repo ? `${repo}/${path}` : path);
|
||||
// stage, discard and commit, the push of the branch, and its history. A workspace
|
||||
// that is one repository shows just that; a folder of projects shows one section per
|
||||
// repository, each acting on its own.
|
||||
export function WorkbenchGit({ port, workspaceRoot, changes, repos, failure = "", onRetry, wrote, current, onOpen, onChanged, remote }: Props) {
|
||||
export function WorkbenchGit({ port, workspaceRoot, changes, repos, failure = "", onRetry, wrote, current, onOpen, onMenu, onChanged, remote }: Props) {
|
||||
// A list that could not be read says so, and the workspace is shown as the one
|
||||
// repository it was before there was a list: the changes the pane holds are real.
|
||||
if (failure) {
|
||||
@@ -44,25 +45,25 @@ export function WorkbenchGit({ port, workspaceRoot, changes, repos, failure = ""
|
||||
{failure}
|
||||
{onRetry && <button type="button" className="btn sm" data-action="workbench.repos-reload" onClick={onRetry}>{t("重新读取")}</button>}
|
||||
</p>
|
||||
<RepoSection port={port} workspaceRoot={workspaceRoot} repo="" changes={changes} current={current} onOpen={onOpen} onChanged={onChanged} remote={remote} />
|
||||
<RepoSection port={port} workspaceRoot={workspaceRoot} repo="" changes={changes} current={current} onOpen={onOpen} onMenu={onMenu} onChanged={onChanged} remote={remote} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (repos.length === 0) return null;
|
||||
const several = repos.length > 1 || repos[0].dir !== "";
|
||||
if (!several) {
|
||||
return <RepoSection port={port} workspaceRoot={workspaceRoot} repo="" changes={changes} current={current} onOpen={onOpen} onChanged={onChanged} remote={remote} />;
|
||||
return <RepoSection port={port} workspaceRoot={workspaceRoot} repo="" changes={changes} current={current} onOpen={onOpen} onMenu={onMenu} onChanged={onChanged} remote={remote} />;
|
||||
}
|
||||
return (
|
||||
<div className="workbench-git" data-repos={repos.length}>
|
||||
{repos.map((row) => (
|
||||
<Folder key={row.dir} port={port} workspaceRoot={workspaceRoot} row={row} wrote={wrote} current={current} onOpen={onOpen} remote={remote} />
|
||||
<Folder key={row.dir} port={port} workspaceRoot={workspaceRoot} row={row} wrote={wrote} current={current} onOpen={onOpen} onMenu={onMenu} remote={remote} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Folder({ port, workspaceRoot, row, wrote, current, onOpen, remote }: { port: AgentPort; workspaceRoot?: string; row: GitRepoRow; wrote: number; current: string; onOpen: (path: string) => void; remote: boolean }) {
|
||||
function Folder({ port, workspaceRoot, row, wrote, current, onOpen, onMenu, remote }: { port: AgentPort; workspaceRoot?: string; row: GitRepoRow; wrote: number; current: string; onOpen: (path: string) => void; onMenu?: Props["onMenu"]; remote: boolean }) {
|
||||
const [open, setOpen] = useState(true);
|
||||
const { changes, reload } = useRepoChanges(port, row.dir, open, wrote);
|
||||
return (
|
||||
@@ -74,13 +75,13 @@ function Folder({ port, workspaceRoot, row, wrote, current, onOpen, remote }: {
|
||||
<span>{row.branch}</span>
|
||||
<small>{changes.length}</small>
|
||||
</button>
|
||||
{open && <RepoSection port={port} workspaceRoot={workspaceRoot} repo={row.dir} changes={changes} current={current} onOpen={onOpen} onChanged={reload} remote={remote} />}
|
||||
{open && <RepoSection port={port} workspaceRoot={workspaceRoot} repo={row.dir} changes={changes} current={current} onOpen={onOpen} onMenu={onMenu} onChanged={reload} remote={remote} />}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function RepoSection({ port, workspaceRoot, repo, changes, current, onOpen, onChanged, remote }: {
|
||||
port: AgentPort; workspaceRoot?: string; repo: string; changes: WorkspaceChange[]; current: string; onOpen: (path: string) => void; onChanged?: () => void; remote: boolean;
|
||||
function RepoSection({ port, workspaceRoot, repo, changes, current, onOpen, onMenu, onChanged, remote }: {
|
||||
port: AgentPort; workspaceRoot?: string; repo: string; changes: WorkspaceChange[]; current: string; onOpen: (path: string) => void; onMenu?: Props["onMenu"]; onChanged?: () => void; remote: boolean;
|
||||
}) {
|
||||
const refresh = () => onChanged?.();
|
||||
const commit = useCommitCard(port, refresh, repo || undefined);
|
||||
@@ -95,6 +96,7 @@ function RepoSection({ port, workspaceRoot, repo, changes, current, onOpen, onCh
|
||||
changes={changes}
|
||||
current={inside}
|
||||
onOpen={(path) => onOpen(join(repo, path))}
|
||||
onMenu={onMenu}
|
||||
onChanged={onChanged}
|
||||
commit={commit}
|
||||
actions={remote ? null : push.button}
|
||||
|
||||
@@ -690,6 +690,7 @@ export function WorkbenchPanel({
|
||||
openFile(path);
|
||||
setMode("diff");
|
||||
}}
|
||||
onMenu={offerMenu}
|
||||
onChanged={onTreeChanged}
|
||||
remote={remote}
|
||||
/>
|
||||
|
||||
@@ -46,6 +46,6 @@ describe("a workspace with very many changes", () => {
|
||||
it("draws a short list whole, with nothing left out", () => {
|
||||
const { container } = draw(many(30));
|
||||
expect(screen.queryByRole("button", { name: /再显示/ })).toBeNull();
|
||||
expect(container.querySelectorAll('[data-action="workbench.diff"]').length).toBe(30);
|
||||
expect(container.querySelectorAll('[data-action-click="workbench.diff"]').length).toBe(30);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
// @vitest-environment jsdom
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { afterEach, expect, it, vi } from "vitest";
|
||||
import { MockPort } from "../port/mock";
|
||||
import type { WorkspaceChange } from "../port/port";
|
||||
import { WorkbenchPanel } from "./WorkbenchPanel";
|
||||
import "./testkit";
|
||||
|
||||
const change = (path: string, status = "M"): WorkspaceChange => ({ path, status, staged: false, unstaged: true });
|
||||
const props = (port: MockPort, changes: WorkspaceChange[]) => ({
|
||||
port, workspaceRoot: "/work/project", tabs: [], manual: false, shown: true, scheme: "light" as const,
|
||||
changes, onCloseManual: vi.fn(), onSurfaces: vi.fn(), onExternal: vi.fn(), onAddToChat: vi.fn(),
|
||||
});
|
||||
afterEach(() => { cleanup(); vi.restoreAllMocks(); });
|
||||
|
||||
it("offers the file menu on a changed file, with the path the tree would give", async () => {
|
||||
const port = new MockPort();
|
||||
vi.spyOn(port, "workspaceFiles").mockResolvedValue({ files: [], directories: [] });
|
||||
render(<WorkbenchPanel {...props(port, [change("web/src/prototype.html", "A")])} />);
|
||||
const row = await screen.findByTitle("web/src/prototype.html");
|
||||
fireEvent.contextMenu(row);
|
||||
expect(await screen.findByRole("menuitem", { name: "复制路径" })).toBeTruthy();
|
||||
expect(screen.getByRole("menuitem", { name: "复制引用" })).toBeTruthy();
|
||||
});
|
||||
|
||||
it("reveals the changed file in the file manager from its menu", async () => {
|
||||
const port = new MockPort();
|
||||
vi.spyOn(port, "workspaceFiles").mockResolvedValue({ files: [], directories: [] });
|
||||
vi.spyOn(port, "revealsFiles").mockReturnValue(true);
|
||||
const reveal = vi.spyOn(port, "revealInFileManager").mockResolvedValue(undefined);
|
||||
render(<WorkbenchPanel {...props(port, [change("a/b.ts")])} />);
|
||||
fireEvent.contextMenu(await screen.findByTitle("a/b.ts"));
|
||||
fireEvent.click(await screen.findByRole("menuitem", { name: /在访达中显示|在文件资源管理器中显示|在系统文件管理器中显示/ }));
|
||||
await waitFor(() => expect(reveal).toHaveBeenCalledWith("a/b.ts"));
|
||||
});
|
||||
|
||||
it("leaves a plain click on a changed file opening its diff", async () => {
|
||||
const port = new MockPort();
|
||||
vi.spyOn(port, "workspaceFiles").mockResolvedValue({ files: [], directories: [] });
|
||||
render(<WorkbenchPanel {...props(port, [change("a/b.ts")])} />);
|
||||
fireEvent.click(await screen.findByTitle("a/b.ts"));
|
||||
expect(screen.queryByRole("menuitem", { name: "复制路径" })).toBeNull();
|
||||
});
|
||||
@@ -84,7 +84,7 @@ it("references search results and changed files through the same intake", async
|
||||
return found!;
|
||||
});
|
||||
drag(row, box);
|
||||
drag(document.querySelector<HTMLElement>('[data-action="workbench.diff"]')!, box);
|
||||
drag(document.querySelector<HTMLElement>('[data-action-click="workbench.diff"]')!, box);
|
||||
await waitFor(() => expect(screen.getAllByRole("button", { name: "移除 retry.go" })).toHaveLength(2));
|
||||
expect(dropRefs.mock.calls).toEqual([[["/workspace/internal/provider/retry.go"]], [["/workspace/internal/provider/retry.go"]]]);
|
||||
});
|
||||
@@ -95,7 +95,7 @@ it("keeps the nested repository prefix when dragging changed files", async () =>
|
||||
port.repoChanges = { "apps/site": [{ path: "README.md", status: "M" }] };
|
||||
});
|
||||
const row = await waitFor(() => {
|
||||
const found = document.querySelector<HTMLElement>('[data-repo="apps/site"] [data-action="workbench.diff"]');
|
||||
const found = document.querySelector<HTMLElement>('[data-repo="apps/site"] [data-action-click="workbench.diff"]');
|
||||
expect(found).toBeTruthy();
|
||||
return found!;
|
||||
});
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
- 修复:工作台「改动」列表里的文件和文件夹现在也能右键,菜单与文件树一致,可在系统文件管理器中显示、复制路径或引用、加入对话。修复 #14758。
|
||||
- Fixed: files and folders in the workbench changes list now have the same right-click menu as the file tree: reveal in the file manager, copy path or reference, add to chat. Fixes #14758.
|
||||
Reference in new issue
Block a user