From 3b5613469470d2369784a0b49394c74b9c5355dd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 22 Jun 2026 13:57:02 +0200 Subject: [PATCH] fix(tui): bind ctrl+j as newline by default --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/docs/keybindings.md | 2 +- packages/coding-agent/docs/terminal-setup.md | 8 +------- packages/tui/CHANGELOG.md | 4 ++++ packages/tui/src/keybindings.ts | 2 +- packages/tui/test/keybindings.test.ts | 8 ++++++++ 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 989572ba1..69e2d8cb3 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changed +- Added `Ctrl+J` as a default newline keybinding alongside `Shift+Enter`. - Renamed the displayed `zai` provider label to ZAI Coding Plan (Global) for clarity ([#5965](https://github.com/earendil-works/pi/issues/5965)). ## [0.79.10] - 2026-06-22 diff --git a/packages/coding-agent/docs/keybindings.md b/packages/coding-agent/docs/keybindings.md index 333b94e39..a15a25a02 100644 --- a/packages/coding-agent/docs/keybindings.md +++ b/packages/coding-agent/docs/keybindings.md @@ -54,7 +54,7 @@ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `ctrl+1 | Keybinding id | Default | Description | |--------|---------|-------------| -| `tui.input.newLine` | `shift+enter` | Insert new line | +| `tui.input.newLine` | `shift+enter`, `ctrl+j` | Insert new line | | `tui.input.submit` | `enter` | Submit input | | `tui.input.tab` | `tab` | Tab / autocomplete | diff --git a/packages/coding-agent/docs/terminal-setup.md b/packages/coding-agent/docs/terminal-setup.md index ff1638b5e..d2a8fb92c 100644 --- a/packages/coding-agent/docs/terminal-setup.md +++ b/packages/coding-agent/docs/terminal-setup.md @@ -30,13 +30,7 @@ That mapping sends a raw linefeed byte. Inside pi, that is indistinguishable fro If Claude Code 2.x or newer is the only reason you added that mapping, you can remove it, unless you want to use Claude Code in tmux, where it still requires that Ghostty mapping. -If you want `Shift+Enter` to keep working in tmux via that remap, add `ctrl+j` to your pi `newLine` keybinding in `~/.pi/agent/keybindings.json`: - -```json -{ - "newLine": ["shift+enter", "ctrl+j"] -} -``` +Pi binds `Ctrl+J` as a default newline alias, so `Shift+Enter` keeps working in tmux via that remap without extra pi configuration. ## WezTerm diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 0a34ae38c..a3cf70ef7 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Added `Ctrl+J` as a default newline keybinding alongside `Shift+Enter`. + ## [0.79.10] - 2026-06-22 ## [0.79.9] - 2026-06-20 diff --git a/packages/tui/src/keybindings.ts b/packages/tui/src/keybindings.ts index 789124ada..eaf0838b4 100644 --- a/packages/tui/src/keybindings.ts +++ b/packages/tui/src/keybindings.ts @@ -115,7 +115,7 @@ export const TUI_KEYBINDINGS = { "tui.editor.yank": { defaultKeys: "ctrl+y", description: "Yank" }, "tui.editor.yankPop": { defaultKeys: "alt+y", description: "Yank pop" }, "tui.editor.undo": { defaultKeys: "ctrl+-", description: "Undo" }, - "tui.input.newLine": { defaultKeys: "shift+enter", description: "Insert newline" }, + "tui.input.newLine": { defaultKeys: ["shift+enter", "ctrl+j"], description: "Insert newline" }, "tui.input.submit": { defaultKeys: "enter", description: "Submit input" }, "tui.input.tab": { defaultKeys: "tab", description: "Tab / autocomplete" }, "tui.input.copy": { defaultKeys: "ctrl+c", description: "Copy selection" }, diff --git a/packages/tui/test/keybindings.test.ts b/packages/tui/test/keybindings.test.ts index 37c674422..8bd7f4387 100644 --- a/packages/tui/test/keybindings.test.ts +++ b/packages/tui/test/keybindings.test.ts @@ -3,6 +3,14 @@ import { describe, it } from "node:test"; import { KeybindingsManager, TUI_KEYBINDINGS } from "../src/keybindings.ts"; describe("KeybindingsManager", () => { + it("binds Ctrl+J as a default newline alias", () => { + const keybindings = new KeybindingsManager(TUI_KEYBINDINGS); + + assert.deepStrictEqual(keybindings.getKeys("tui.input.newLine"), ["shift+enter", "ctrl+j"]); + assert.strictEqual(keybindings.matches("\n", "tui.input.newLine"), true); + assert.strictEqual(keybindings.matches("\x1b[106;5u", "tui.input.newLine"), true); + }); + it("does not evict selector confirm when input submit is rebound", () => { const keybindings = new KeybindingsManager(TUI_KEYBINDINGS, { "tui.input.submit": ["enter", "ctrl+enter"],