From 809e46c9883dd8d4610d8a5e69581a2282eb29ea Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Mon, 30 Mar 2026 09:58:51 +0200 Subject: [PATCH] fix history --- .../opencode/src/cli/cmd/run/footer.view.tsx | 28 +++++++++++++++---- packages/opencode/src/cli/cmd/run/runtime.ts | 6 ++++ packages/opencode/src/cli/cmd/run/types.ts | 2 ++ .../test/cli/run/footer-view.test.tsx | 20 +++++++++++-- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run/footer.view.tsx b/packages/opencode/src/cli/cmd/run/footer.view.tsx index 21aab66030..10587d4e80 100644 --- a/packages/opencode/src/cli/cmd/run/footer.view.tsx +++ b/packages/opencode/src/cli/cmd/run/footer.view.tsx @@ -47,6 +47,9 @@ type History = { type Area = { isDestroyed: boolean virtualLineCount: number + visualCursor: { + visualRow: number + } plainText: string cursorOffset: number setText(text: string): void @@ -152,6 +155,8 @@ export function RunFooterView(props: RunFooterViewProps) { const leaders = createMemo(() => Keybind.parse(props.keybinds.leader)) const cycles = createMemo(() => Keybind.parse(props.keybinds.variantCycle)) const interrupts = createMemo(() => Keybind.parse(props.keybinds.interrupt)) + const historyPrevious = createMemo(() => Keybind.parse(props.keybinds.historyPrevious)) + const historyNext = createMemo(() => Keybind.parse(props.keybinds.historyNext)) const variant = createMemo(() => printableBinding(props.keybinds.variantCycle, props.keybinds.leader)) const interrupt = createMemo(() => printableBinding(props.keybinds.interrupt, props.keybinds.leader)) const bindings = createMemo(() => textareaBindings(props.keybinds)) @@ -342,17 +347,30 @@ export function RunFooterView(props: RunFooterViewProps) { return } - if (event.ctrl || event.meta || event.shift || event.super || event.hyper) { + const key = toKeyInfo(event, false) + const previous = match(historyPrevious(), key) + const next = match(historyNext(), key) + + if (!previous && !next) { return } - if (event.name === "up") { - move(-1, event) + if (!area || area.isDestroyed) { return } - if (event.name === "down") { - move(1, event) + const dir = previous ? -1 : 1 + if ((dir === -1 && area.cursorOffset === 0) || (dir === 1 && area.cursorOffset === area.plainText.length)) { + move(dir, event) + return + } + + if (dir === -1 && area.visualCursor.visualRow === 0) { + area.cursorOffset = 0 + } + + if (dir === 1 && area.visualCursor.visualRow === area.virtualLineCount - 1) { + area.cursorOffset = area.plainText.length } } diff --git a/packages/opencode/src/cli/cmd/run/runtime.ts b/packages/opencode/src/cli/cmd/run/runtime.ts index da401894c8..3832caa8ff 100644 --- a/packages/opencode/src/cli/cmd/run/runtime.ts +++ b/packages/opencode/src/cli/cmd/run/runtime.ts @@ -11,6 +11,8 @@ const DEFAULT_KEYBINDS: FooterKeybinds = { leader: "ctrl+x", variantCycle: "ctrl+t,t", interrupt: "escape", + historyPrevious: "up", + historyNext: "down", inputSubmit: "return", inputNewline: "shift+return,ctrl+return,alt+return,ctrl+j", } @@ -118,6 +120,8 @@ async function resolveFooterKeybinds(): Promise { const configuredLeader = config.keybinds?.leader?.trim() || DEFAULT_KEYBINDS.leader const configuredVariantCycle = config.keybinds?.variant_cycle?.trim() || "ctrl+t" const configuredInterrupt = config.keybinds?.session_interrupt?.trim() || DEFAULT_KEYBINDS.interrupt + const configuredHistoryPrevious = config.keybinds?.history_previous?.trim() || DEFAULT_KEYBINDS.historyPrevious + const configuredHistoryNext = config.keybinds?.history_next?.trim() || DEFAULT_KEYBINDS.historyNext const configuredSubmit = config.keybinds?.input_submit?.trim() || DEFAULT_KEYBINDS.inputSubmit const configuredNewline = config.keybinds?.input_newline?.trim() || DEFAULT_KEYBINDS.inputNewline @@ -134,6 +138,8 @@ async function resolveFooterKeybinds(): Promise { leader: configuredLeader, variantCycle: variantBindings.join(","), interrupt: configuredInterrupt, + historyPrevious: configuredHistoryPrevious, + historyNext: configuredHistoryNext, inputSubmit: configuredSubmit, inputNewline: configuredNewline, } diff --git a/packages/opencode/src/cli/cmd/run/types.ts b/packages/opencode/src/cli/cmd/run/types.ts index 047a9e179b..2cdc071522 100644 --- a/packages/opencode/src/cli/cmd/run/types.ts +++ b/packages/opencode/src/cli/cmd/run/types.ts @@ -41,6 +41,8 @@ export type FooterKeybinds = { leader: string variantCycle: string interrupt: string + historyPrevious: string + historyNext: string inputSubmit: string inputNewline: string } diff --git a/packages/opencode/test/cli/run/footer-view.test.tsx b/packages/opencode/test/cli/run/footer-view.test.tsx index 92688536ee..72cadb3188 100644 --- a/packages/opencode/test/cli/run/footer-view.test.tsx +++ b/packages/opencode/test/cli/run/footer-view.test.tsx @@ -67,6 +67,8 @@ describe("run footer view", () => { leader: "ctrl+x", variantCycle: "ctrl+t,t", interrupt: "escape", + historyPrevious: "up", + historyNext: "down", inputSubmit: "return", inputNewline: "shift+return,ctrl+return,alt+return,ctrl+j", }} @@ -120,6 +122,8 @@ describe("run footer view", () => { leader: "ctrl+x", variantCycle: "ctrl+t,t", interrupt: "escape", + historyPrevious: "up", + historyNext: "down", inputSubmit: "return", inputNewline: "shift+return,ctrl+return,alt+return,ctrl+j", }} @@ -165,12 +169,14 @@ describe("run footer view", () => { expect(area.plainText).toBe("one") expect(area.cursorOffset).toBe(0) - area.cursorOffset = area.plainText.length + setup.mockInput.pressArrow("down") + expect(area.plainText).toBe("one") + expect(area.cursorOffset).toBe(area.plainText.length) + setup.mockInput.pressArrow("down") expect(area.plainText).toBe("two") expect(area.cursorOffset).toBe(area.plainText.length) - area.cursorOffset = area.plainText.length setup.mockInput.pressArrow("down") expect(area.plainText).toBe("") expect(area.cursorOffset).toBe(0) @@ -239,6 +245,8 @@ describe("run footer view", () => { leader: "ctrl+x", variantCycle: "ctrl+t,t", interrupt: "escape", + historyPrevious: "up", + historyNext: "down", inputSubmit: "return", inputNewline: "shift+return,ctrl+return,alt+return,ctrl+j", }} @@ -290,6 +298,8 @@ describe("run footer view", () => { leader: "ctrl+x", variantCycle: "ctrl+t,t", interrupt: "escape", + historyPrevious: "up", + historyNext: "down", inputSubmit: "return", inputNewline: "shift+return,ctrl+return,alt+return,ctrl+j", }} @@ -344,6 +354,8 @@ describe("run footer view", () => { leader: "ctrl+x", variantCycle: "ctrl+t,t", interrupt: "escape", + historyPrevious: "up", + historyNext: "down", inputSubmit: "return", inputNewline: "shift+return,ctrl+return,alt+return,ctrl+j", }} @@ -388,6 +400,8 @@ describe("run footer view", () => { leader: "ctrl+x", variantCycle: "ctrl+t,t", interrupt: "escape", + historyPrevious: "up", + historyNext: "down", inputSubmit: "return", inputNewline: "shift+return,ctrl+return,alt+return,ctrl+j", }} @@ -430,6 +444,8 @@ describe("run footer view", () => { leader: "ctrl+x", variantCycle: "ctrl+t,t", interrupt: "escape", + historyPrevious: "up", + historyNext: "down", inputSubmit: "return", inputNewline: "shift+return,ctrl+return,alt+return,ctrl+j", }}