mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-13 04:34:21 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fd0cdf949 |
No files matched your search
@@ -83,6 +83,44 @@ describe("SeController", () => {
|
||||
expect(deps.cancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("lists directories with the files, by score, files first on a tie", async () => {
|
||||
const { c, last } = setup({
|
||||
findFiles: async () => [
|
||||
{ ...fm("src/util.ts"), score: 5 },
|
||||
{ ...fm("lib/utils/a.ts"), score: 2 },
|
||||
],
|
||||
findDirs: async () => [
|
||||
{ path: "/p/src/utils", relPath: "src/utils/", score: 5, indices: [4, 5, 6, 7] },
|
||||
{ path: "/p/lib/utils", relPath: "lib/utils/", score: 3, indices: [] },
|
||||
],
|
||||
});
|
||||
c.run({ query: "util", tab: "files", includeNonProject: false, extra: {} });
|
||||
await flush();
|
||||
const items = last().sections.get("files")!.items;
|
||||
expect(items.map((i) => i.key)).toEqual([
|
||||
"f:/p/src/util.ts",
|
||||
"d:/p/src/utils",
|
||||
"d:/p/lib/utils",
|
||||
"f:/p/lib/utils/a.ts",
|
||||
]);
|
||||
expect(items[1]).toMatchObject({
|
||||
kind: "file",
|
||||
folder: true,
|
||||
name: "utils",
|
||||
dir: "src",
|
||||
nameIndices: [0, 1, 2, 3],
|
||||
});
|
||||
|
||||
// A `File.kt:42` query asks for a file.
|
||||
c.run({ query: "util:3", tab: "files", includeNonProject: false, extra: {} });
|
||||
await flush();
|
||||
expect(
|
||||
last()
|
||||
.sections.get("files")!
|
||||
.items.every((i) => !("folder" in i) || !i.folder),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("parses File.kt:42 and opens files at the line", async () => {
|
||||
const findFiles = vi.fn(async () => [fm("File.kt")]);
|
||||
const { c, last } = setup({ findFiles });
|
||||
|
||||
@@ -313,6 +313,8 @@ function SearchEverywhereDialog() {
|
||||
return;
|
||||
}
|
||||
case "file":
|
||||
if (row.folder) return closeThen(() => void executeAction("navigate.selectInProjectView", row.path));
|
||||
return closeThen(() => void openResult(row, { side }));
|
||||
case "symbol":
|
||||
case "text":
|
||||
return closeThen(() => void openResult(row, { side }));
|
||||
@@ -448,7 +450,8 @@ function SearchEverywhereDialog() {
|
||||
useEffect(() => {
|
||||
if (nothingInProject) widenSearchEverywhere(searchQuery);
|
||||
}, [nothingInProject, searchQuery]);
|
||||
const canSplit = activeRow?.kind === "file" || activeRow?.kind === "symbol" || activeRow?.kind === "text";
|
||||
const canSplit =
|
||||
(activeRow?.kind === "file" && !activeRow.folder) || activeRow?.kind === "symbol" || activeRow?.kind === "text";
|
||||
const onTabKey = (e: KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
|
||||
e.preventDefault();
|
||||
@@ -708,7 +711,7 @@ function describe(row: SeRow | undefined): string {
|
||||
function previewOf(row: SeRow | undefined): { path: string; line: number; highlights: PreviewHighlight[] } | null {
|
||||
switch (row?.kind) {
|
||||
case "file":
|
||||
return { path: row.path, line: row.line ?? 0, highlights: [] };
|
||||
return row.folder ? null : { path: row.path, line: row.line ?? 0, highlights: [] };
|
||||
case "symbol":
|
||||
return {
|
||||
path: row.path,
|
||||
@@ -780,7 +783,16 @@ function ResultRow({
|
||||
</span>
|
||||
);
|
||||
case "file":
|
||||
return <FileRow path={row.path} name={row.name} dir={row.dir} indices={row.nameIndices} line={row.line} />;
|
||||
return (
|
||||
<FileRow
|
||||
path={row.path}
|
||||
name={row.name}
|
||||
dir={row.dir}
|
||||
indices={row.nameIndices}
|
||||
line={row.line}
|
||||
folder={row.folder}
|
||||
/>
|
||||
);
|
||||
case "symbol":
|
||||
return (
|
||||
<>
|
||||
@@ -911,18 +923,20 @@ function FileRow({
|
||||
dir,
|
||||
indices,
|
||||
line,
|
||||
folder,
|
||||
}: {
|
||||
path: string;
|
||||
name: string;
|
||||
dir: string;
|
||||
indices: number[];
|
||||
line: number | null;
|
||||
folder?: boolean;
|
||||
}) {
|
||||
const deco = useFileDecoration(path);
|
||||
const { icon } = useFilePresentation(path);
|
||||
return (
|
||||
<>
|
||||
<Icon name={icon} size={16} className={styles.icon} />
|
||||
<Icon name={folder ? "folder" : icon} size={16} className={styles.icon} />
|
||||
<span className={styles.main}>
|
||||
<span className={styles.name} style={deco.color ? { color: deco.color } : undefined}>
|
||||
<HighlightedText text={name} indices={indices} tone="strong" />
|
||||
@@ -935,8 +949,9 @@ function FileRow({
|
||||
}
|
||||
|
||||
type NavigationRow = Extract<SeRow, { kind: "file" | "symbol" | "text" }>;
|
||||
/** A row that opens in an editor (a directory selects in the Project view instead). */
|
||||
function isNavigationRow(row: SeRow): row is NavigationRow {
|
||||
return row.kind === "file" || row.kind === "symbol" || row.kind === "text";
|
||||
return (row.kind === "file" && !row.folder) || row.kind === "symbol" || row.kind === "text";
|
||||
}
|
||||
function openResult(row: NavigationRow, options: Parameters<typeof openLocation>[2]): Promise<void> {
|
||||
if (row.kind === "file")
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
TAB_LIMIT,
|
||||
actionItem,
|
||||
fileItem,
|
||||
folderItem,
|
||||
matchActions,
|
||||
matchSettings,
|
||||
mergeActionResults,
|
||||
@@ -40,6 +41,8 @@ import {
|
||||
|
||||
export interface SeDeps {
|
||||
findFiles(query: string, limit: number): Promise<FileMatch[]>;
|
||||
/** Directories by name or path (`relPath` ending in `/`), listed with the files. */
|
||||
findDirs?(query: string, limit: number): Promise<FileMatch[]>;
|
||||
findSymbols(query: string, filter: SymbolFilter, limit: number): Promise<SymbolMatch[]>;
|
||||
/** Optional language-server symbols; rejects with `unavailable` when no server. */
|
||||
workspaceSymbols?(request: RequestId, query: string): Promise<WorkspaceSymbol[]>;
|
||||
@@ -253,12 +256,24 @@ export class SeController {
|
||||
? (path: string) => (!scope || scope(path)) && (!filter || passesFilter(filter, path))
|
||||
: null;
|
||||
// Over-fetch so a narrowed scope still fills the section.
|
||||
const fetched = await this.deps.findFiles(text, keep ? limit * 4 + 1 : limit + 1);
|
||||
const matches = keep ? fetched.filter((m) => keep(m.path)) : fetched;
|
||||
const want = keep ? limit * 4 + 1 : limit + 1;
|
||||
// Directories rank with the files (equal scores: the file first); a file type filter
|
||||
// and a `File.kt:42` query ask for files only.
|
||||
const withDirs = !!this.deps.findDirs && !!text && !filter && q.line === null;
|
||||
const [fetched, dirs] = await Promise.all([
|
||||
this.deps.findFiles(text, want),
|
||||
withDirs ? this.deps.findDirs!(text, want).catch(() => []) : [],
|
||||
]);
|
||||
const merged = dirs.length
|
||||
? [...fetched.map((m) => ({ m, folder: false })), ...dirs.map((m) => ({ m, folder: true }))].sort(
|
||||
(a, b) => b.m.score - a.m.score || Number(a.folder) - Number(b.folder),
|
||||
)
|
||||
: fetched.map((m) => ({ m, folder: false }));
|
||||
const matches = keep ? merged.filter(({ m }) => keep(m.path)) : merged;
|
||||
this.set(
|
||||
id,
|
||||
gen,
|
||||
matches.slice(0, limit).map((m) => fileItem(m, q.line, q.col)),
|
||||
matches.slice(0, limit).map(({ m, folder }) => (folder ? folderItem(m) : fileItem(m, q.line, q.col))),
|
||||
matches.length > limit,
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { recentActions } from "./store";
|
||||
export function ipcDeps(ctx: ContextKeys): SeDeps {
|
||||
return {
|
||||
findFiles: (q, limit) => call(commands.findFiles(q, limit)),
|
||||
findDirs: (q, limit) => call(commands.findDirs(q, limit)),
|
||||
findSymbols: (q, filter, limit) => call(commands.findSymbols(q, filter, limit)),
|
||||
workspaceSymbols: (request, q) => call(commands.lspWorkspaceSymbols(request, q)),
|
||||
searchText: async (request, query, onFile) => {
|
||||
|
||||
@@ -76,6 +76,8 @@ export interface FileItem {
|
||||
nameIndices: number[];
|
||||
line: number | null;
|
||||
col: number | null;
|
||||
/** A directory: choosing it selects it in the Project view. */
|
||||
folder?: boolean;
|
||||
}
|
||||
|
||||
export interface SymbolItem {
|
||||
@@ -169,6 +171,12 @@ export function fileItem(
|
||||
};
|
||||
}
|
||||
|
||||
/** A directory match (`relPath` ends in `/`), listed with the files as IDEA's Go to File does. */
|
||||
export function folderItem(m: Pick<FileMatch, "path" | "relPath" | "indices">): FileItem {
|
||||
const rel = m.relPath.replace(/\/$/, "");
|
||||
return { ...fileItem({ ...m, relPath: rel }), key: `d:${m.path}`, folder: true };
|
||||
}
|
||||
|
||||
export function symbolItem(m: SymbolMatch): SymbolItem {
|
||||
return {
|
||||
kind: "symbol",
|
||||
|
||||
Reference in new issue
Block a user