Rank slash command autocomplete matches
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { filterAndRankCommandAutocompleteEntries } from "./agent-command-autocomplete";
|
||||
|
||||
describe("filterAndRankCommandAutocompleteEntries", () => {
|
||||
const entries = [
|
||||
{ source: "provider" as const, command: { name: "paseo-committee" } },
|
||||
{ source: "provider" as const, command: { name: "commit" } },
|
||||
{ source: "provider" as const, command: { name: "paseo-advisor" } },
|
||||
];
|
||||
|
||||
it("ranks command-name prefixes above later word-boundary partial matches", () => {
|
||||
const result = filterAndRankCommandAutocompleteEntries(entries, "comm");
|
||||
|
||||
expect(result.map((entry) => entry.command.name)).toEqual(["commit", "paseo-committee"]);
|
||||
});
|
||||
|
||||
it("matches client command aliases", () => {
|
||||
const result = filterAndRankCommandAutocompleteEntries(
|
||||
[
|
||||
{ source: "client" as const, command: { name: "exit", aliases: ["quit", "q"] } },
|
||||
{ source: "client" as const, command: { name: "clear", aliases: ["new"] } },
|
||||
],
|
||||
"q",
|
||||
);
|
||||
|
||||
expect(result.map((entry) => entry.command.name)).toEqual(["exit"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { compareMatchScores, type MatchScore, scoreTextFields } from "@/utils/score-match";
|
||||
|
||||
interface CommandAutocompleteEntry {
|
||||
command: {
|
||||
name: string;
|
||||
aliases?: readonly string[];
|
||||
};
|
||||
}
|
||||
|
||||
interface ScoredCommandAutocompleteEntry<TEntry> {
|
||||
entry: TEntry;
|
||||
score: MatchScore;
|
||||
}
|
||||
|
||||
function scoreCommandAutocompleteEntry(
|
||||
entry: CommandAutocompleteEntry,
|
||||
query: string,
|
||||
): MatchScore | null {
|
||||
return scoreTextFields(query, [entry.command.name, ...(entry.command.aliases ?? [])]);
|
||||
}
|
||||
|
||||
export function filterAndRankCommandAutocompleteEntries<TEntry extends CommandAutocompleteEntry>(
|
||||
entries: readonly TEntry[],
|
||||
query: string,
|
||||
): TEntry[] {
|
||||
const normalizedQuery = query.trim().toLowerCase();
|
||||
if (normalizedQuery.length === 0) {
|
||||
return [...entries];
|
||||
}
|
||||
|
||||
const scoredEntries: ScoredCommandAutocompleteEntry<TEntry>[] = [];
|
||||
for (const entry of entries) {
|
||||
const score = scoreCommandAutocompleteEntry(entry, normalizedQuery);
|
||||
if (score) {
|
||||
scoredEntries.push({ entry, score });
|
||||
}
|
||||
}
|
||||
|
||||
scoredEntries.sort((a, b) => {
|
||||
const scoreComparison = compareMatchScores(a.score, b.score);
|
||||
if (scoreComparison !== 0) {
|
||||
return scoreComparison;
|
||||
}
|
||||
return a.entry.command.name.localeCompare(b.entry.command.name);
|
||||
});
|
||||
|
||||
return scoredEntries.map((scored) => scored.entry);
|
||||
}
|
||||
Reference in New Issue
Block a user