fix(tui): complete slash commands after leading whitespace (#10218)

This commit is contained in:
xu0o0
2026-09-30 23:13:01 +02:00
committed by GitHub
parent b35af04f46
commit 65117e31f2
2 changed files with 52 additions and 6 deletions
+7 -6
View File
@@ -335,11 +335,12 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
};
}
if (!options.force && textBeforeCursor.startsWith("/")) {
const spaceIndex = textBeforeCursor.indexOf(" ");
const commandText = textBeforeCursor.trimStart();
if (!options.force && commandText.startsWith("/")) {
const spaceIndex = commandText.indexOf(" ");
if (spaceIndex === -1) {
const prefix = textBeforeCursor.slice(1);
const prefix = commandText.slice(1);
const commandItems = this.commands.map((cmd) => {
const name = "name" in cmd ? cmd.name : cmd.value;
const hint = "argumentHint" in cmd && cmd.argumentHint ? cmd.argumentHint : undefined;
@@ -371,12 +372,12 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
return {
items: filtered,
prefix: textBeforeCursor,
prefix: commandText,
};
}
const commandName = textBeforeCursor.slice(1, spaceIndex);
const argumentText = textBeforeCursor.slice(spaceIndex + 1);
const commandName = commandText.slice(1, spaceIndex);
const argumentText = commandText.slice(spaceIndex + 1);
const command = this.commands.find((cmd) => {
const name = "name" in cmd ? cmd.name : cmd.value;
@@ -32,6 +32,51 @@ describe("CombinedAutocompleteProvider slash-command filter", () => {
assert.ok(items.includes("model"));
});
it("completes commands after leading whitespace and preserves it", async () => {
const provider = new CombinedAutocompleteProvider([{ name: "model" }], process.cwd());
for (const [line, expected] of [
[" /", " /model "],
[" /mod", " /model "],
["\t/mod", "\t/model "],
] as const) {
const result = await provider.getSuggestions([line], 0, line.length, {
signal: new AbortController().signal,
});
assert.ok(result);
assert.equal(result.prefix, line.trimStart());
assert.deepStrictEqual(
result.items.map((item) => item.value),
["model"],
);
const applied = provider.applyCompletion([line], 0, line.length, result.items[0]!, result.prefix);
assert.equal(applied.lines[0], expected);
assert.equal(applied.cursorCol, expected.length);
}
});
it("completes command arguments after leading whitespace", async () => {
const provider = new CombinedAutocompleteProvider(
[
{
name: "model",
getArgumentCompletions: (prefix: string) => {
assert.equal(prefix, "son");
return [{ value: "sonnet", label: "sonnet" }];
},
},
],
process.cwd(),
);
const line = " /model son";
const result = await provider.getSuggestions([line], 0, line.length, {
signal: new AbortController().signal,
});
assert.ok(result);
assert.equal(result.prefix, "son");
const applied = provider.applyCompletion([line], 0, line.length, result.items[0]!, result.prefix);
assert.equal(applied.lines[0], " /model sonnet");
});
it("keeps explicit skill: queries working", async () => {
const items = await suggestionsFor("skill:side");
assert.ok(items.includes("skill:to-sidecar"));