diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index d643c0704f..93a5da037f 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -761,6 +761,7 @@ function App(props: { onSnapshot?: () => Promise }) { keybind: "terminal_suspend", category: "System", hidden: true, + enabled: tuiConfig.keybinds?.terminal_suspend !== "none", onSelect: () => { process.once("SIGCONT", () => { renderer.resume() diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx index 08e429617f..a87e4ed2b7 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx @@ -148,5 +148,7 @@ const TIPS = [ "Use {highlight}/review{/highlight} to review uncommitted changes, branches, or PRs", "Run {highlight}/help{/highlight} or {highlight}Ctrl+X H{/highlight} to show the help dialog", "Use {highlight}/rename{/highlight} to rename the current session", - "Press {highlight}Ctrl+Z{/highlight} to suspend the terminal and return to your shell", + ...(process.platform === "win32" + ? ["Press {highlight}Ctrl+Z{/highlight} to undo changes in your prompt"] + : ["Press {highlight}Ctrl+Z{/highlight} to suspend the terminal and return to your shell"]), ] diff --git a/packages/opencode/src/config/tui.ts b/packages/opencode/src/config/tui.ts index adfb3c7810..fa2022482d 100644 --- a/packages/opencode/src/config/tui.ts +++ b/packages/opencode/src/config/tui.ts @@ -111,7 +111,15 @@ export namespace TuiConfig { } } - acc.result.keybinds = Config.Keybinds.parse(acc.result.keybinds ?? {}) + const keybinds = { ...(acc.result.keybinds ?? {}) } + if (process.platform === "win32") { + // Native Windows terminals do not support POSIX suspend, so prefer prompt undo. + keybinds.terminal_suspend = "none" + keybinds.input_undo ??= unique(["ctrl+z", ...Config.Keybinds.shape.input_undo.parse(undefined).split(",")]).join( + ",", + ) + } + acc.result.keybinds = Config.Keybinds.parse(keybinds) const deps: Promise[] = [] if (acc.result.plugin?.length) { diff --git a/packages/opencode/test/config/tui.test.ts b/packages/opencode/test/config/tui.test.ts index a8d98b66cd..b761d59ea4 100644 --- a/packages/opencode/test/config/tui.test.ts +++ b/packages/opencode/test/config/tui.test.ts @@ -9,6 +9,7 @@ import { Global } from "../../src/global" import { Filesystem } from "../../src/util/filesystem" const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR! +const wintest = process.platform === "win32" ? test : test.skip beforeEach(async () => { await Config.invalidate(true) @@ -441,6 +442,53 @@ test("merges keybind overrides across precedence layers", async () => { }) }) +wintest("defaults Ctrl+Z to input undo on Windows", async () => { + await using tmp = await tmpdir() + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await TuiConfig.get() + expect(config.keybinds?.terminal_suspend).toBe("none") + expect(config.keybinds?.input_undo).toBe("ctrl+z,ctrl+-,super+z") + }, + }) +}) + +wintest("keeps explicit input undo overrides on Windows", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write(path.join(dir, "tui.json"), JSON.stringify({ keybinds: { input_undo: "ctrl+y" } })) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await TuiConfig.get() + expect(config.keybinds?.terminal_suspend).toBe("none") + expect(config.keybinds?.input_undo).toBe("ctrl+y") + }, + }) +}) + +wintest("ignores terminal suspend bindings on Windows", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write(path.join(dir, "tui.json"), JSON.stringify({ keybinds: { terminal_suspend: "alt+z" } })) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await TuiConfig.get() + expect(config.keybinds?.terminal_suspend).toBe("none") + expect(config.keybinds?.input_undo).toBe("ctrl+z,ctrl+-,super+z") + }, + }) +}) + test("OPENCODE_TUI_CONFIG provides settings when no project config exists", async () => { await using tmp = await tmpdir({ init: async (dir) => {