diff options
| author | Sherlock Holmes <[email protected]> | 2025-12-19 14:40:16 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-12-19 00:40:16 -0600 |
| commit | 6a802c01cd80741544a97dee5aad99d82642cb18 (patch) | |
| tree | 590b3e2428754412ed0f3ae98fcf5ce406b895b4 | |
| parent | 14146428dd3eeec8fb827b08a6ac209c71698872 (diff) | |
| download | opencode-6a802c01cd80741544a97dee5aad99d82642cb18.tar.gz opencode-6a802c01cd80741544a97dee5aad99d82642cb18.zip | |
feat(tui): implement smooth scrolling for autocomplete dropdown navigation (#5559)
Co-authored-by: Aiden Cline <[email protected]>
Co-authored-by: Aiden Cline <[email protected]>
| -rw-r--r-- | packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx | 27 |
1 files changed, 23 insertions, 4 deletions
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 bf656e890..b2221a3b6 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -1,4 +1,4 @@ -import type { BoxRenderable, TextareaRenderable, KeyEvent } from "@opentui/core" +import type { BoxRenderable, TextareaRenderable, KeyEvent, ScrollBoxRenderable } from "@opentui/core" import fuzzysort from "fuzzysort" import { firstBy } from "remeda" import { createMemo, createResource, createEffect, onMount, onCleanup, For, Show, createSignal } from "solid-js" @@ -369,7 +369,7 @@ export function Autocomplete(props: { store.visible === "@" ? [...agents(), ...(files() || [])] : [...commands()] ).filter((x) => x.disabled !== true) const currentFilter = filter() - if (!currentFilter) return mixed.slice(0, 10) + if (!currentFilter) return mixed const result = fuzzysort.go(currentFilter, mixed, { keys: [(obj) => obj.display.trimEnd(), "description", (obj) => obj.aliases?.join(" ") ?? ""], limit: 10, @@ -395,7 +395,19 @@ export function Autocomplete(props: { let next = store.selected + direction if (next < 0) next = options().length - 1 if (next >= options().length) next = 0 + moveTo(next) + } + + function moveTo(next: number) { setStore("selected", next) + if (!scroll) return + const viewportHeight = Math.min(height(), options().length) + const scrollBottom = scroll.scrollTop + viewportHeight + if (next < scroll.scrollTop) { + scroll.scrollBy(next - scroll.scrollTop) + } else if (next + 1 > scrollBottom) { + scroll.scrollBy(next + 1 - scrollBottom) + } } function select() { @@ -497,6 +509,8 @@ export function Autocomplete(props: { return 1 }) + let scroll: ScrollBoxRenderable + return ( <box visible={store.visible !== false} @@ -508,7 +522,12 @@ export function Autocomplete(props: { {...SplitBorder} borderColor={theme.border} > - <box backgroundColor={theme.backgroundMenu} height={height()}> + <scrollbox + ref={(r: ScrollBoxRenderable) => (scroll = r)} + backgroundColor={theme.backgroundMenu} + height={height()} + scrollbarOptions={{ visible: false }} + > <For each={options()} fallback={ @@ -535,7 +554,7 @@ export function Autocomplete(props: { </box> )} </For> - </box> + </scrollbox> </box> ) } |
