支持文件和变更区域的根目录快捷操作

This commit is contained in:
xintaofei
2026-03-13 21:50:15 +08:00
parent bf14a99168
commit 3376974d0f
4 changed files with 252 additions and 59 deletions

View File

@@ -1120,7 +1120,7 @@ export function FileWorkspacePanel() {
path: diffListContext.path, path: diffListContext.path,
branch: diffListContext.branch, branch: diffListContext.branch,
}) })
: diffListContext.path : (activeFileTab.description ?? diffListContext.path)
const handleOpenDiff = async (path: string) => { const handleOpenDiff = async (path: string) => {
if (diffListContext.kind === "commit") { if (diffListContext.kind === "commit") {

View File

@@ -1701,6 +1701,22 @@ export function FileTreeTab() {
return baseName(folder.path) return baseName(folder.path)
}, [folder?.path, t]) }, [folder?.path, t])
const systemExplorerLabel =
typeof navigator === "undefined"
? t("openInFileManager")
: (() => {
const platform =
`${navigator.platform} ${navigator.userAgent}`.toLowerCase()
if (platform.includes("mac")) return t("openInFinder")
if (platform.includes("win")) return t("openInExplorer")
return t("openInFileManager")
})()
const rootTarget: FileActionTarget = useMemo(
() => ({ kind: "dir", path: "", name: rootNodeName }),
[rootNodeName]
)
useEffect(() => { useEffect(() => {
if (!isFileTreeTabActive) return if (!isFileTreeTabActive) return
void fetchTree() void fetchTree()
@@ -2009,45 +2025,128 @@ export function FileTreeTab() {
onSelect={handleTreeSelect} onSelect={handleTreeSelect}
> >
{folder?.path && ( {folder?.path && (
<FileTreeFolder <ContextMenu>
path={FILE_TREE_ROOT_PATH} <ContextMenuTrigger asChild>
name={rootNodeName} <FileTreeFolder
className="font-medium" path={FILE_TREE_ROOT_PATH}
> name={rootNodeName}
{nodes.map((node) => ( className="font-medium"
<RenderNode >
key={node.path} {nodes.map((node) => (
node={node} <RenderNode
expandedPaths={expandedPaths} key={node.path}
workspacePath={folder.path} node={node}
activeSessionTabId={activeSessionTabId} expandedPaths={expandedPaths}
gitEnabled={gitEnabled} workspacePath={folder.path}
gitStatusByPath={gitStatusByPath} activeSessionTabId={activeSessionTabId}
gitChangedDirPaths={gitChangedDirPaths} gitEnabled={gitEnabled}
gitignoreIgnoredPaths={gitignoreIgnoredPaths} gitStatusByPath={gitStatusByPath}
ancestorGitignoreIgnored={false} gitChangedDirPaths={gitChangedDirPaths}
onOpenFilePreview={(path) => { gitignoreIgnoredPaths={gitignoreIgnoredPaths}
void openFilePreview(path) ancestorGitignoreIgnored={false}
onOpenFilePreview={(path) => {
void openFilePreview(path)
}}
onOpenFileDiff={(path) => {
void openWorkingTreeDiff(path)
}}
onOpenDirDiff={(path) => {
void openWorkingTreeDiff(path, {
mode: "overview",
})
}}
onOpenCommitWindow={handleOpenCommitWindow}
onRequestCompareWithBranch={
handleRequestCompareWithBranch
}
onRequestRollback={handleRequestRollback}
onOpenDirInTerminal={handleOpenDirInTerminal}
onRequestAddToVcs={handleAddToVcs}
onRequestRename={handleRequestRename}
onRequestDelete={handleRequestDelete}
onRefresh={fetchTree}
/>
))}
</FileTreeFolder>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuSub>
<ContextMenuSubTrigger disabled={!gitEnabled}>
{t("git")}
</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuItem
onSelect={() => handleOpenCommitWindow()}
disabled={!gitEnabled}
>
{t("actions.commitCode")}
</ContextMenuItem>
<ContextMenuItem
onSelect={() => void handleAddToVcs(rootTarget)}
disabled={!gitEnabled}
>
{t("actions.addToVcs")}
</ContextMenuItem>
<ContextMenuItem
onSelect={() =>
void openWorkingTreeDiff(".", {
mode: "overview",
})
}
disabled={!gitEnabled}
>
{tCommon("viewDiff")}
</ContextMenuItem>
<ContextMenuItem
onSelect={() =>
handleRequestCompareWithBranch(rootTarget)
}
disabled={!gitEnabled}
>
{t("compareWithBranch")}
</ContextMenuItem>
<ContextMenuItem
variant="destructive"
onSelect={() => handleRequestRollback(rootTarget)}
disabled={!gitEnabled}
>
{t("actions.rollback")}
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuItem
onSelect={() => {
void fetchTree()
}} }}
onOpenFileDiff={(path) => { >
void openWorkingTreeDiff(path) {t("reloadFromDisk")}
}} </ContextMenuItem>
onOpenDirDiff={(path) => { <ContextMenuSub>
void openWorkingTreeDiff(path, { mode: "overview" }) <ContextMenuSubTrigger>
}} {t("openIn")}
onOpenCommitWindow={handleOpenCommitWindow} </ContextMenuSubTrigger>
onRequestCompareWithBranch={ <ContextMenuSubContent>
handleRequestCompareWithBranch <ContextMenuItem
} onSelect={() => {
onRequestRollback={handleRequestRollback} void revealItemInDir(folder.path)
onOpenDirInTerminal={handleOpenDirInTerminal} }}
onRequestAddToVcs={handleAddToVcs} >
onRequestRename={handleRequestRename} {systemExplorerLabel}
onRequestDelete={handleRequestDelete} </ContextMenuItem>
onRefresh={fetchTree} <ContextMenuItem
/> onSelect={() => {
))} void handleOpenDirInTerminal(
</FileTreeFolder> folder.path,
rootNodeName
)
}}
>
{t("openInTerminal")}
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuContent>
</ContextMenu>
)} )}
</FileTree> </FileTree>
</div> </div>

View File

@@ -1236,15 +1236,60 @@ export function GitChangesTab() {
expanded={expandedTrackedPaths} expanded={expandedTrackedPaths}
onExpandedChange={setExpandedTrackedPaths} onExpandedChange={setExpandedTrackedPaths}
> >
<FileTreeFolder <ContextMenu>
path={TRACKED_ROOT_PATH} <ContextMenuTrigger asChild>
name={folderName} <FileTreeFolder
suffix={`(${trackedChanges.length})`} path={TRACKED_ROOT_PATH}
suffixClassName="text-muted-foreground/45" name={folderName}
title={folderName} suffix={`(${trackedChanges.length})`}
> suffixClassName="text-muted-foreground/45"
{trackedTreeNodes.map(renderTrackedNode)} title={folderName}
</FileTreeFolder> >
{trackedTreeNodes.map(renderTrackedNode)}
</FileTreeFolder>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
onSelect={() => {
handleOpenCommitWindow()
}}
>
{t("actions.commitCode")}
</ContextMenuItem>
<ContextMenuItem
onSelect={() => {
void openWorkingTreeDiff(".", {
mode: "overview",
})
}}
>
{tCommon("viewDiff")}
</ContextMenuItem>
<ContextMenuItem
onSelect={() => {
handleRequestRollback({
kind: "dir",
path: "",
name: folderName,
})
}}
variant="destructive"
>
{t("actions.rollback")}
</ContextMenuItem>
<ContextMenuItem
onSelect={() => {
void handleAddToVcs({
kind: "dir",
path: "",
name: folderName,
})
}}
>
{t("actions.addToVcs")}
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
</FileTree> </FileTree>
</section> </section>
)} )}
@@ -1284,15 +1329,60 @@ export function GitChangesTab() {
expanded={expandedUntrackedPaths} expanded={expandedUntrackedPaths}
onExpandedChange={setExpandedUntrackedPaths} onExpandedChange={setExpandedUntrackedPaths}
> >
<FileTreeFolder <ContextMenu>
path={UNTRACKED_ROOT_PATH} <ContextMenuTrigger asChild>
name={folderName} <FileTreeFolder
suffix={`(${untrackedChanges.length})`} path={UNTRACKED_ROOT_PATH}
suffixClassName="text-muted-foreground/45" name={folderName}
title={folderName} suffix={`(${untrackedChanges.length})`}
> suffixClassName="text-muted-foreground/45"
{untrackedTreeNodes.map(renderUntrackedNode)} title={folderName}
</FileTreeFolder> >
{untrackedTreeNodes.map(renderUntrackedNode)}
</FileTreeFolder>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
onSelect={() => {
handleOpenCommitWindow()
}}
>
{t("actions.commitCode")}
</ContextMenuItem>
<ContextMenuItem
onSelect={() => {
void openWorkingTreeDiff(".", {
mode: "overview",
})
}}
>
{tCommon("viewDiff")}
</ContextMenuItem>
<ContextMenuItem
onSelect={() => {
handleRequestRollback({
kind: "dir",
path: "",
name: folderName,
})
}}
variant="destructive"
>
{t("actions.rollback")}
</ContextMenuItem>
<ContextMenuItem
onSelect={() => {
void handleAddToVcs({
kind: "dir",
path: "",
name: folderName,
})
}}
>
{t("actions.addToVcs")}
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
</FileTree> </FileTree>
</section> </section>
)} )}

View File

@@ -440,10 +440,14 @@ export function WorkspaceProvider({ children }: WorkspaceProviderProps) {
const mode = options?.mode ?? "auto" const mode = options?.mode ?? "auto"
if (mode === "overview") { if (mode === "overview") {
const isRoot = path === "."
const displayPath = isRoot ? folderPath : path
const encodedPath = encodeURIComponent(path) const encodedPath = encodeURIComponent(path)
const tabId = `diff:working-overview:${encodedPath}` const tabId = `diff:working-overview:${encodedPath}`
const title = t("diffTitleFile", { name: fileName(path) }) const title = t("diffTitleFile", {
const description = path name: fileName(displayPath ?? path),
})
const description = displayPath ?? path
upsertLoadingTab( upsertLoadingTab(
loadingTab(tabId, "diff", title, description, path, "diff") loadingTab(tabId, "diff", title, description, path, "diff")
) )