فهرست منبع

feat(TUI): add autocomplete readline style keybinds (#3717)

Co-authored-by: Aiden Cline <[email protected]>
Timo Clasen 3 ماه پیش
والد
کامیت
8a9a474df6

+ 9 - 4
packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx

@@ -22,15 +22,16 @@ export type CommandOption = DialogSelectOption & {
 
 function init() {
   const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
+  const [suspendCount, setSuspendCount] = createSignal(0)
   const dialog = useDialog()
   const keybind = useKeybind()
   const options = createMemo(() => {
     return registrations().flatMap((x) => x())
   })
+  const suspended = () => suspendCount() > 0
 
-  let keybinds = true
   useKeyboard((evt) => {
-    if (!keybinds) return
+    if (suspended()) return
     for (const option of options()) {
       if (option.keybind && keybind.match(option.keybind, evt)) {
         evt.preventDefault()
@@ -50,8 +51,9 @@ function init() {
       }
     },
     keybinds(enabled: boolean) {
-      keybinds = enabled
+      setSuspendCount((count) => count + (enabled ? -1 : 1))
     },
+    suspended,
     show() {
       dialog.replace(() => <DialogCommand options={options()} />)
     },
@@ -83,7 +85,10 @@ export function CommandProvider(props: ParentProps) {
   const keybind = useKeybind()
 
   useKeyboard((evt) => {
-    if (keybind.match("command_list", evt) && dialog.stack.length === 0) {
+    if (value.suspended()) return
+    if (dialog.stack.length > 0) return
+    if (evt.defaultPrevented) return
+    if (keybind.match("command_list", evt)) {
       evt.preventDefault()
       dialog.replace(() => <DialogCommand options={value.options} />)
       return

+ 25 - 5
packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx

@@ -393,11 +393,31 @@ export function Autocomplete(props: {
       },
       onKeyDown(e: KeyEvent) {
         if (store.visible) {
-          if (e.name === "up") move(-1)
-          if (e.name === "down") move(1)
-          if (e.name === "escape") hide()
-          if (e.name === "return" || e.name === "tab") select()
-          if (["up", "down", "return", "tab", "escape"].includes(e.name)) e.preventDefault()
+          const name = e.name?.toLowerCase()
+          const ctrlOnly = e.ctrl && !e.meta && !e.shift
+          const isNavUp = name === "up" || (ctrlOnly && name === "p")
+          const isNavDown = name === "down" || (ctrlOnly && name === "n")
+
+          if (isNavUp) {
+            move(-1)
+            e.preventDefault()
+            return
+          }
+          if (isNavDown) {
+            move(1)
+            e.preventDefault()
+            return
+          }
+          if (name === "escape") {
+            hide()
+            e.preventDefault()
+            return
+          }
+          if (name === "return" || name === "tab") {
+            select()
+            e.preventDefault()
+            return
+          }
         }
         if (!store.visible) {
           if (e.name === "@") {