Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ide/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3494,7 +3494,9 @@ export class App extends React.Component<{ playOpening?: boolean }, S> {
? `"${query}" 는 훑은 범위 안에 없었습니다. 파일이 많아 색인을 다 훑지 못했으니(${symbolIndex.TS_MODEL_CAP}개까지) 없다고 단정하지 말고 search_files 로 확인하세요.`
: `"${query}" 로 찾은 심볼 없음. 색인은 있습니다(${r.sources.join(", ")}) — 이름이 다르거나 색인이 없는 언어의 파일일 수 있으니 search_files 도 해 보세요.`;
}
return r.hits.map(h => `${h.rel}:${h.line}:${h.column} ${h.container ? h.container + "." : ""}${h.name}`).join(String.fromCharCode(10));
// 잘랐으면 잘랐다고 말한다 — 안 그러면 이 목록이 전부인 줄 안다.
return r.hits.map(h => `${h.rel}:${h.line}:${h.column} ${h.container ? h.container + "." : ""}${h.name}`).join(String.fromCharCode(10))
+ (r.sliced ? String.fromCharCode(10) + String.fromCharCode(10) + "… 상한에서 잘렸습니다. 이름을 더 정확히 주면 좁혀집니다." : "");
}
if (call.name === "find_references") {
const name = String(call.input?.name ?? "").trim();
Expand Down
18 changes: 13 additions & 5 deletions ide/src/editor/symbolIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import monaco from "./monacoSetup";
import * as lspClient from "./lspClient";
import * as projectModels from "./projectModels";
import { flattenNavTree, isTestPath } from "../engine/navTree";
import { flattenNavTree, orderSymbols } from "../engine/navTree";

export interface SymbolHit {
name: string;
Expand All @@ -37,6 +37,8 @@ export interface SymbolAnswer {
capped: boolean;
/** 파일이 너무 많아 색인을 아예 만들지 않았다. "이 언어는 지원 안 함" 과 다르다. */
tooBig: boolean;
/** 상한에서 잘랐다 — 보여준 것이 전부가 아니다. */
sliced: boolean;
}

/** 워커가 주는 파일 이름(모델 uri) → 워크스페이스 상대 경로 */
Expand Down Expand Up @@ -122,7 +124,7 @@ export function isTooBig(): boolean { return projectModels.isPreloadSkipped(); }
/** 이름으로 심볼을 찾는다. 두 통로에 모두 물어보고 합친다. */
export async function findSymbols(query: string, max = 100): Promise<SymbolAnswer> {
const q = String(query ?? "").trim();
if (!q) return { hits: [], sources: [], capped: false, tooBig: false };
if (!q) return { hits: [], sources: [], capped: false, tooBig: false, sliced: false };
const [ts, lsp] = await Promise.all([fromTypescript(q, max), fromLsp(q)]);
const sources: ("ts" | "lsp")[] = [];
if (ts.available) sources.push("ts");
Expand All @@ -137,9 +139,15 @@ export async function findSymbols(query: string, max = 100): Promise<SymbolAnswe
seen.add(key);
hits.push(h);
}
// 테스트 파일은 뒤로. describe("이름", …) 이 심볼로 잡혀 첫 답이 테스트가 되곤 했다.
const ordered = [...hits.filter(h => !isTestPath(h.rel)), ...hits.filter(h => isTestPath(h.rel))];
return { hits: ordered.slice(0, max), sources, capped: ts.capped, tooBig: projectModels.isPreloadSkipped() };
// 자르기 전에 전체를 다시 세운다 — 정확 일치가 뒤쪽 파일에 있어도 살아남게.
const ordered = orderSymbols(hits, q);
return {
hits: ordered.slice(0, max),
sources,
capped: ts.capped,
tooBig: projectModels.isPreloadSkipped(),
sliced: ordered.length > max,
};
}

// ── 참조 찾기 ───────────────────────────────────────────────────────────────
Expand Down
46 changes: 45 additions & 1 deletion ide/src/engine/navTree.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { flattenNavTree, isTestPath, TS_KIND } from "./navTree";
import { flattenNavTree, isTestPath, orderSymbols, TS_KIND } from "./navTree";

/** 실제 워커가 주는 모양 그대로 — 맨 위는 파일 자신이고 이름에 따옴표가 붙는다. */
const TREE = {
Expand Down Expand Up @@ -132,3 +132,47 @@ describe("isTestPath", () => {
}
});
});

describe("orderSymbols", () => {
const s = (name: string, rel: string) => ({ name, rel });

// 이 케이스 때문에 있다. 앞쪽 파일이 부분 일치를 잔뜩 내면 정확 일치가 잘려
// 나가고, 참조 찾기가 "정의를 못 찾았습니다" 라고 답한다 — 있는데도.
it("정확히 같은 이름을 파일을 가로질러 맨 앞으로", () => {
const hits = [
s("fooBarBaz", "a.ts"), s("fooBarQux", "b.ts"), s("foo", "z.ts"),
];
expect(orderSymbols(hits, "foo").map(h => h.rel)).toEqual(["z.ts", "a.ts", "b.ts"]);
});

it("앞에서 시작하는 것이 그다음", () => {
const hits = [s("xxfoo", "a.ts"), s("foobar", "b.ts")];
expect(orderSymbols(hits, "foo").map(h => h.rel)).toEqual(["b.ts", "a.ts"]);
});

it("같은 등급이면 테스트 파일이 뒤", () => {
const hits = [s("foo", "a.test.ts"), s("foo", "a.ts")];
expect(orderSymbols(hits, "foo").map(h => h.rel)).toEqual(["a.ts", "a.test.ts"]);
});

it("정확 일치는 테스트 파일이어도 부분 일치보다 앞", () => {
const hits = [s("fooBar", "a.ts"), s("foo", "a.test.ts")];
expect(orderSymbols(hits, "foo").map(h => h.rel)).toEqual(["a.test.ts", "a.ts"]);
});

it("등급이 같으면 들어온 순서를 지킨다", () => {
const hits = [s("foo", "b.ts"), s("foo", "a.ts")];
expect(orderSymbols(hits, "foo").map(h => h.rel)).toEqual(["b.ts", "a.ts"]);
});

it("원본을 건드리지 않는다", () => {
const hits = [s("zz", "a.ts"), s("foo", "b.ts")];
const before = hits.map(h => h.rel);
orderSymbols(hits, "foo");
expect(hits.map(h => h.rel)).toEqual(before);
});

it("빈 목록", () => {
expect(orderSymbols([], "foo")).toEqual([]);
});
});
18 changes: 18 additions & 0 deletions ide/src/engine/navTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,21 @@ function firstOffset(node: any): number {
const n = Number(node?.spans?.[0]?.start);
return Number.isFinite(n) && n >= 0 ? n : 0;
}

/** 여러 파일에서 모은 심볼을 한 줄로 세운다.
*
* flattenNavTree 의 정렬은 **한 파일 안에서만** 돈다. 파일을 가로질러 모은 뒤
* 자르면, 앞쪽 파일이 부분 일치를 잔뜩 내는 바람에 **정확히 그 이름인 정의가
* 잘려 나간다.** 그러면 참조 찾기가 "정의를 못 찾았습니다" 라고 답한다 —
* 있는데도. 그래서 자르기 전에 전체를 다시 세운다.
*
* 순서: 정확히 같은 이름 → 앞에서 시작 → 나머지. 테스트 파일은 각 묶음 안에서 뒤로. */
export function orderSymbols<T extends { name: string; rel: string }>(hits: readonly T[], query: string): T[] {
const q = String(query ?? "").toLowerCase();
const rank = (h: T): number => {
const n = h.name.toLowerCase();
const base = n === q ? 0 : n.startsWith(q) ? 2 : 4;
return base + (isTestPath(h.rel) ? 1 : 0);
};
return hits.map((h, i) => ({ h, i })).sort((a, b) => rank(a.h) - rank(b.h) || a.i - b.i).map(x => x.h);
}
Loading