Procházet zdrojové kódy

feat(tui): implement smooth scrolling for autocomplete dropdown navigation (#5559)

Co-authored-by: Aiden Cline <[email protected]>
Co-authored-by: Aiden Cline <[email protected]>
Sherlock Holmes před 3 měsíci
rodič
revize
6a802c01cd

+ 23 - 4
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 fuzzysort from "fuzzysort"
 import { firstBy } from "remeda"
 import { firstBy } from "remeda"
 import { createMemo, createResource, createEffect, onMount, onCleanup, For, Show, createSignal } from "solid-js"
 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()]
       store.visible === "@" ? [...agents(), ...(files() || [])] : [...commands()]
     ).filter((x) => x.disabled !== true)
     ).filter((x) => x.disabled !== true)
     const currentFilter = filter()
     const currentFilter = filter()
-    if (!currentFilter) return mixed.slice(0, 10)
+    if (!currentFilter) return mixed
     const result = fuzzysort.go(currentFilter, mixed, {
     const result = fuzzysort.go(currentFilter, mixed, {
       keys: [(obj) => obj.display.trimEnd(), "description", (obj) => obj.aliases?.join(" ") ?? ""],
       keys: [(obj) => obj.display.trimEnd(), "description", (obj) => obj.aliases?.join(" ") ?? ""],
       limit: 10,
       limit: 10,
@@ -395,7 +395,19 @@ export function Autocomplete(props: {
     let next = store.selected + direction
     let next = store.selected + direction
     if (next < 0) next = options().length - 1
     if (next < 0) next = options().length - 1
     if (next >= options().length) next = 0
     if (next >= options().length) next = 0
+    moveTo(next)
+  }
+
+  function moveTo(next: number) {
     setStore("selected", next)
     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() {
   function select() {
@@ -497,6 +509,8 @@ export function Autocomplete(props: {
     return 1
     return 1
   })
   })
 
 
+  let scroll: ScrollBoxRenderable
+
   return (
   return (
     <box
     <box
       visible={store.visible !== false}
       visible={store.visible !== false}
@@ -508,7 +522,12 @@ export function Autocomplete(props: {
       {...SplitBorder}
       {...SplitBorder}
       borderColor={theme.border}
       borderColor={theme.border}
     >
     >
-      <box backgroundColor={theme.backgroundMenu} height={height()}>
+      <scrollbox
+        ref={(r: ScrollBoxRenderable) => (scroll = r)}
+        backgroundColor={theme.backgroundMenu}
+        height={height()}
+        scrollbarOptions={{ visible: false }}
+      >
         <For
         <For
           each={options()}
           each={options()}
           fallback={
           fallback={
@@ -535,7 +554,7 @@ export function Autocomplete(props: {
             </box>
             </box>
           )}
           )}
         </For>
         </For>
-      </box>
+      </scrollbox>
     </box>
     </box>
   )
   )
 }
 }