Compare commits

..

1 Commits

Author SHA1 Message Date
Kit Langton 9144e6d01a fix(tui): preserve legacy option enter newlines 2026-08-07 21:16:06 +00:00
6 changed files with 80 additions and 14 deletions
+10
View File
@@ -156,6 +156,7 @@ const appBindingCommands = [
"help.show",
"docs.open",
"diff.open",
"app.debug",
"app.console",
"terminal.suspend",
"terminal.title.toggle",
@@ -957,6 +958,15 @@ function App(props: { pair?: DialogPairCredentials }) {
run: () => exit(),
category: "System",
},
{
name: "app.debug",
title: "Toggle debug panel",
category: "System",
run: () => {
renderer.toggleDebugOverlay()
dialog.clear()
},
},
{
name: "app.console",
title: "Toggle console",
@@ -41,7 +41,6 @@ export function DevToolsBar() {
const [dumping, setDumping] = createSignal(false)
const [dumpPath, setDumpPath] = createSignal<string>()
const [dumpError, setDumpError] = createSignal<string>()
const [debugOverlay, setDebugOverlay] = createSignal(Boolean(renderer.debugOverlay.enabled))
const [frontendSamples, setFrontendSamples] = createSignal<readonly ProcessSample[]>([])
let focus: Renderable | null
const connected = createMemo(() => client.connection.status() === "connected")
@@ -392,15 +391,6 @@ export function DevToolsBar() {
>
{verboseTurnTokens() ? "[x]" : "[ ]"} Turn token usage (verbose)
</Action>
<Action
onClick={() => {
renderer.toggleDebugOverlay()
setDebugOverlay(Boolean(renderer.debugOverlay.enabled))
}}
hoverBackground
>
{debugOverlay() ? "[x]" : "[ ]"} Renderer debug overlay
</Action>
</box>
<For each={groups()}>
{(group) => (
+5 -2
View File
@@ -18,6 +18,7 @@ const BindingObject = Schema.StructWithRest(
Schema.Struct({
key: Schema.Union([Schema.String, KeyStroke]),
event: Schema.optional(Schema.Literals(["press", "release"])),
source: Schema.optional(Schema.Literals(["raw", "kitty"])),
preventDefault: Schema.optional(Schema.Boolean),
fallthrough: Schema.optional(Schema.Boolean),
}),
@@ -46,6 +47,7 @@ export const Definitions = {
leader: keybind(LeaderDefault, "Leader key for keybind combinations"),
app_exit: keybind("ctrl+c,ctrl+d,<leader>q", "Exit the application"),
app_debug: keybind("none", "Toggle debug panel"),
app_console: keybind("none", "Toggle console"),
app_heap_snapshot: keybind("none", "Write heap snapshot"),
app_toggle_animations: keybind("none", "Toggle animations"),
@@ -161,7 +163,7 @@ export const Definitions = {
display_thinking: keybind("none", "Toggle thinking blocks visibility"),
prompt_submit: keybind("none", "Submit prompt"),
prompt_queue: keybind("alt+return", "Queue prompt"),
prompt_queue: keybind({ key: "alt+return", source: "kitty" }, "Queue prompt"),
prompt_editor_context_clear: keybind("none", "Clear editor context"),
prompt_skills: keybind("none", "Open skill selector"),
prompt_stash: keybind("none", "Stash prompt"),
@@ -171,7 +173,7 @@ export const Definitions = {
input_clear: keybind("ctrl+c", "Clear input field"),
input_paste: keybind({ key: "ctrl+v", preventDefault: false }, "Paste from clipboard"),
input_submit: keybind("return", "Submit input"),
input_newline: keybind("shift+return,ctrl+return,ctrl+j", "Insert newline in input"),
input_newline: keybind("shift+return,ctrl+return,alt+return,ctrl+j", "Insert newline in input"),
input_move_left: keybind("left,ctrl+b", "Move cursor left in input"),
input_move_right: keybind("right,ctrl+f", "Move cursor right in input"),
input_move_up: keybind("up", "Move cursor up in input"),
@@ -250,6 +252,7 @@ export const Descriptions = Object.fromEntries(
) as Record<KeybindName, string>
export const CommandMap = {
app_exit: "app.exit",
app_debug: "app.debug",
app_console: "app.console",
app_heap_snapshot: "app.heap_snapshot",
app_toggle_animations: "app.toggle.animations",
+26
View File
@@ -66,6 +66,32 @@ function Provider(props: ParentProps<{ config?: KeymapConfig }>) {
}
}
const dispose = [
keymap.registerBindingFields({
source: (source, context) => {
if (source !== "raw" && source !== "kitty") throw new Error(`Invalid key source: ${String(source)}`)
context.attr("source", source)
},
}),
keymap.appendBindingTransformer((binding, context) => {
const source = binding.source
if (source !== "raw" && source !== "kitty") return
const command = binding.cmd
if (!command) return
context.add({
...binding,
cmd: (commandContext) => {
if (commandContext.event.source !== source) return false
if (typeof command === "function") return command(commandContext)
return commandContext.keymap.runCommand(command, {
event: commandContext.event,
focused: commandContext.focused,
target: commandContext.target,
payload: commandContext.payload,
})
},
})
context.skipOriginal()
}),
registerCommaBindings(keymap),
keymap.appendBindingExpander((context) => {
const key = Object.entries({ enter: "return", esc: "escape", pgdown: "pagedown", pgup: "pageup" }).reduce(
+37
View File
@@ -1,5 +1,6 @@
/** @jsxImportSource @opentui/solid */
import { testRender } from "@opentui/solid"
import type { TextareaRenderable } from "@opentui/core"
import { expect, test } from "bun:test"
import { ConfigProvider } from "../src/config"
import { Keymap } from "../src/context/keymap"
@@ -139,3 +140,39 @@ test("global commands stay reachable when the mode changes", async () => {
app.renderer.destroy()
}
})
test("queues explicit option enter and keeps raw option enter as newline", async () => {
async function exercise(kittyKeyboard: boolean) {
let area: TextareaRenderable | undefined
let queued = 0
function Harness() {
Keymap.createLayer(() => ({
priority: 1,
commands: [{ id: "prompt.queue", run: () => void queued++ }],
}))
return <textarea ref={(value) => (area = value)} focused />
}
const app = await testRender(
() => (
<ConfigProvider config={createTuiResolvedConfig()}>
<Keymap.Provider>
<Harness />
</Keymap.Provider>
</ConfigProvider>
),
{ kittyKeyboard },
)
try {
app.mockInput.pressEnter({ meta: true })
await app.renderOnce()
return { queued, text: area?.plainText }
} finally {
app.renderer.destroy()
}
}
expect(await exercise(true)).toEqual({ queued: 1, text: "" })
expect(await exercise(false)).toEqual({ queued: 0, text: "\n" })
})
+2 -2
View File
@@ -82,8 +82,8 @@ describe("run runtime boot", () => {
expect(result.keybinds.get("prompt.history.next")?.[0]?.key).toBe("down")
expect(result.keybinds.get("prompt.clear")?.[0]?.key).toBe("ctrl+c")
expect(result.keybinds.get("input.submit")?.[0]?.key).toBe("return")
expect(result.keybinds.get("input.newline")?.[0]?.key).toBe("shift+return,ctrl+return,ctrl+j")
expect(result.keybinds.get("prompt.queue")?.[0]?.key).toBe("alt+return")
expect(result.keybinds.get("input.newline")?.[0]?.key).toBe("shift+return,ctrl+return,alt+return,ctrl+j")
expect(result.keybinds.get("prompt.queue")?.[0]).toMatchObject({ key: "alt+return", source: "kitty" })
})
test("preserves disabled leader from resolved tui config", async () => {