summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 09:43:37 -0300
committerGabriel Schneider <[email protected]>2026-10-01 09:47:18 -0300
commit802de4f12f125abc261c58bdb5385f6be3ad469a (patch)
tree7dba19dcdfb2153e9dfa1d1e99b0697aabd49613
parent51df4b01f4ea8768b14048841f1c5fdd75128cd3 (diff)
downloadpardes-802de4f12f125abc261c58bdb5385f6be3ad469a.tar.gz
pardes-802de4f12f125abc261c58bdb5385f6be3ad469a.zip
Shift-Esc always backs out: in every mode and pane it goes to the previous pane, as Esc does from normal, and a raw program never sees it; Ctrl-b alone toggles raw
Shift-Esc switched a terminal in normal or insert mode into raw, so the one key meant to get out of anything got a script or a person into a program instead. It is now handled before any mode: an open prompt is closed, insert mode left, a pending count dropped, and Last runs, the pane keeping its mode (a raw terminal stays raw). Ctrl-b is the only switch between raw and normal. The keymap's two chords become one, back_out; the cheatsheet row, the guide's mode table and the tutor say so, and the tty and builtins goldens are re-recorded. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--docs/typ/cheatsheet.typ2
-rw-r--r--docs/typ/guide.typ6
-rw-r--r--src/config.zig6
-rw-r--r--src/gui/gui.zig5
-rw-r--r--src/normal.zig5
-rw-r--r--src/pardes.zig53
-rw-r--r--src/tutor.txt8
-rw-r--r--test/snapshots/builtins.golden4
-rw-r--r--test/snapshots/tty.golden17
-rw-r--r--test/snapshots/tty.snap25
10 files changed, 78 insertions, 53 deletions
diff --git a/docs/typ/cheatsheet.typ b/docs/typ/cheatsheet.typ
index cec31428..0487611a 100644
--- a/docs/typ/cheatsheet.typ
+++ b/docs/typ/cheatsheet.typ
@@ -78,7 +78,7 @@ Helix-style: motions *select*, then an edit acts on the selection. Mode box: bla
#pairs(
[#key("Ctrl-w") #keys("h", "j", "k", "l")], [then a direction: focus a neighbour (not in insert or raw `$`)],
[#key("Esc")], [back to the previous pane (raw: only at a prompt with nothing typed; insert: to normal)],
- [#key("Shift-Esc")], [As Esc, but always back to the previous pane from raw `$` or a PDF, and into raw from a terminal's normal or insert mode.],
+ [#key("Shift-Esc")], [back to the previous pane from any mode or pane, as Esc from normal; a raw program never sees it],
[#keys("Ctrl-o", "Ctrl-i")], [jump history back, forward],
[#key("Alt-n")], [new terminal below],
[#key("Alt-c")], [move the pane to a new column],
diff --git a/docs/typ/guide.typ b/docs/typ/guide.typ
index 8bab9e56..6d140da7 100644
--- a/docs/typ/guide.typ
+++ b/docs/typ/guide.typ
@@ -27,13 +27,13 @@ copy from. #word("Mode") in the tag steps raw, normal, insert. Esc and
Shift-Esc depend on the mode:
#pairs(
- [normal], [#key("Esc"): back to the previous pane (#word("Last")). #key("Shift-Esc"): the same, except in a terminal, where it switches to raw.],
- [insert], [#key("Esc"): back to normal. #key("Shift-Esc"): the same, except in a terminal, where it switches to raw.],
+ [normal], [#key("Esc"): back to the previous pane (#word("Last")). #key("Shift-Esc"): the same.],
+ [insert], [#key("Esc"): back to normal. #key("Shift-Esc"): out of insert and back to the previous pane.],
[raw `$`], [#key("Esc"): to the program, except at a shell prompt with nothing typed on it, where it goes back to the previous pane. #key("Shift-Esc"): always back to the previous pane. Either way the terminal stays `$`.],
[a PDF], [#key("Esc"): clears the selection and the search highlights. #key("Shift-Esc"): back to the previous pane.],
)
-In short, #key("Shift-Esc") is as Esc, but always back to the previous pane from raw `$` or a PDF, and into raw from a terminal's normal or insert mode. "A shell prompt with nothing
+In short, #key("Shift-Esc") is the Esc nothing swallows: in every mode and pane it goes back to the previous pane, as Esc does from normal, and a raw program never sees it. #key("Ctrl-b") alone switches a terminal between raw and normal. "A shell prompt with nothing
typed on it" is one pardes knows from the prompt marks it injects into bash
and fish (and, for other shells, from no program holding the terminal).
"The previous pane" is the last other pane on the jump list; with none, the
diff --git a/src/config.zig b/src/config.zig
index cc46adfc..7559d481 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -249,10 +249,12 @@ pub const jump_keys = [_]struct { chord: Chord, cmd: Builtin }{
.{ .chord = .{ .cp = 'i', .ctrl = true }, .cmd = .Forward },
};
-pub const leave_pane: []const Chord = &.{.{ .cp = Key.escape, .shift = true }};
+/// Shift-Esc: the Esc nothing swallows. In every mode and pane it does what
+/// Esc does from normal mode, back to the previous pane (Last); a raw
+/// terminal's program never sees it. Ctrl-b alone toggles raw.
+pub const back_out: []const Chord = &.{.{ .cp = Key.escape, .shift = true }};
pub const tty_toggle_default: u21 = 'b';
-pub const tty_toggle_alt: []const Chord = &.{.{ .cp = Key.escape, .shift = true }};
// OSC 133 prompt cells are hidden in normal mode; input columns stay intact.
pub const tty_blank: enum { prompt, prompt_and_input } = .prompt;
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 739e5a69..e8dc99b9 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -3790,10 +3790,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
.cap_n => .{ .cp = 'N', .text = "N" },
.enter => .{ .cp = pardes.Key.enter },
.tab => .{ .cp = pardes.Key.tab },
- .tty_toggle => if (in.core) |core|
- .{ .cp = core.opts.tty_toggle, .ctrl = true }
- else
- .{ .cp = config.tty_toggle_alt[0].cp, .shift = true },
+ .tty_toggle => .{ .cp = if (in.core) |core| core.opts.tty_toggle else config.tty_toggle_default, .ctrl = true },
},
}),
.rumble => if (g.gamepad) |pad| {
diff --git a/src/normal.zig b/src/normal.zig
index 17ebaac6..93487e88 100644
--- a/src/normal.zig
+++ b/src/normal.zig
@@ -815,11 +815,6 @@ fn executeNormalAction(p: *Pardes, t: *Text, semantic: modal.Normal.Action) void
pub fn handlePdfNormal(p: *Pardes, pane: *Pane, key: Key) void {
if (comptime !pdf_enabled) return;
if (pane.pdf == null) return;
- if (hit(key, config.leave_pane)) {
- pane.body.normal.clear();
- pane.body.select = false;
- return exec.runBuiltin(p, .Last, p.active, "", null);
- }
if (pane.body.normal.prefix == .none and isPrefix(key, 'f')) {
pane.body.normal.clear();
return exec.runBuiltin(p, .PdfSections, p.active, "", null);
diff --git a/src/pardes.zig b/src/pardes.zig
index dbb37084..0690b7eb 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -6122,6 +6122,24 @@ pub const Pardes = struct {
}
const pane = p.panes[p.active] orelse return;
p.noteTyped();
+ // Shift-Esc backs out of whatever the pane is in -- a prompt, insert
+ // mode, a pending count, a raw program -- to the previous pane, as
+ // Esc does from normal mode. Nothing under it sees the key.
+ if (hit(key, config.back_out)) {
+ p.leader_on = false;
+ p.ctrl_w_pending = false;
+ if (pane.prompt != .none) {
+ pane.closePrompt();
+ pane.focus = .body;
+ }
+ for ([_]*Text{ &pane.body, &pane.tag }) |t| {
+ if (t.mode == .insert) edit.exitInsert(p, t);
+ t.normal.clear();
+ t.select = false;
+ }
+ tagline.exitHeader(p);
+ return exec.runBuiltin(p, .Last, p.active, "", null);
+ }
// Alt-Down and Alt-Up in a pane's tag expand and collapse it, in
// either mode: acme's Tagdown and Tagup (plan9port text.c:776).
if (pane.focus == .tag and key.alt and !key.ctrl and (key.cp == Key.down or key.cp == Key.up) and tagline.focusedHeader(p) == null) {
@@ -6135,10 +6153,8 @@ pub const Pardes = struct {
p.ctrl_w_pending = false;
if (key.ctrl and !key.alt and key.cp == p.opts.tty_toggle)
return p.toggleTty(p.active);
- // Shift-Escape is the unconditional way out, even mid-application;
- // bare Escape only leaves at a shell prompt.
- if (hit(key, config.tty_toggle_alt))
- return exec.runBuiltin(p, .Last, p.active, "", null);
+ // Bare Escape leaves only at a shell prompt (Shift-Esc, above,
+ // always).
if (key.cp == Key.escape and !key.ctrl and !key.alt and !key.shift and exec.takesCommandLine(p, p.active))
return exec.runBuiltin(p, .Last, p.active, "", null);
panes.terminal.followOutput(pane); // typing snaps back to live output
@@ -6196,8 +6212,7 @@ pub const Pardes = struct {
}
return;
}
- const tty_alt = hit(key, config.tty_toggle_alt);
- const tty_toggle = (key.ctrl and key.cp == p.opts.tty_toggle) or tty_alt;
+ const tty_toggle = key.ctrl and key.cp == p.opts.tty_toggle;
if (pane.isTerminal() and tty_toggle) {
return p.toggleTty(p.active);
}
@@ -7601,7 +7616,7 @@ test "Esc back into a tty leaves its view at the prompt" {
p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = r.x + config.GUTTER + 1, .row = r.y + BOX_H } });
try std.testing.expectEqual(shell, p.active);
- p.update(.{ .key = .{ .cp = Key.escape, .shift = true } });
+ p.update(.{ .key = .{ .cp = 'b', .ctrl = true } });
try std.testing.expectEqual(Text.Mode.tty, sp.body.mode);
// tty mode follows output to the bottom, so a screenful and a half of it
@@ -8012,7 +8027,7 @@ test "tty output follows only from the bottom" {
p.update(.{ .key = .{ .cp = 'n', .alt = true } }); // a shell under the doc
const shell = p.active;
const sp = p.panes[shell].?;
- p.update(.{ .key = .{ .cp = Key.escape, .shift = true } });
+ p.update(.{ .key = .{ .cp = 'b', .ctrl = true } });
try std.testing.expectEqual(Text.Mode.tty, sp.body.mode);
for (0..60) |i| {
@@ -8122,6 +8137,28 @@ test "raw tty Escape only leaves at a shell prompt" {
try std.testing.expectEqual(Text.Mode.tty, pane.body.mode);
}
+test "Shift-Esc backs out to the previous pane in every mode, never into raw" {
+ if (platform == .web) return;
+ const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24, .file = "mise.toml" });
+ defer p.deinit();
+ p.update(.{ .key = .{ .cp = 'n', .alt = true } }); // a shell under the doc
+ const shell = p.active;
+ const sp = p.panes[shell].?;
+ // A terminal in normal mode: out, still normal, never raw.
+ if (sp.body.mode == .tty) p.update(.{ .key = .{ .cp = 'b', .ctrl = true } });
+ try std.testing.expectEqual(Text.Mode.normal, sp.body.mode);
+ p.update(.{ .key = .{ .cp = Key.escape, .shift = true } });
+ try std.testing.expectEqual(@as(usize, 0), p.active);
+ try std.testing.expectEqual(Text.Mode.normal, sp.body.mode);
+ // A text pane in insert mode: out of insert, and out of the pane.
+ const doc = p.panes[0].?;
+ p.update(.{ .key = .{ .cp = 'i', .text = "i" } });
+ try std.testing.expectEqual(Text.Mode.insert, doc.body.mode);
+ p.update(.{ .key = .{ .cp = Key.escape, .shift = true } });
+ try std.testing.expectEqual(Text.Mode.normal, doc.body.mode);
+ try std.testing.expect(p.active != 0);
+}
+
test "raw tty Ctrl-B switches to editor mode without sending child input" {
if (platform == .web) return;
const p = try Pardes.init(std.testing.allocator, .{ .file = "mise.toml" });
diff --git a/src/tutor.txt b/src/tutor.txt
index 37938574..bd79d5a4 100644
--- a/src/tutor.txt
+++ b/src/tutor.txt
@@ -43,9 +43,9 @@
Esc and Shift-Esc, by mode:
NORMAL Esc back to the previous pane (`Last`, SPC j j)
- Shift-Esc the same; in a terminal it switches to RAW
+ Shift-Esc the same
INSERT Esc back to NORMAL
- Shift-Esc the same; in a terminal it switches to RAW
+ Shift-Esc out of INSERT and back to the previous pane
RAW Esc goes to the program, except at an idle,
EMPTY shell prompt: back to the previous pane
Shift-Esc always back to the previous pane
@@ -447,8 +447,8 @@ typed
MODES blank NORMAL, ^ INSERT, $ RAW; each pane keeps its own
Esc: back to the previous pane (RAW: only at an empty
prompt; INSERT: back to NORMAL)
- Shift-Esc: out of RAW always; in a terminal's NORMAL,
- into RAW; Ctrl-b toggles RAW and NORMAL
+ Shift-Esc: back to the previous pane from any mode,
+ RAW included; Ctrl-b toggles RAW and NORMAL
MOUSE L select/focus M execute R look
1-2 cut 1-3 paste 1-2 then 1-3 copy
diff --git a/test/snapshots/builtins.golden b/test/snapshots/builtins.golden
index 27b43a3a..68fab516 100644
--- a/test/snapshots/builtins.golden
+++ b/test/snapshots/builtins.golden
@@ -73,9 +73,9 @@
|58: 43 Esc and Shift-Esc, by mode:
|59: 44
|60: 45 NORMAL Esc back to the previous pane (`Last`, SPC j j)
-|61: 46 Shift-Esc the same; in a terminal it switches to RAW
+|61: 46 Shift-Esc the same
|62: 47 INSERT Esc back to NORMAL
-|63: 48 Shift-Esc the same; in a terminal it switches to RAW
+|63: 48 Shift-Esc out of INSERT and back to the previous pane
|64: 49 RAW Esc goes to the program, except at an idle,
|65: 50 EMPTY shell prompt: back to the previous pane
|66: 51 Shift-Esc always back to the previous pane
diff --git a/test/snapshots/tty.golden b/test/snapshots/tty.golden
index 981a5c7f..b1ee7afd 100644
--- a/test/snapshots/tty.golden
+++ b/test/snapshots/tty.golden
@@ -29,15 +29,14 @@
|4: ls ls
|6: echo SNAP''OK
|8:
-== snap shift-esc-on grid=100x31 cursor=4,8
-|2: $ /tmp/pardes-snap/tty/cwd Tty+bash Save Mode Fil /tmp/pardes-snap/tty/cwd Tty+bash Save Mode Fil
-|4: $ ls ls
-|6: $ echo SNAP''OK
-|8: $
-== snap shift-esc-nodoc grid=100x31 cursor=4,21
-|4: $ ls Last ls
-== snap ctrl-b-off grid=100x31 cursor=4,21
-|4: $ ls ls
+== snap shift-esc-normal grid=100x31 cursor=4,21
+|4: ls Last ls
+== snap ctrl-b-on grid=100x31 cursor=4,21
+|4: ls ls
|17: $ /tmp/pardes-snap/tty/cwd Tty+bash Save Mode Fil
|19: $ ls
|21: $
+== snap ctrl-b-off grid=100x31 cursor=4,21
+|17: /tmp/pardes-snap/tty/cwd Tty+bash Save Mode Fil
+|19: ls
+|21:
diff --git a/test/snapshots/tty.snap b/test/snapshots/tty.snap
index 6ce0d5ba..48fffbd1 100644
--- a/test/snapshots/tty.snap
+++ b/test/snapshots/tty.snap
@@ -1,11 +1,8 @@
-# tty mode round-trip: Ctrl-b in, run a command, Ctrl-b out. Shift-Esc (CSI
-# 27;2u — needs a host that reports modifiers on Escape) also gets you IN; a
-# PLAIN Esc does not, it still goes to the shell.
-#
-# Getting OUT is Ctrl-b alone. Shift-Esc in tty is what Escape is in normal
-# mode instead — Last, the pane you were in before this one — leaving this pane
-# in tty so coming back lands in the program you left. The hop itself is
-# covered by a unit test in pardes.zig.
+# tty mode round-trip: Ctrl-b in, run a command, Ctrl-b out. Ctrl-b is the one
+# switch between raw and normal. Shift-Esc (CSI 27;2u — needs a host that
+# reports modifiers on Escape) never switches: in every mode it is what Escape
+# is in normal mode — Last, the pane you were in before this one — and a raw
+# terminal stays raw. The hop itself is covered by a unit test in pardes.zig.
file m.txt marker
start 31 100 -n 3
wait 8000 m.txt
@@ -21,16 +18,14 @@ snap tty-echo
key c-b
stable 600 8000
snap tty-off
-# Shift-Esc: in, the same as Ctrl-b...
+# Shift-Esc from normal: never into raw; it backs out to the previous pane.
send \e[27;2u
stable 600 8000
-snap shift-esc-on
-# ...but not out. With no doc pane to hop to it changes nothing: still tty,
-# prompts still shown, same pane focused.
-send \e[27;2u
+snap shift-esc-normal
+# Ctrl-b is the switch: into raw, and out again.
+key c-b
stable 600 8000
-snap shift-esc-nodoc
-# Ctrl-b still leaves tty in place, which is now the only thing that does.
+snap ctrl-b-on
key c-b
stable 600 8000
snap ctrl-b-off