feat(find): browse for the Directory scope's folder in Find in Files (#1085)

The Directory scope of Find in Files only took a typed path. A folder button next to the field now opens a folder chooser at the current directory and fills the field with the chosen folder.

Fixes #847
This commit is contained in:
l0ng-ai
2026-11-04 02:53:03 +08:00
committed by GitHub
parent 63801ba249
commit cfdbd3872a
3 changed files with 46 additions and 1 deletions
@@ -55,6 +55,13 @@ vi.mock("@/workbench/messages", async (orig) => ({
},
}));
const dialogOpen = vi.fn();
vi.mock("@tauri-apps/plugin-dialog", () => ({ open: (o: unknown) => dialogOpen(o) }));
vi.mock("@/platform/ipc", async (orig) => ({
...(await orig<typeof import("@/platform/ipc")>()),
isTauri: () => true,
}));
const { FindInFilesOverlay } = await import("../find/FindInFiles");
const { findHistory, hideFindPopup, rememberFindString, runFindPopupCommand, showFindPopup, useFindPopup } =
await import("../find/store");
@@ -176,6 +183,18 @@ describe("Find in Files popup", () => {
act(() => hideFindPopup());
});
it("the Directory scope's browse button picks the folder, starting at the current one", async () => {
dialogOpen.mockResolvedValue("/p/src/main");
useFindPopup.setState({ scopeTab: "directory", directory: "/p/src" });
render(<FindInFilesOverlay />);
act(() => showFindPopup(false, { query: "order" }));
await settle();
fireEvent.click(screen.getByRole("button", { name: "Browse…" }));
await settle();
expect(dialogOpen).toHaveBeenCalledWith(expect.objectContaining({ directory: true, defaultPath: "/p/src" }));
expect(useFindPopup.getState().directory).toBe("/p/src/main");
});
it("closing remembers the search text", async () => {
render(<FindInFilesOverlay />);
act(() => showFindPopup(false, { query: "order" }));
+26 -1
View File
@@ -4,7 +4,7 @@ import { openEditorPaths, useEditorRegistry } from "@/editor/api";
import { executeAction } from "@/platform/actions";
import { setContext } from "@/platform/context";
import { useTxTemplate, tx } from "@/platform/i18n";
import { call, commands, type SearchScope } from "@/platform/ipc";
import { call, commands, isTauri, type SearchScope } from "@/platform/ipc";
import { useProject } from "@/platform/project";
import { useProjectModules } from "@/platform/projectRoots";
import { EDIT_SCOPES_ACTION, useCustomScopeList } from "@/platform/search";
@@ -60,6 +60,7 @@ import {
recentMasks,
rememberFindString,
rememberMask,
resolveDirectory,
resolveFindScope,
setFindPopupCommandHandler,
setFindPopupPinned,
@@ -73,6 +74,19 @@ import {
import { ipcFindDeps, openInFindWindow } from "./windowStore";
import styles from "./FindInFiles.module.css";
/** The Directory scope's browse button: a folder chooser opened at the current directory. */
async function chooseDirectory(current: string): Promise<string | null> {
if (!isTauri()) return null;
const { open } = await import("@tauri-apps/plugin-dialog");
const picked = await open({
directory: true,
multiple: false,
title: tx("Select Search Directory"),
defaultPath: current || undefined,
});
return (Array.isArray(picked) ? picked[0] : picked) ?? null;
}
const POPUP_MAX_MATCHES = 2000;
const SEARCH_DEBOUNCE_MS = 150;
const ROW = 22;
@@ -631,6 +645,17 @@ function FindInFilesDialog() {
onChange={(e) => set({ directory: e.target.value })}
/>
)}
{s.scopeTab === "directory" && (
<IconButton
icon="folder-open"
title="Browse…"
onClick={() =>
void chooseDirectory(resolveDirectory(s.directory, root)).then(
(dir) => dir && set({ directory: dir }),
)
}
/>
)}
{s.scopeTab === "module" && (
<Select<string>
value={s.module}
+1
View File
@@ -1956,6 +1956,7 @@
"Select JDK Home Directory": "选择 JDK 主目录",
"Select Local Repository": "选择本地仓库",
"Select Maven Home Directory": "选择 Maven 主目录",
"Select Search Directory": "选择查找目录",
"Select Maven settings.xml": "选择 Maven settings.xml",
"Select Next Tab": "选择下一个标签页",
"Select Opened File": "选择已打开的文件",