mirror of
https://github.com/earendil-works/pi.git
synced 2026-06-18 15:54:04 +08:00
fix(tui): re-query autocomplete picker on cursor movement
moveCursor() repositioned the cursor but never called updateAutocomplete() or cancelAutocomplete(), unlike insertCharacter()/handleBackspace(). So an open autocomplete picker went stale when the cursor moved: e.g. typing `/cmd ` (argument menu open) then arrowing Left back into the command name left the argument list showing against a `/cmd` prefix, and a Tab there concatenated the stale suggestion onto the partial command name (e.g. `/swarepo`). Sync the picker at the end of moveCursor(): when a picker is open, re-query via updateAutocomplete(), which refreshes the list for the new cursor position or closes it when nothing matches. Adds a regression test in test/editor.test.ts (verified to fail without the fix). Fixes earendil-works/pi#5496.
This commit is contained in:
@@ -1742,6 +1742,18 @@ export class Editor implements Component, Focusable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep an open autocomplete picker in sync with the new cursor
|
||||
// position: cursor movement changes the text before the cursor, so a
|
||||
// picker computed for the old position is stale. Re-query so it
|
||||
// refreshes — or closes when the new position yields no suggestions —
|
||||
// mirroring insertCharacter()/handleBackspace(). Without this, arrowing
|
||||
// left from `/cmd ` back into the command name leaves the argument
|
||||
// picker showing against a `/cmd` prefix (and a Tab there would
|
||||
// concatenate the stale suggestion onto the partial command name).
|
||||
if (this.autocompleteState) {
|
||||
this.updateAutocomplete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2250,6 +2250,65 @@ describe("Editor component", () => {
|
||||
assert.strictEqual(editor.isShowingAutocomplete(), true);
|
||||
});
|
||||
|
||||
it("re-queries the autocomplete picker when the cursor moves back into the command name", async () => {
|
||||
// Regression for earendil-works/pi#5496: arrowing left out of a slash
|
||||
// command's argument region must re-query the picker, not leave the
|
||||
// stale argument list showing. Before the fix, moveCursor() never
|
||||
// called updateAutocomplete(), so `/cmd ` (argument menu) + Left kept
|
||||
// displaying the arguments against a `/cmd` prefix — and a Tab there
|
||||
// would concatenate the stale suggestion onto the partial command name.
|
||||
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||
|
||||
const mockProvider: AutocompleteProvider = {
|
||||
getSuggestions: async (lines, _cursorLine, cursorCol) => {
|
||||
const before = (lines[0] || "").slice(0, cursorCol);
|
||||
if (!before.startsWith("/")) return null;
|
||||
// Past the command name (a space before the cursor): offer arguments.
|
||||
if (before.includes(" ")) {
|
||||
return {
|
||||
items: [
|
||||
{ value: "repo", label: "repo" },
|
||||
{ value: "message", label: "message" },
|
||||
{ value: "help", label: "help" },
|
||||
],
|
||||
prefix: before.slice(before.indexOf(" ") + 1),
|
||||
};
|
||||
}
|
||||
// Inside the command name: offer the command name only.
|
||||
return { items: [{ value: "cmd", label: "cmd" }], prefix: before };
|
||||
},
|
||||
applyCompletion,
|
||||
};
|
||||
|
||||
editor.setAutocompleteProvider(mockProvider);
|
||||
|
||||
// Type `/cmd ` so the picker ends up showing the argument list.
|
||||
for (const ch of "/cmd ") {
|
||||
editor.handleInput(ch);
|
||||
await flushAutocomplete();
|
||||
}
|
||||
assert.strictEqual(editor.getText(), "/cmd ");
|
||||
assert.strictEqual(editor.isShowingAutocomplete(), true);
|
||||
const atArg = editor
|
||||
.render(80)
|
||||
.map((l) => stripVTControlCharacters(l))
|
||||
.join("\n");
|
||||
assert.ok(atArg.includes("repo"), "argument menu should be visible at `/cmd `");
|
||||
|
||||
// Arrow Left back into the command name (`/cmd`).
|
||||
editor.handleInput("\x1b[D");
|
||||
await flushAutocomplete();
|
||||
|
||||
// The picker must have re-queried: the stale argument items are gone
|
||||
// (replaced by the command-name suggestion, or the picker closed).
|
||||
const afterMove = editor
|
||||
.render(80)
|
||||
.map((l) => stripVTControlCharacters(l))
|
||||
.join("\n");
|
||||
assert.ok(!afterMove.includes("repo"), "stale argument menu must not survive the cursor move");
|
||||
assert.ok(!afterMove.includes("message"), "stale argument menu must not survive the cursor move");
|
||||
});
|
||||
|
||||
it("debounces # autocomplete while typing", async () => {
|
||||
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||
let suggestionCalls = 0;
|
||||
|
||||
Reference in New Issue
Block a user