summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-02 18:04:34 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit4ffc8ed795ff58426aea28d3b3b38df1428d2980 (patch)
tree65c36f8b2a7c8a3f8597b23b95878a52423f4f20
parent5961587b227e5fa097fb033e32c29da08a23fb18 (diff)
downloadpardes-4ffc8ed795ff58426aea28d3b3b38df1428d2980.tar.gz
pardes-4ffc8ed795ff58426aea28d3b3b38df1428d2980.zip
normal Esc toggles document/terminal focus
-rw-r--r--docs/design.typ4
-rw-r--r--docs/helix-keys.md2
-rw-r--r--next-steps.txt2
-rw-r--r--src/config.zig6
-rw-r--r--src/output_pane.zig5
-rw-r--r--src/pardes.zig15
-rw-r--r--src/tutor.txt9
-rw-r--r--test/snapshots/leader.golden2
-rw-r--r--test/snapshots/leader.snap9
-rw-r--r--test/snapshots/themesel.snap8
-rw-r--r--test/snapshots/windownav.golden62
-rw-r--r--test/snapshots/windownav.snap15
12 files changed, 108 insertions, 31 deletions
diff --git a/docs/design.typ b/docs/design.typ
index 9cc05cab..69c138f2 100644
--- a/docs/design.typ
+++ b/docs/design.typ
@@ -200,7 +200,9 @@ the source (cursor row kept visible); dying pane's weight absorbed by one
sibling; emptied column hands width to a neighbor; border-drag resize on a
pane's own trailing edge (v and h), hover shows `╎`/`╌` glyph-only hints;
move-drag via the gutter box with preview; Alt-n new shell below, Alt-c move
-pane to new column; Ctrl-w h/j/k/l directional focus; `--tty` single-pane mode.
+pane to new column; Ctrl-w h/j/k/l directional focus; body-normal Esc toggles
+between the latest document and terminal (the same builtin as SPC w t);
+`--tty` single-pane mode.
*Mouse.* Left: select (block, stays highlighted after drag, pins cursor,
enters normal), click clears, tag-row click enters tag edit, scrollbar
diff --git a/docs/helix-keys.md b/docs/helix-keys.md
index 839550a2..7fc318ba 100644
--- a/docs/helix-keys.md
+++ b/docs/helix-keys.md
@@ -83,7 +83,7 @@ with a reason in `test/hxcases/waivers.jsonl`.
| `y` | yank selection; bare cursor yanks the 1-wide selection (char under cursor) | line-yank vim-ism removed (phase 5); yank keeps selection AND cursor (helix). Also emits `set_clipboard` (OSC 52 out) | helix-verified |
| `u` / `U` | undo / redo | restores the pre-edit selection (helix); snapshot granularity, no `Alt-u`/`Alt-U` history walking (skipped) | helix-verified |
| `p` (normal) | paste the core's yank register after the selection | helix default-register semantics; yank still mirrors OUT via OSC 52 (`set_clipboard`) but `p` never reads the system clipboard back — most terminals refuse the OSC 52 read, which left `p` a silent no-op | helix-verified |
-| `Esc` (normal) | cancel pending prefix / exit select mode; the selection is KEPT | helix Esc does not collapse selections; never drops to tty mode | helix-verified |
+| `Esc` (body normal) | clear a pending modal prefix / exit select mode, keeping the selection, then run `Toggleterm`: hop between the most recently focused document and terminal, exactly like `SPC w t` | Pardes-specific focus binding layered on helix's cleanup. A leader path, tag, topbar or search owns Esc while it is active; raw tty forwards it | pardes-specific (cleanup helix-verified) |
| `Esc` (insert) | back to normal mode, cursor right after the insertion (no vim left-step) | | helix-verified |
| `Enter` (normal) | acme **look** chord: EXPLICIT selection, else file-ish word under cursor | pardes-specific, keep (helix normal-mode Enter unbound). Covers helix `gf`. Implicit motion residue falls back to the cursor word | pardes-specific |
| `Tab` (normal) | acme **execute** chord | pardes-specific, keep; explicit-selection rule as Enter | pardes-specific |
diff --git a/next-steps.txt b/next-steps.txt
index 1b6309d6..63c355e9 100644
--- a/next-steps.txt
+++ b/next-steps.txt
@@ -1,4 +1,4 @@
-- Esc in normal mode toggles focus (the same functionality as spc+w+t).
++ Esc in normal mode toggles focus (the same functionality as spc+w+t).
- sdl backend no longer grabs mouse focus
- implement a real basic config file at ~/.config/pardes (use zig stdlib to locate these config dirs) which will execute the builtins in each line; like Font abc, Theme asdfd, this will execute before rendering so that the application won't flash or anything when styling changes. also if there's an error with a command, it will silently fail.
- implement ipc comms via unix sockets, this will be used to deatach and reattach to a pardes session .
diff --git a/src/config.zig b/src/config.zig
index cee3613e..fed4994a 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -769,9 +769,9 @@ pub const redo: []const Chord = &.{.{ .cp = 'U' }};
/// where it is still SIGINT.
pub const comment_toggle: []const Chord = &.{.{ .cp = 'c', .ctrl = true }};
-/// Exit select mode / drop pending prefixes and the count; leave insert mode;
-/// abandon a tag, an armed search or the topbar. On an OUTPUT buffer there is
-/// nothing worth keeping, so Esc there is the Del builtin instead.
+/// In body normal mode, clear modal residue and run Toggleterm (the same
+/// builtin as `SPC w t`). Elsewhere: leave insert mode; abandon a leader path,
+/// tag, armed search or the topbar; raw tty mode forwards it to the program.
pub const escape: []const Chord = &.{.{ .cp = Key.escape }};
// ---- insert mode ----
diff --git a/src/output_pane.zig b/src/output_pane.zig
index bd11a227..4b62a06e 100644
--- a/src/output_pane.zig
+++ b/src/output_pane.zig
@@ -105,9 +105,6 @@ pub const Traits = struct {
/// that asked for it, lands directly below it and takes its rows from
/// there, so opening or closing one never resizes a bystander.
doc: bool = false,
- /// Esc dismisses it: there is no file behind the pane and nothing worth
- /// keeping, so "never mind" means close it — Esc IS the Del its tag offers.
- dismiss: bool = true,
/// there is a file behind it to write. Also what its tag says: no Save to
/// offer means the plain pane tail rather than the file one.
saves: bool = false,
@@ -116,7 +113,7 @@ pub const Traits = struct {
/// A REAL file pane, as a row of the same table — so kind-agnostic code asks
/// one question and gets one answer whichever it is holding. `name` is unused:
/// a file already has a path.
-const file_row: Traits = .{ .name = "", .doc = true, .dismiss = false, .saves = true };
+const file_row: Traits = .{ .name = "", .doc = true, .saves = true };
/// THE TABLE. Everything above, answered from the command that opened the
/// buffer. Exhaustive on purpose: a new `lsp.Kind` or a new output-opening
diff --git a/src/pardes.zig b/src/pardes.zig
index 86dcb97f..7b359f0d 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -4151,19 +4151,20 @@ pub const Pardes = struct {
const text = p.flatSurface(pane, pl) catch return;
const lines = pl.lines;
- // Esc: exit select mode / drop pending prefixes + count. The
- // selection itself SURVIVES (helix; `;` collapses it) — motions
- // replace it anyway. A buffer the table marks `dismiss` (every output
- // buffer today) instead takes Esc as "never mind" and closes: Esc IS
- // Del, the same builtin its tag offers.
+ // Esc: the fast document<->terminal hop, running the SAME Toggleterm
+ // builtin `SPC w t` does. Clear the normal-mode residue it used to
+ // cancel first: select mode and pending prefixes/count end here, while
+ // the selection itself SURVIVES (helix; `;` collapses it). Scoped Esc
+ // meanings (leader/tag/topbar/search cancel, insert -> normal, raw tty
+ // forwarding) are intercepted in handleKey before this body-normal
+ // handler and therefore stay unchanged.
if (hit(key, config.escape)) {
- if (pane.file) |f| if (output_pane.fileTraits(f.output).dismiss) return p.runBuiltin(.Del, p.active, "", null);
pane.select = false;
pane.pending = 0;
pane.pending2 = 0;
pane.pending_ch = 0;
pane.count = 0;
- return;
+ return p.runBuiltin(.Toggleterm, p.active, "", null);
}
// count prefix: digits accumulate while no prefix op is in flight
// (`0` stays line-start until a count begins)
diff --git a/src/tutor.txt b/src/tutor.txt
index 71a8fabb..6721e9e8 100644
--- a/src/tutor.txt
+++ b/src/tutor.txt
@@ -225,7 +225,9 @@
TTY ($) the REAL shell — prompts + typed input shown, and keys
go straight to the pty as terminal input.
- ESC: insert -> normal. (A plain Esc never reaches tty.)
+ ESC: insert -> normal; from body NORMAL it hops between the last
+ document and last terminal (the same Toggleterm as SPC w t).
+ In raw TTY it still goes to the program.
Ctrl-b: toggles TTY on a terminal by default; SHIFT-ESC is the same
toggle, where the host reports modifiers on Escape. Those are
the ONLY ways in or out.
@@ -490,6 +492,8 @@
Ctrl-w h/j/k/l focus the pane left/down/up/right (SPC w h/j/k/l
does the same; Ctrl-w also reaches a tty pane,
where SPC belongs to the shell)
+ Esc hop between the last document and last terminal
+ (body normal mode; the same builtin as SPC w t)
Alt-n new terminal below the active one
Alt-c move the active terminal into a fresh column
@@ -554,12 +558,13 @@
the selection; the mode+path selects but never edits;
k off the TOPMOST tagline = the top bar, j back down)
zt zz zb Ctrl-d/u Ctrl-f Ctrl-w hjkl Alt-n/c
+ Esc = last document <-> last terminal (body normal)
SPC = the leader: a key path runs a builtin (SPC ? lists
them; SPC k Kill, SPC d Del, SPC f s Save, SPC f f Find,
SPC w hjkl focus, SPC w t file <-> terminal)
vs Helix: no multi-cursor; selection is LINE-first (x), plus v chars.
- vs Vim: no verb+noun (dw); motions only move; Esc never reaches tty.
+ vs Vim: no verb+noun (dw); motions only move; body-normal Esc hops focus.
vs both: a terminal is just a pane; the same keys edit text and
navigate the shell screen, and the tty toggle reuses the
shell's own prompt to position you precisely.
diff --git a/test/snapshots/leader.golden b/test/snapshots/leader.golden
index 8b7a76a3..8966eeb4 100644
--- a/test/snapshots/leader.golden
+++ b/test/snapshots/leader.golden
@@ -490,7 +490,7 @@
|
|
|
-== snap esc-del grid=100x40 cursor=7,2
+== snap del-output grid=100x40 cursor=7,2
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| /tmp/pardes-snap/leader/cwd/cmds.txt Save Del /tmp/pardes-snap/leader/cwd Del
| 1 Help
diff --git a/test/snapshots/leader.snap b/test/snapshots/leader.snap
index 7cd0b98d..94276bbe 100644
--- a/test/snapshots/leader.snap
+++ b/test/snapshots/leader.snap
@@ -94,10 +94,9 @@ key ?
wait 10000 WsDiagnostics
stable 700 15000
snap help-language
-# Esc in the BODY of an output buffer IS Del: +Help has no file behind it and
-# nothing worth keeping, so the key that elsewhere only drops a selection
-# dismisses the whole pane and hands its height back to the pane above.
-key esc
+# Output buffers close through the same explicit Del builtin as every other
+# pane. Body-normal Esc is now Toggleterm and deliberately keeps +Help alive.
+key space d
settle 100
stable 700 15000
-snap esc-del
+snap del-output
diff --git a/test/snapshots/themesel.snap b/test/snapshots/themesel.snap
index fa53612c..56d81494 100644
--- a/test/snapshots/themesel.snap
+++ b/test/snapshots/themesel.snap
@@ -52,9 +52,9 @@ key N
stable 700 15000
snap step-back
snapstyle step-back
-# PICKING is just stopping. Walk down into the list and Esc it away (an output
-# buffer has no file behind it, so Esc IS Del there): the theme the last step
-# left behind is still on.
+# PICKING is just stopping. Walk down into the list and explicitly Del it: the
+# theme the last step left behind is still on. Body-normal Esc is Toggleterm,
+# so it deliberately leaves this document-like output buffer open.
key space w j
stable 400 5000
# ...and the WHOLE ring is in there, all 228 rows: `ge` to the last line, which
@@ -64,7 +64,7 @@ stable 400 5000
key g e
stable 700 15000
snap tail
-key esc
+key space d
settle 100
stable 700 15000
snap picked
diff --git a/test/snapshots/windownav.golden b/test/snapshots/windownav.golden
index d61f9620..2358654b 100644
--- a/test/snapshots/windownav.golden
+++ b/test/snapshots/windownav.golden
@@ -91,6 +91,68 @@
|
|
|
+== snap esc-to-term grid=100x30 cursor=54,4
+|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
+| /tmp/pardes-snap/windownav/cwd/w.txt Save Del /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap esc-back-to-file grid=100x30 cursor=7,2
+|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
+| /tmp/pardes-snap/windownav/cwd/w.txt Save Del /tmp/pardes-snap/windownav/cwd Del
+| 1 alpha
+| 2 beta w.txt
+| 3 gamma
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
== snap altn grid=100x30 cursor=4,19
|Kill Newcol Tutor Debug NextColor Dump Find Grep Help
| /tmp/pardes-snap/windownav/cwd/w.txt Save Del /tmp/pardes-snap/windownav/cwd Del
diff --git a/test/snapshots/windownav.snap b/test/snapshots/windownav.snap
index 0e57fc1e..cbb5490b 100644
--- a/test/snapshots/windownav.snap
+++ b/test/snapshots/windownav.snap
@@ -1,8 +1,9 @@
# RULE: moving focus is a BUILTIN like any other, so there is ONE
# implementation and every route reaches it. SPC w h/j/k/l run Left/Down/Up/
# Right — the geometric focusDir the Ctrl-w prefix also runs — and SPC w t
-# runs Toggleterm, the hop between the most recently focused DOCUMENT (a file,
-# an image, a +Search/+Help output buffer: things you READ) and the most
+# plus body-normal Esc run Toggleterm, the hop between the most recently
+# focused DOCUMENT (a file, an image, a +Search/+Help output buffer: things
+# you READ) and the most
# recently focused TERMINAL. "Most recent" is the focus history the core
# already keeps to hand focus back when a pane closes, so the hop back is the
# same key. Focus shows up in the golden's `cursor=` header: that is the
@@ -23,6 +24,16 @@ snap to-term
key space w t
stable 400 5000
snap back-to-file
+# A bare Esc from a body in normal mode dispatches that SAME builtin: file to
+# terminal, then the same key on the normal-mode terminal returns to the file.
+key esc
+settle 100
+stable 400 5000
+snap esc-to-term
+key esc
+settle 100
+stable 400 5000
+snap esc-back-to-file
# Alt-n: a second shell, below the file — the left column now has two rows
key a-n
stable 700 15000