فهرست منبع

fix(tui): keep tab behavior in shell mode

Kit Langton 1 ماه پیش
والد
کامیت
c40b478756

+ 5 - 0
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

@@ -33,6 +33,7 @@ import { DialogAlert } from "../../ui/dialog-alert"
 import { useToast } from "../../ui/toast"
 import { useKV } from "../../context/kv"
 import { useTextareaKeybindings } from "../textarea-keybindings"
+import { shellPassthrough } from "./key"
 import { DialogSkill } from "../dialog-skill"
 
 export type PromptProps = {
@@ -894,6 +895,10 @@ export function Prompt(props: PromptProps) {
                   return
                 }
                 if (store.mode === "shell") {
+                  if (shellPassthrough(keybind, e, store.mode)) {
+                    e.stopPropagation()
+                    return
+                  }
                   if ((e.name === "backspace" && input.visualCursor.offset === 0) || e.name === "escape") {
                     setStore("mode", "normal")
                     e.preventDefault()

+ 16 - 0
packages/opencode/src/cli/cmd/tui/component/prompt/key.ts

@@ -0,0 +1,16 @@
+import type { KeybindKey } from "@/cli/cmd/tui/context/keybind"
+
+type Mode = "normal" | "shell"
+
+type Key = {
+  readonly name?: string
+  readonly shift?: boolean
+}
+
+export function shellPassthrough<E extends Key>(
+  keybind: { readonly match: (key: KeybindKey, evt: E) => boolean | undefined },
+  evt: E,
+  mode: Mode,
+) {
+  return mode === "shell" && (keybind.match("agent_cycle", evt) || keybind.match("agent_cycle_reverse", evt))
+}

+ 26 - 0
packages/opencode/test/cli/tui/prompt-key.test.ts

@@ -0,0 +1,26 @@
+import { describe, expect, test } from "bun:test"
+import { shellPassthrough } from "../../../src/cli/cmd/tui/component/prompt/key"
+
+const key = (name: string, extra: { readonly shift?: boolean } = {}) => ({
+  name,
+  shift: false,
+  ...extra,
+})
+
+describe("shellPassthrough", () => {
+  test("allows tab agent-cycle bindings to pass through in shell mode", () => {
+    const match = (target: string, evt: { readonly name?: string }) => target === "agent_cycle" && evt.name === "tab"
+    expect(shellPassthrough({ match }, key("tab"), "shell")).toBe(true)
+  })
+
+  test("allows reverse agent-cycle bindings to pass through in shell mode", () => {
+    const match = (target: string, evt: { readonly name?: string; readonly shift?: boolean }) =>
+      target === "agent_cycle_reverse" && evt.name === "tab" && evt.shift
+    expect(shellPassthrough({ match }, key("tab", { shift: true }), "shell")).toBe(true)
+  })
+
+  test("does not bypass agent-cycle outside shell mode", () => {
+    const match = (target: string, evt: { readonly name?: string }) => target === "agent_cycle" && evt.name === "tab"
+    expect(shellPassthrough({ match }, key("tab"), "normal")).toBe(false)
+  })
+})