fix(search): match complete paths in file and project search
Treat slash-separated fragments as searchable text and use the same full-path fuzzy matcher for daemon results and remembered project paths.
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
fuzzyPolicyForToken,
|
||||
matchRanges,
|
||||
scoreMatch,
|
||||
scorePathMatch,
|
||||
scoreTextFields,
|
||||
} from "./text-match.js";
|
||||
|
||||
@@ -171,6 +172,28 @@ describe("scoreMatch typo tolerance", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("scorePathMatch", () => {
|
||||
it("matches a literal fragment anywhere in the complete path", () => {
|
||||
expect(
|
||||
scorePathMatch("skills/", "something/something-else/skills/paseo-advisor/SKILL.md"),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it("fuzzy-matches spaced text against a compact path", () => {
|
||||
expect(scorePathMatch("blank page editor", "blankpage/editor")).toEqual({
|
||||
tier: 0,
|
||||
offset: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the original path offset for compact matches", () => {
|
||||
expect(scorePathMatch("blank page editor", "projects/blankpage/editor")).toEqual({
|
||||
tier: 4,
|
||||
offset: 9,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("scoreTextFields", () => {
|
||||
it("requires every token to match some field", () => {
|
||||
expect(scoreTextFields("stripe billing", ["add stripe billing system"])).not.toBeNull();
|
||||
|
||||
@@ -201,6 +201,39 @@ export function scoreMatch(
|
||||
return fuzzy ? scoreFuzzyMatch(q, t, fuzzy) : null;
|
||||
}
|
||||
|
||||
interface CompactText {
|
||||
value: string;
|
||||
offsets: number[];
|
||||
}
|
||||
|
||||
function compactText(value: string): CompactText {
|
||||
let compact = "";
|
||||
const offsets: number[] = [];
|
||||
for (let index = 0; index < value.length; index += 1) {
|
||||
if (!/[a-z0-9]/i.test(value[index])) continue;
|
||||
compact += value[index].toLowerCase();
|
||||
offsets.push(index);
|
||||
}
|
||||
return { value: compact, offsets };
|
||||
}
|
||||
|
||||
/** Match a query against a complete displayed path, including its separators. */
|
||||
export function scorePathMatch(query: string, path: string): MatchScore | null {
|
||||
const direct = scoreMatch(query, path);
|
||||
if (direct) return direct;
|
||||
|
||||
const compactQuery = compactText(query);
|
||||
const compactPath = compactText(path);
|
||||
if (!compactQuery.value || !compactPath.value) return null;
|
||||
|
||||
const compactScore = scoreMatch(compactQuery.value, compactPath.value);
|
||||
if (!compactScore) return null;
|
||||
return {
|
||||
...compactScore,
|
||||
offset: compactPath.offsets[compactScore.offset] ?? compactScore.offset,
|
||||
};
|
||||
}
|
||||
|
||||
export interface MatchRange {
|
||||
start: number;
|
||||
length: number;
|
||||
|
||||
Reference in New Issue
Block a user