diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 9edab99f9..46a0b178c 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -14,6 +14,7 @@ - Codemode `image()` now also saves each image to a temp file and names the path in the result, so later turns can copy or move generated images ([#10310](https://github.com/earendil-works/pi/issues/10310)) - Output files (full text of truncated tool output, binary MCP resources, codemode images) are now readable only by the user +- `Home`/`End` now always move the editor cursor to the line start/end; fullscreen transcript top/bottom moved to `Ctrl+Home`/`Ctrl+End`, which no longer move the editor cursor ([#10314](https://github.com/earendil-works/pi/issues/10314)) ### Fixed diff --git a/packages/coding-agent/docs/keybindings.md b/packages/coding-agent/docs/keybindings.md index ed001040d..cfc5e4320 100644 --- a/packages/coding-agent/docs/keybindings.md +++ b/packages/coding-agent/docs/keybindings.md @@ -57,8 +57,8 @@ Examples: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `super+k`, `ctrl+sup | `tui.editor.cursorRight` | `right`, `ctrl+f` | Move cursor right | | `tui.editor.cursorWordLeft` | `alt+left`, `ctrl+left`, `alt+b` | Move cursor word left | | `tui.editor.cursorWordRight` | `alt+right`, `ctrl+right`, `alt+f` | Move cursor word right | -| `tui.editor.cursorLineStart` | `home`, `ctrl+home`, `ctrl+a` | Move to line start | -| `tui.editor.cursorLineEnd` | `end`, `ctrl+end`, `ctrl+e` | Move to line end | +| `tui.editor.cursorLineStart` | `home`, `ctrl+a` | Move to line start | +| `tui.editor.cursorLineEnd` | `end`, `ctrl+e` | Move to line end | | `tui.editor.jumpForward` | `ctrl+]` | Jump forward to character | | `tui.editor.jumpBackward` | `ctrl+alt+]` | Jump backward to character | | `tui.editor.pageUp` | `pageUp`, `ctrl+pageUp` | Scroll up by page | @@ -113,8 +113,8 @@ In fullscreen mode, these actions control the transcript and take precedence ove | `tui.altScreen.searchNext` | `enter`, `ctrl+g` | Select the next search match while searching | | `tui.altScreen.searchPrevious` | `shift+enter`, `ctrl+shift+g` | Select the previous search match while searching | | `tui.altScreen.searchClose` | `escape` | Close transcript search | -| `tui.altScreen.top` | `home` | Scroll to the beginning of the transcript | -| `tui.altScreen.bottom` | `end` | Scroll to the transcript end and follow new output | +| `tui.altScreen.top` | `ctrl+home` | Scroll to the beginning of the transcript | +| `tui.altScreen.bottom` | `ctrl+end` | Scroll to the transcript end and follow new output | ### Application diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index f5f6c22f4..85cbdae9a 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- `Home`/`End` now always move the editor cursor to the line start/end; fullscreen transcript top/bottom moved to `Ctrl+Home`/`Ctrl+End`, which no longer move the editor cursor ([#10314](https://github.com/earendil-works/pi/issues/10314)) + ## [1.0.2] - 2026-10-04 ## [1.0.1] - 2026-10-03 diff --git a/packages/tui/src/keybindings.ts b/packages/tui/src/keybindings.ts index d6afb5ec3..ed6e95171 100644 --- a/packages/tui/src/keybindings.ts +++ b/packages/tui/src/keybindings.ts @@ -96,11 +96,11 @@ export const TUI_KEYBINDINGS = { description: "Move cursor word right", }, "tui.editor.cursorLineStart": { - defaultKeys: ["home", "ctrl+home", "ctrl+a"], + defaultKeys: ["home", "ctrl+a"], description: "Move to line start", }, "tui.editor.cursorLineEnd": { - defaultKeys: ["end", "ctrl+end", "ctrl+e"], + defaultKeys: ["end", "ctrl+e"], description: "Move to line end", }, "tui.editor.jumpForward": { @@ -205,8 +205,8 @@ export const TUI_KEYBINDINGS = { defaultKeys: "escape", description: "Close transcript search", }, - "tui.altScreen.top": { defaultKeys: "home", description: "Scroll viewport to top" }, - "tui.altScreen.bottom": { defaultKeys: "end", description: "Scroll viewport to bottom" }, + "tui.altScreen.top": { defaultKeys: "ctrl+home", description: "Scroll viewport to top" }, + "tui.altScreen.bottom": { defaultKeys: "ctrl+end", description: "Scroll viewport to bottom" }, } as const satisfies KeybindingDefinitions; export interface KeybindingConflict { diff --git a/packages/tui/test/keybindings.test.ts b/packages/tui/test/keybindings.test.ts index b0c827dde..a68644c54 100644 --- a/packages/tui/test/keybindings.test.ts +++ b/packages/tui/test/keybindings.test.ts @@ -14,8 +14,8 @@ describe("KeybindingsManager", () => { it("binds modified and unmodified editor viewport navigation", () => { const keybindings = new KeybindingsManager(TUI_KEYBINDINGS); - assert.deepStrictEqual(keybindings.getKeys("tui.editor.cursorLineStart"), ["home", "ctrl+home", "ctrl+a"]); - assert.deepStrictEqual(keybindings.getKeys("tui.editor.cursorLineEnd"), ["end", "ctrl+end", "ctrl+e"]); + assert.deepStrictEqual(keybindings.getKeys("tui.editor.cursorLineStart"), ["home", "ctrl+a"]); + assert.deepStrictEqual(keybindings.getKeys("tui.editor.cursorLineEnd"), ["end", "ctrl+e"]); assert.deepStrictEqual(keybindings.getKeys("tui.editor.pageUp"), ["pageUp", "ctrl+pageUp"]); assert.deepStrictEqual(keybindings.getKeys("tui.editor.pageDown"), ["pageDown", "ctrl+pageDown"]); }); @@ -42,8 +42,8 @@ describe("KeybindingsManager", () => { assert.deepStrictEqual(keybindings.getKeys("tui.altScreen.searchNext"), ["enter", "ctrl+g"]); assert.deepStrictEqual(keybindings.getKeys("tui.altScreen.searchPrevious"), ["shift+enter", "ctrl+shift+g"]); assert.deepStrictEqual(keybindings.getKeys("tui.altScreen.searchClose"), ["escape"]); - assert.deepStrictEqual(keybindings.getKeys("tui.altScreen.top"), ["home"]); - assert.deepStrictEqual(keybindings.getKeys("tui.altScreen.bottom"), ["end"]); + assert.deepStrictEqual(keybindings.getKeys("tui.altScreen.top"), ["ctrl+home"]); + assert.deepStrictEqual(keybindings.getKeys("tui.altScreen.bottom"), ["ctrl+end"]); }); it("does not evict selector confirm when input submit is rebound", () => { diff --git a/packages/tui/test/tui-alt-screen.test.ts b/packages/tui/test/tui-alt-screen.test.ts index 2a2ea5490..7b4ffffc0 100644 --- a/packages/tui/test/tui-alt-screen.test.ts +++ b/packages/tui/test/tui-alt-screen.test.ts @@ -576,14 +576,14 @@ describe("TuiAltScreen", () => { ["line 5", "line 6", "line 7", "line 8", "line 9", "line 10", "line 11", "line 12"], ); - terminal.sendInput("\x1bOH"); + terminal.sendInput("\x1b[7^"); await terminal.waitForRender(); assert.deepStrictEqual( terminal.getViewport().map((line) => line.trimEnd()), ["line 1", "line 2", "line 3", "line 4", "line 5", "line 6", "line 7", "line 8"], ); - terminal.sendInput("\x1bOF"); + terminal.sendInput("\x1b[8^"); await terminal.waitForRender(); assert.deepStrictEqual( terminal.getViewport().map((line) => line.trimEnd()), @@ -898,7 +898,7 @@ describe("TuiAltScreen", () => { } }); - it("routes Ctrl-modified viewport navigation to the focused component", async () => { + it("routes Home and End to the focused component and Ctrl+Home/End to the transcript", async () => { const terminal = new VirtualTerminal(20, 6); const tui = new TuiAltScreen(terminal); const transcript = new ScrollView( @@ -922,22 +922,34 @@ describe("TuiAltScreen", () => { tui.start(); await terminal.waitForRender(); - terminal.sendInput("\x1bOH"); + const bottom = transcript.scrollTop; + assert.ok(bottom > 0); + + // #10314: unmodified Home/End belong to the editor in every UI mode. + const editorKeys = ["\x1bOH", "\x1b[F", "\x1b[57423u", "\x1b[5;5~", "\x1b[6;5~"]; + for (const input of editorKeys) terminal.sendInput(input); + await terminal.waitForRender(); + assert.strictEqual(transcript.scrollTop, bottom); + assert.deepStrictEqual(editorInputs, editorKeys); + + terminal.sendInput("\x1b[1;5H"); await terminal.waitForRender(); assert.strictEqual(transcript.scrollTop, 0); - assert.deepStrictEqual(editorInputs, []); - const modifiedInputs = ["\x1b[1;5H", "\x1b[1;5F", "\x1b[5;5~", "\x1b[6;5~", "\x1b[57423;5u"]; - for (const input of modifiedInputs) terminal.sendInput(input); + terminal.sendInput("\x1b[1;5F"); + await terminal.waitForRender(); + assert.strictEqual(transcript.scrollTop, bottom); + assert.strictEqual(transcript.isFollowingEnd, true); + + terminal.sendInput("\x1b[57423;5u"); terminal.sendInput("\x1b[57423;5:3u"); await terminal.waitForRender(); assert.strictEqual(transcript.scrollTop, 0); - assert.deepStrictEqual(editorInputs, modifiedInputs); terminal.sendInput("\x1b[6~"); await terminal.waitForRender(); assert.strictEqual(transcript.scrollTop, 1); - assert.deepStrictEqual(editorInputs, modifiedInputs); + assert.deepStrictEqual(editorInputs, editorKeys); tui.stop(); });