summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/config.zig12
-rw-r--r--src/file_pane.zig9
-rw-r--r--src/pardes.zig75
-rw-r--r--test/snapshots/badutf.snap6
-rw-r--r--test/snapshots/hscroll.snap5
-rw-r--r--test/snapshots/lspdebug.golden16
-rw-r--r--test/snapshots/tutor.golden46
-rw-r--r--test/snapshots/wheeldrift.snap5
-rw-r--r--test/snapshots/wrap.golden80
-rw-r--r--test/snapshots/wrap.snap59
10 files changed, 188 insertions, 125 deletions
diff --git a/src/config.zig b/src/config.zig
index b7ca9b11..a8e3639f 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -432,11 +432,13 @@ test "wheel drift guard" {
/// the file pane's line-number gutter, in columns
pub const PREFIX_W: u16 = 5;
-/// soft wrap (the Wrap builtin): the widest a wrapped row may get. The rule is
-/// min(this, the pane's own content width), so a narrow pane breaks at its own
-/// right edge and a wide one stops here — a line dragged across a 300-column
-/// monitor is not readable text, it is a scanline.
-pub const WRAP_COLS: usize = 100;
+/// soft wrap (the Wrap builtin): the glyph a wrapped row ends with, in the one
+/// column bodyText keeps free for it. A break is the one thing about a wrapped
+/// line you cannot see — the text simply continues, and a missing line number
+/// on the row below is an absence, which is a poor thing to read a document by.
+/// So the break says so at the point it happens, in the chrome's own colour
+/// because it is not in the file.
+pub const wrap_marker = "↩";
/// vim 'scrolloff': keyboard cursor moves keep this many context rows visible
/// above/below the cursor (clamped at file boundaries and short panes), and
/// the same count of COLUMNS horizontally
diff --git a/src/file_pane.zig b/src/file_pane.zig
index b76a7882..98a72ac4 100644
--- a/src/file_pane.zig
+++ b/src/file_pane.zig
@@ -191,13 +191,14 @@ pub fn refreshHighlights(p: *Pardes) void {
/// readers treat as "rows are lines", so the off path never consults an array.
pub fn bodyText(arena: std.mem.Allocator, pane: *Pane, f: *File, wrap: bool) ![]const u8 {
var out: std.ArrayList(u8) = .empty;
- // min(WRAP_COLS, the pane's own content width), and 0 means don't wrap.
- // A pane taller than the map refuses too, rather than recording part of
- // itself — see Pane.wrap_line.
+ // the pane's own content width, less the ONE column the break marker sits
+ // in — a wrapped row that filled its last cell would have nowhere to say it
+ // wrapped. 0 means don't wrap. A pane taller than the map refuses too,
+ // rather than recording part of itself — see Pane.wrap_line.
const width: usize = if (!wrap or pane.rows > pane.wrap_line.len)
0
else
- @max(1, @min(config.WRAP_COLS, @as(usize, pane.cols -| config.PREFIX_W)));
+ @max(1, @as(usize, pane.cols -| config.PREFIX_W) -| 1);
pane.wrap_n = 0;
// start ON the first visible line instead of walking the file to it: this
// walk was O(f.scroll) and recolorSyntax below ran the identical one again
diff --git a/src/pardes.zig b/src/pardes.zig
index 7ff5ba1f..b8b2de33 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -2600,6 +2600,15 @@ pub const Surface = struct {
if (cp == '\r') continue;
const width: u16 = if (cp < 0x80) 1 else uucode.get(.width, cp);
if (width == 0) continue;
+ // a DOUBLE-width glyph with one column left is not drawn at all.
+ // Writing it puts one cell in the surface and two on the glass, and
+ // when that column is the screen's last the terminal wraps the tail
+ // onto the next row — where our own model says "space", so the diff
+ // render never repaints it and the smear outlives the frame. A
+ // blank at the edge is what every terminal does with the same
+ // problem. Reachable from any byte cut through wide text: hscroll's
+ // and soft wrap's both.
+ if (width == 2 and col + 1 >= end) break;
s.set(col, y, cp_slice, style);
if (width == 2 and col + 1 < end) {
// spacer: empty cell under the wide glyph's tail
@@ -3663,19 +3672,29 @@ pub const Pane = struct {
var margin: i32 = @min(config.scroll_off, @divTrunc(@as(i32, pane.rows) - 1, 2));
const off = pane.scroll();
// A WRAPPED body shows fewer LINES than it has rows, and scrolling is
- // still by line — so the bottom of the view is the last line the map
- // recorded, not off+rows-1, or every long line would scroll the file
- // out from under itself. Clamped to the file's own last line because
- // the rows past EOF go on counting, and the margin shrinks with it for
- // the same reason the short-pane clamp above exists: a band wider than
- // the view has no stable position and the two branches below then take
- // turns moving it, one keystroke each, for ever.
+ // still by line, so the bottom of the view is not off+rows-1 — a long
+ // line at the bottom would leave the cursor below the last row it can
+ // actually see. What the map is asked for is the COUNT of lines that
+ // fit, not which ones: this runs on every cursor move and the map is
+ // last FRAME's, but several keys can arrive between two renders (an
+ // autorepeated j, a paste) and then its absolute line numbers name a
+ // scroll offset that has already moved on — reading them cost a
+ // batched j four extra lines of scroll per keystroke. A count is
+ // scroll-independent, and when nothing wrapped it is exactly `rows`,
+ // so this whole block is a no-op on an unwrapped body and the margin
+ // clamp below reduces to the short-pane one above it.
+ //
+ // ponytail: last frame's line count applied to this frame's offset. It
+ // is exact whenever a render happened in between (the normal case) and
+ // an estimate mid-batch, self-correcting on the next key. The exact
+ // answer is to re-walk the lines from `off` accumulating wrapped
+ // heights — do that when a batch visibly lands the cursor off screen.
var last = off + @as(i32, pane.rows) - 1;
- if (pane.wrap_n > 0) if (pane.file) |*f| {
- const eof: i32 = @intCast(file_pane.nlines(pane.gpa, f) -| 1);
- last = @min(pane.wrap_line[pane.wrap_n - 1], eof);
- margin = @min(margin, @divTrunc(@max(0, last - off), 2));
- };
+ if (pane.wrap_n > 0) {
+ const lines_shown = pane.wrap_line[pane.wrap_n - 1] - pane.wrap_line[0];
+ last = off + lines_shown;
+ margin = @min(margin, @divTrunc(@max(0, lines_shown), 2));
+ }
if (pane.cur_row < off + margin) {
pane.scrollBy(pane.cur_row - margin - off); // scrollBy clamps at line 0
} else if (pane.cur_row > last - margin) {
@@ -3941,10 +3960,13 @@ pub const Pardes = struct {
/// its first theme. No frame is rendered in that interval.
animate_theme_changes: bool = false,
colors_on: bool = true,
- /// soft line breaks in file panes (the Wrap builtin). Off by default: with
- /// it off every row math in here is the one-row-per-line arithmetic it has
- /// always been, and nothing on screen moves by a cell.
- wrap_on: bool = false,
+ /// soft line breaks in file panes (the Wrap builtin). ON by default: a line
+ /// running off the right edge is text you cannot read without scrolling
+ /// sideways for it, and the reason to reach for the toggle is the rare
+ /// file — a table, a fixture, a minified blob — whose columns mean
+ /// something. Turned off, every row math in here is the one-row-per-line
+ /// arithmetic it always was and hscroll comes back.
+ wrap_on: bool = true,
show_debug: bool = false,
/// the Crt builtin's CRT post-process, rendered by the gui shell only
/// (the tty and web shells never read it)
@@ -8796,7 +8818,6 @@ pub const Pardes = struct {
// a topbar word runs on the PRESS — there is no
// drag to chord into, so the argument is simply
// whatever is selected right now: select a word,
- // then click Grep, and it greps that word.
if (word.len > 0) _ = p.execute(p.active, p.withArg(word, p.heldSelection(p.active)));
}
return;
@@ -12023,6 +12044,26 @@ pub const Pardes = struct {
if (pane.file) |*f| {
file_pane.drawGutter(p, pane, r, tx, tw, body_h, active);
if (p.colors_on) file_pane.recolorSyntax(p, pane, f, r, tx, tw, body_h);
+ // the break marker, in the column bodyText kept free for it: a row
+ // that continues says so where it broke. Drawn after the syntax
+ // pass so nothing recolors it (it is chrome, not file bytes) and
+ // before the selection passes, which still win over it — a swept
+ // wrapped line should highlight whole.
+ //
+ // ponytail: a row continues iff the NEXT row shows the same line,
+ // so the bottom row of a pane never carries one — what it continues
+ // into is off screen anyway. Record the cut in bodyText next to the
+ // map the day that reads as a lie.
+ if (tw > config.PREFIX_W + 1) {
+ var wr: u16 = 0;
+ while (wr + 1 < pane.wrap_n and wr + 1 < body_h) : (wr += 1) {
+ if (pane.wrap_line[wr + 1] != pane.wrap_line[wr]) continue;
+ s.set(tx + tw - 1, r.y + BOX_H + wr, config.wrap_marker, .{
+ .fg = .{ .rgb = chrome.lineno },
+ .bg = pane_bg,
+ });
+ }
+ }
}
const off = pane.scroll();
if (p.colors_on and pane.isTerminal()) term_pane.recolorAnsi(p, pane, r, tx, tw, body_h, off);
diff --git a/test/snapshots/badutf.snap b/test/snapshots/badutf.snap
index c1d818ac..49d90bdd 100644
--- a/test/snapshots/badutf.snap
+++ b/test/snapshots/badutf.snap
@@ -14,6 +14,12 @@ snap badbytes
start 30 50 wide.txt
wait 8000 New Newcol
stable 700 20000
+# SPC t w turns soft wrap OFF: hscroll is the other half of this case and a
+# wrapped body has nothing to scroll sideways, so the byte cut that halves a
+# glyph only exists here. (The WRAP cut halves one too — same bytes, same
+# U+FFFD — and wrap.snap pins that one.)
+key space t w
+stable 400 5000
key $
stable 700 10000
snap hscroll-midglyph
diff --git a/test/snapshots/hscroll.snap b/test/snapshots/hscroll.snap
index 3f371147..078b56e4 100644
--- a/test/snapshots/hscroll.snap
+++ b/test/snapshots/hscroll.snap
@@ -7,6 +7,11 @@ file wide.txt short1 END_A_MARKER_WAY_OUT_RIGHT_padpadpadpadpadpadpadpadpadpadpa
start 30 90 -n 3
wait 8000 wide.txt
stable 700 20000
+# soft wrap is ON by default and a wrapped body has nothing to scroll sideways
+# — hscroll is what you get when you turn it OFF, so this whole script runs
+# with it off. SPC t w is global, so it is spent once, before the file exists.
+key space t w
+stable 400 5000
# open wide.txt: click a body spot, type the name, look it
press left 5 8
release left 5 8
diff --git a/test/snapshots/lspdebug.golden b/test/snapshots/lspdebug.golden
index afc747eb..b34a264f 100644
--- a/test/snapshots/lspdebug.golden
+++ b/test/snapshots/lspdebug.golden
@@ -204,15 +204,15 @@
| 4 offsets: utf-8 walk caps: 512 files, 2000 rows
| 5
| 6 dependency imports gd can follow (8):
-| 7 zls /home/goblin/00-projects/0x4200.cafe/02-pardes-code/zig-pkg/zls-0.16.1-dev-rmm5ftfOJgAzIdWUGVB08
-| 8 tree-sitter /home/goblin/00-projects/0x4200.cafe/02-pardes-code/zig-pkg/tree_sitter-0.26.0-8heIf3CaAQB4SSumo
-| 9 zstbi /home/goblin/00-projects/0x4200.cafe/02-pardes-code/zig-pkg/zstbi-0.11.0-dev-L0Ea_3GWBwAh7m54XX0
-| 10 mvzr /home/goblin/00-projects/0x4200.cafe/02-pardes-code/zig-pkg/mvzr-0.3.9-ZSOky6t2AQCA7efmm5EYbPb5s
+| 7 zls /home/goblin/00-projects/0x4200.cafe/02-pardes-code/zig-pkg/zls-0.16.1-dev-rmm5ftfOJgAzIdWUGVB0↩
+| 8ycFbud2O3viKnGF1HZgfKZJ/src/zls.zig
+| 8 tree-sitter /home/goblin/00-projects/0x4200.cafe/02-pardes-code/zig-pkg/tree_sitter-0.26.0-8heIf3CaAQB4SSum↩
+| oGzq3gLSz2rkvXxcKFyskRWqDGBz/src/root.zig
+| 9 zstbi /home/goblin/00-projects/0x4200.cafe/02-pardes-code/zig-pkg/zstbi-0.11.0-dev-L0Ea_3GWBwAh7m54XX↩
+| 055vwLKPTPij5hyqyeq92MXPE2/src/zstbi.zig
+| 10 mvzr /home/goblin/00-projects/0x4200.cafe/02-pardes-code/zig-pkg/mvzr-0.3.9-ZSOky6t2AQCA7efmm5EYbPb5↩
+| sOdbXa4EFsUgYbQ6hZv9/src/mvzr.zig
| 11 mupdf /home/goblin/00-projects/0x4200.cafe/02-pardes-code/src/pdf.zig
-| 12 ghostty-vt /home/goblin/00-projects/0x4200.cafe/02-pardes-code/zig-pkg/ghostty-1.3.2-dev-5UdBC1v_EAUhcFYXrd
-| 13 vaxis /home/goblin/00-projects/0x4200.cafe/02-pardes-code/zig-pkg/vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402r
-| 14 uucode /home/goblin/00-projects/0x4200.cafe/02-pardes-code/zig-pkg/uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIH
-| 15 asked from: /tmp/pardes-snap/lspdebug/cwd/z.zig
| /tmp/pardes-snap/lspdebug/cwd/+Lsp New Del
| 1 lsp explain — the definition query at byte 29 of z.zig
| 2
diff --git a/test/snapshots/tutor.golden b/test/snapshots/tutor.golden
index 53e9cc4e..93ecbe58 100644
--- a/test/snapshots/tutor.golden
+++ b/test/snapshots/tutor.golden
@@ -2,30 +2,30 @@
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
| /Tutor Save New Del /tmp/pardes-snap/tutor/cwd New Del /tmp/pardes-snap/tutor/cwd New Del
| 1
-| 2 PARDES TUTOR
-| 3 tmux + vi + acme, from
+| 2 PARDES TUTO↩
+| R
+| 3 tmux + vi + acme, fro↩
+| m scratch.
| 4
-| 5 The model is acme's: EVERYTHING is
-| 6 the same way. Modal editing (helix-
-| 7 Press j until you reach the introdu
+| 5 The model is acme's: EVERYTHING is↩
+| text, editable and executable
+| 6 the same way. Modal editing (helix↩
+| -style) lives on top of that.
+| 7 Press j until you reach the introd↩
+| uction.
| 8
| 9
-| 10 ===========================================
-| 11 = INTRODUCTION
-| 12 ===========================================
+| 10 ==========================================↩ /tmp/pardes-snap/tutor/cwd New Del
+| =======================
+| 11 = INTRODUCTION ↩
+| =
+| 12 ==========================================↩
+| =======================
| 13
-| 14 Pardes is tmux + vi + acme, made from scra
-| 15 the acme model: a pane is just text on scr /tmp/pardes-snap/tutor/cwd New Del
-| 16 "buffer" type — what you see is what you e
-| 17 be live terminals (tty mode); most are tex
-| 18 over. Modal editing (helix-style) is layer
-| 19
-| 20 Three modes (the BOX at the pane's top-lef
-| 21 • NORMAL block cursor, keys move/edit
-| 22 ^ INSERT keys type text at the cursor
-| 23 $ TTY keys go straight to the shel
-| 24
-| 25 This tutor is in THREE parts, ordered by w
-| 26 editors you may know:
-| 27 PART 1 — the MOUSE. Acme's three buttons
-| 28 PART 2 — the TTY. A terminal is just a
+| 14 Pardes is tmux + vi + acme, made from scr↩
+| atch. The foundation is
+| 15 the acme model: a pane is just text on sc↩
+| reen. There is no separate
+| 16 "buffer" type — what you see is what yo ↩
+| u edit. Some panes happen to
+| 17 be live terminals (tty mode); most are te
diff --git a/test/snapshots/wheeldrift.snap b/test/snapshots/wheeldrift.snap
index a546d2ff..5122c949 100644
--- a/test/snapshots/wheeldrift.snap
+++ b/test/snapshots/wheeldrift.snap
@@ -10,6 +10,11 @@ lines drift.txt 20 _0123456789_abcdefghij_ZEND
start 16 70 drift.txt
wait 8000 drift.txt
stable 700 20000
+# the guard protects HSCROLL, and hscroll only exists with soft wrap off (a
+# wrapped body has nothing off to the right to drift towards), so turn it off
+# first — otherwise every sideways tick is a no-op and the test proves nothing
+key space t w
+stable 400 5000
snap home
# a drifting swipe: five ticks down with three sideways ones mixed in. The
# view scrolls DOWN five lines and does not move one column sideways.
diff --git a/test/snapshots/wrap.golden b/test/snapshots/wrap.golden
index 87b70f68..7481cec3 100644
--- a/test/snapshots/wrap.golden
+++ b/test/snapshots/wrap.golden
@@ -1,7 +1,11 @@
-== snap off grid=47x14 cursor=7,2
-|New Newcol Find Grep Help Tutor Dump NextColor
-| /tmp/pardes-snap/wrap/cwd/long.txt Save New D
-| 1 0---------1---------2---------3---------
+== snap on-narrow grid=48x14 cursor=7,2
+|New Newcol Find Grep Help Tutor Dump NextColor D
+| /tmp/pardes-snap/wrap/cwd/long.txt Save New De
+| 1 0---------1---------2---------3---------↩
+| 4---------5---------6---------7---------↩
+| 8---------9---------A---------B---------↩
+| C---------D---------E---------F---------↩
+| G---------H---------I---------J---------
| 2 second line
| 3 third line
| 4
@@ -9,17 +13,13 @@
|
|
|
-|
-|
-|
-|
-== snap on-narrow grid=47x14 cursor=7,2
-|New Newcol Find Grep Help Tutor Dump NextColor
-| /tmp/pardes-snap/wrap/cwd/long.txt Save New D
-| 1 0---------1---------2---------3---------
-| 4---------5---------6---------7---------
-| 8---------9---------A---------B---------
-| C---------D---------E---------F---------
+== snap clicked grid=48x14 cursor=17,3
+|New Newcol Find Grep Help Tutor Dump NextColor D
+| /tmp/pardes-snap/wrap/cwd/long.txt Save New De
+| 1 0---------1---------2---------3---------↩
+| 4---------5---------6---------7---------↩
+| 8---------9---------A---------B---------↩
+| C---------D---------E---------F---------↩
| G---------H---------I---------J---------
| 2 second line
| 3 third line
@@ -28,13 +28,13 @@
|
|
|
-== snap clicked grid=47x14 cursor=17,3
-|New Newcol Find Grep Help Tutor Dump NextColor
-| /tmp/pardes-snap/wrap/cwd/long.txt Save New D
-| 1 0---------1---------2---------3---------
-| 4---------5---------6---------7---------
-| 8---------9---------A---------B---------
-| C---------D---------E---------F---------
+== snap logical-j grid=48x14 cursor=18,7
+|New Newcol Find Grep Help Tutor Dump NextColor D
+| /tmp/pardes-snap/wrap/cwd/long.txt Save New De
+| 1 0---------1---------2---------3---------↩
+| 4---------5---------6---------7---------↩
+| 8---------9---------A---------B---------↩
+| C---------D---------E---------F---------↩
| G---------H---------I---------J---------
| 2 second line
| 3 third line
@@ -43,14 +43,10 @@
|
|
|
-== snap logical-j grid=47x14 cursor=18,7
-|New Newcol Find Grep Help Tutor Dump NextColor
-| /tmp/pardes-snap/wrap/cwd/long.txt Save New D
-| 1 0---------1---------2---------3---------
-| 4---------5---------6---------7---------
-| 8---------9---------A---------B---------
-| C---------D---------E---------F---------
-| G---------H---------I---------J---------
+== snap off grid=48x14 cursor=18,3
+|New Newcol Find Grep Help Tutor Dump NextColor D
+| /tmp/pardes-snap/wrap/cwd/long.txt Save New De
+| 1 0---------1---------2---------3---------4
| 2 second line
| 3 third line
| 4
@@ -58,26 +54,30 @@
|
|
|
-== snap off-again grid=47x14 cursor=18,3
-|New Newcol Find Grep Help Tutor Dump NextColor
-| /tmp/pardes-snap/wrap/cwd/long.txt Save New D
-| 1 0---------1---------2---------3---------
-| 2 second line
-| 3 third line
-| 4
|
|
|
|
+== snap on-again grid=48x14 cursor=18,7
+|New Newcol Find Grep Help Tutor Dump NextColor D
+| /tmp/pardes-snap/wrap/cwd/long.txt Save New De
+| 1 0---------1---------2---------3---------↩
+| 4---------5---------6---------7---------↩
+| 8---------9---------A---------B---------↩
+| C---------D---------E---------F---------↩
+| G---------H---------I---------J---------
+| 2 second line
+| 3 third line
+| 4
|
|
|
|
-== snap on-wide grid=127x14 cursor=7,2
+== snap on-wide grid=128x14 cursor=7,2
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
| /tmp/pardes-snap/wrap/cwd/long.txt Save New Del
-| 1 0---------1---------2---------3---------4---------5---------6---------7---------8---------9---------
-| A---------B---------C---------D---------E---------F---------G---------H---------I---------J---------
+| 1 0---------1---------2---------3---------4---------5---------6---------7---------8---------9---------A---------B---------↩
+| C---------D---------E---------F---------G---------H---------I---------J---------
| 2 second line
| 3 third line
| 4
diff --git a/test/snapshots/wrap.snap b/test/snapshots/wrap.snap
index c6e976b1..b8849474 100644
--- a/test/snapshots/wrap.snap
+++ b/test/snapshots/wrap.snap
@@ -1,35 +1,34 @@
-# Soft line breaks (the Wrap builtin, `SPC t w`): a file line too long for the
-# pane renders across several body rows instead of running off the right edge.
-# OFF by default, and off it is the clipping render it has always been.
+# Soft line breaks: ON by default, off with the Wrap builtin (`SPC t w`). A
+# file line too long for the pane renders across several body rows instead of
+# running off the right edge.
#
-# Three rules pinned here. The break is at min(100, the pane's own content
-# width), so a narrow pane breaks at its own edge and a wide one stops at 100.
-# A continuation row carries a BLANK gutter — the number belongs to the LINE,
-# and a number repeated down a wrapped line would read as several lines. And
-# the mouse follows the render: a click on a continuation row lands on the
-# character under the pointer, which only works if the click resolves through
-# the same row->line map the body was drawn from.
+# Three rules pinned here. The break is at the PANE'S OWN content width and
+# nothing else — there is no upper bound, a wide window breaks wide. A
+# continuation row carries a BLANK gutter (the number belongs to the LINE, and
+# a number repeated down a wrapped line would read as several lines) and the
+# row it broke out of ends in `↩`, in the one column the wrap keeps free for
+# it — a break is otherwise invisible, and an absent line number is a poor
+# thing to read a document by. And the mouse follows the render: a click on a
+# continuation row lands on the character under the pointer, which only works
+# if the click resolves through the same row->line map the body was drawn from.
#
# The line is 20 decades, each headed by its own column marker: `0` at column
# 0, `4` at 40, `A` at 100. So the first character of a continuation row NAMES
# the column it broke at, and the width rule is readable straight off the grid.
file long.txt 0---------1---------2---------3---------4---------5---------6---------7---------8---------9---------A---------B---------C---------D---------E---------F---------G---------H---------I---------J---------\nsecond line\nthird line\n
-# 47 columns: the 2-column chrome gutter plus the 5-column line-number gutter
-# leave 40 for content, under 100, so the PANE's width is the break.
-start 14 47 long.txt
+# 48 columns: the 2-column chrome gutter, the 5-column line-number gutter and
+# the 1 column the `↩` sits in leave 40 for content.
+start 14 48 long.txt
wait 8000 New Newcol
stable 700 20000
-# off: line 1 on ONE row, cut at the right edge, lines 2 and 3 under it
-snap off
-key space t w
-stable 400 5000
-# on: five rows for line 1 (200 bytes / 40), the four continuations gutterless
-# and starting at `4` `8` `C` `G` — the columns 40, 80, 120, 160
+# the default: five rows for line 1 (200 bytes / 40), the four continuations
+# gutterless and starting at `4` `8` `C` `G` — the columns 40, 80, 120, 160 —
+# and each of the first four rows ending in the break marker
snap on-narrow
# a click on the SECOND row of line 1, on the `5` marker at column 50. The
# cursor must come back to the cell that was clicked (grid 17,3): the click
# resolved to line 1 column 50, and the render put line 1 column 50 back where
-# the finger was. Off by one row and it would land in line 2 instead.
+# the finger was. Off by one row and it would land in line 2.
press left 18 4
release left 18 4
stable 400 5000
@@ -39,16 +38,20 @@ snap clicked
key j
stable 400 5000
snap logical-j
-# ...and off again is off: the same clipped body as the first snap, because
-# the map the readers consult goes back to empty and every one of them falls
-# back to the row arithmetic it used before any of this existed
+# SPC t w turns it off: line 1 back on ONE row, clipped at the right edge with
+# no marker, lines 2 and 3 under it — the render this pane had before soft
+# breaks existed, because the map the readers consult goes back to empty and
+# every one of them falls back to the row arithmetic it used before any of it
key space t w
stable 400 5000
-snap off-again
-# 127 columns: 120 for content, so the 100 wins and the break is at the `A`
-start 14 127 long.txt
-wait 8000 New Newcol
-stable 700 20000
+snap off
+# ...and on again is on
key space t w
stable 400 5000
+snap on-again
+# 128 columns: 120 for content, and the break is at the `C` — column 120, the
+# window's own width. The old 100-column cap would have broken at the `A`.
+start 14 128 long.txt
+wait 8000 New Newcol
+stable 700 20000
snap on-wide