fix(tui): keep Home/End for the editor in fullscreen mode

Fullscreen transcript top/bottom moved to Ctrl+Home/Ctrl+End.

closes #10314
This commit is contained in:
Armin Ronacher 2026-10-05 09:53:25 +02:00
parent a37306d437
commit 6100fe5a83
6 changed files with 38 additions and 21 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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", () => {

View file

@ -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();
});