diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 5f66dc822..30f6f1c79 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -129,6 +129,16 @@ export function Autocomplete(props: { return props.input().getTextRange(store.index + 1, props.input().cursorOffset) }) + // filter() reads reactive props.value plus non-reactive cursor/text state. + // On keypress those can be briefly out of sync, so filter() may return an empty/partial string. + // Copy it into search in an effect because effects run after reactive updates have been rendered and painted + // so the input has settled and all consumers read the same stable value. + const [search, setSearch] = createSignal("") + createEffect(() => { + const next = filter() + setSearch(next ? next : ""); + }) + // When the filter changes due to how TUI works, the mousemove might still be triggered // via a synthetic event as the layout moves underneath the cursor. This is a workaround to make sure the input mode remains keyboard so // that the mouseover event doesn't trigger when filtering. @@ -208,7 +218,7 @@ export function Autocomplete(props: { } const [files] = createResource( - () => filter(), + () => search(), async (query) => { if (!store.visible || store.visible === "/") return [] @@ -378,9 +388,9 @@ export function Autocomplete(props: { const mixed: AutocompleteOption[] = store.visible === "@" ? [...agentsValue, ...(filesValue || []), ...mcpResources()] : [...commandsValue] - const currentFilter = filter() + const searchValue = search() - if (!currentFilter) { + if (!searchValue) { return mixed } @@ -388,7 +398,7 @@ export function Autocomplete(props: { return prev } - const result = fuzzysort.go(removeLineRange(currentFilter), mixed, { + const result = fuzzysort.go(removeLineRange(searchValue), mixed, { keys: [ (obj) => removeLineRange((obj.value ?? obj.display).trimEnd()), "description", @@ -398,7 +408,7 @@ export function Autocomplete(props: { scoreFn: (objResults) => { const displayResult = objResults[0] let score = objResults.score - if (displayResult && displayResult.target.startsWith(store.visible + currentFilter)) { + if (displayResult && displayResult.target.startsWith(store.visible + searchValue)) { score *= 2 } const frecencyScore = objResults.obj.path ? frecency.getFrecency(objResults.obj.path) : 0