summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/design.typ3
-rw-r--r--docs/macos.md16
-rw-r--r--src/file_pane.zig7
-rw-r--r--src/nested.zig98
-rw-r--r--src/pardes.zig57
-rw-r--r--src/tutor.txt3
-rw-r--r--test/e2e_harness.zig9
-rw-r--r--test/snapshots/lsp-rename.golden6
-rw-r--r--test/snapshots/lspcomplete.golden18
-rw-r--r--test/snapshots/lspcompletemoved.golden2
-rw-r--r--test/snapshots/lsprelpath.golden10
-rw-r--r--test/snapshots/msg.golden2
-rw-r--r--test/snapshots/pipe.golden4
-rw-r--r--test/snapshots/selregex.golden4
-rw-r--r--test/snapshots/tagbottom.golden2
-rw-r--r--test/snapshots/tagcmd.golden10
-rw-r--r--test/snapshots/tagpath.golden8
-rw-r--r--test/snapshots/yankpaste.golden8
18 files changed, 169 insertions, 98 deletions
diff --git a/docs/design.typ b/docs/design.typ
index d49be3e6..0c91d15d 100644
--- a/docs/design.typ
+++ b/docs/design.typ
@@ -428,7 +428,8 @@ cannot clobber what the desktop was holding. A paste from an outer terminal
arrives bracketed, as one `paste` event.
*Tag.* Live prefix (mode indicator, cwd or path) + editable tail with the
-full modal editor; defaults `New Del` / `Save New Del`; an image tag reports
+full modal editor; defaults `New Del` / `Save New Del`, with `*` after an
+unsaved file's name; an image tag reports
`img petscii:<on|off> palette:<commodore|terminal> ascii:<on|off> <path>`
before the ordinary tail (its renderer toggles are builtins under `SPC t
p/l/a`). Topbar:
diff --git a/docs/macos.md b/docs/macos.md
index 5e5f13e3..be110532 100644
--- a/docs/macos.md
+++ b/docs/macos.md
@@ -323,13 +323,15 @@ document, so focus landing on one clears the icon and puts the title back to
icon: they are real paths, and a proxy icon is about the file, not about who
may edit it.
-The dirty half needed the one core change in all of this. `File` counted
-`revision` but never recorded which edit was last *written*, so no shell could
-derive "unsaved" — `File.saved_revision` is that watermark, set by `Save` at
-the moment the write is asked for. Nothing in the core renders it and no other
-shell reads it; it exists because a windowed host has somewhere to put the
-answer. It is marked at ask-time rather than on completion because `save_file`
-carries none back, which makes it exactly as honest as the tagline already was.
+The dirty half needed one core watermark. `File` counted `revision` but never
+recorded which edit was last *written*, so no shell could derive "unsaved" —
+`File.saved_revision` is that watermark, advanced by `Save` at the moment the
+write is asked for and by a successful external-file reload whose bytes
+already came from disk. The core renders the same answer as ` *` after the
+path in every tagline, while this host also puts it in the native close button.
+Save is marked at ask-time rather than on completion because `save_file`
+carries none back, which makes both indicators exactly as honest as the Save
+request.
## The menu bar, and the chords the core never sees
diff --git a/src/file_pane.zig b/src/file_pane.zig
index abf37448..fd0ab2b7 100644
--- a/src/file_pane.zig
+++ b/src/file_pane.zig
@@ -36,7 +36,8 @@ pub const State = struct {
/// goes through setContent, which bumps this; a pipe completion accepted
/// against another revision would overwrite intervening work.
revision: u32 = 0,
- /// Revision last handed to Save. Equal means the screen matches disk.
+ /// Revision last known to match disk, after Save or an external reload.
+ /// Equal means the screen matches disk.
saved_revision: u32 = 0,
/// Non-null for a generated output buffer rather than an on-disk file.
output: ?output_pane.Output = null,
@@ -485,6 +486,10 @@ pub fn changed(p: *Pardes, id: u8, bytes: []const u8) void {
const new = p.gpa.dupe(u8, bytes) catch return;
pushUndo(p, pane);
setContent(p, f, new);
+ // These bytes came from the watched path, so the new on-screen revision
+ // is already saved. Undoing back to displaced local work bumps revision
+ // again and makes that restored edit dirty, as it should.
+ f.saved_revision = f.revision;
// restoreSnap only consumes cursor/selection from this synthetic snapshot.
restoreSnap(pane, f, .{
.content = undefined,
diff --git a/src/nested.zig b/src/nested.zig
index 16cea6a1..eb01b2e0 100644
--- a/src/nested.zig
+++ b/src/nested.zig
@@ -25,9 +25,9 @@
//! null and a pardes inside a pardes opens a second session, as before.
//!
//! macOS also has a third executable in the family: the app bundle. Its binary
-//! is the same build as `bin/pardes` installed a second time, at a path that
-//! shares nothing below the install prefix, so identity is compared at that
-//! prefix — see samePardesExecutable.
+//! is named `pardes`, like the tty frontend, wherever the bundle is installed.
+//! Executable identity therefore comes from the family name rather than its
+//! path — see samePardesExecutable.
const std = @import("std");
const builtin = @import("builtin");
const libc = std.c;
@@ -115,44 +115,22 @@ fn stripDeleted(link: []const u8) []const u8 {
return if (std.mem.endsWith(u8, link, suffix)) link[0 .. link.len - suffix.len] else link;
}
-/// The install prefix a program directory belongs to. `bin/pardes` and
-/// `pardes.app/Contents/MacOS/pardes` are one build installed twice and share
-/// no directory at all, so comparing dirnames says they are strangers; both
-/// reduce to the prefix, and so does everything else — a directory that is
-/// neither wrapper is its own prefix, which leaves the same-directory rule
-/// below exactly as strict as it was.
-fn installPrefix(dir: []const u8) []const u8 {
- const macos_dir = "/Contents/MacOS";
- if (std.mem.endsWith(u8, dir, macos_dir)) {
- const app = dir[0 .. dir.len - macos_dir.len];
- if (std.mem.endsWith(u8, app, ".app")) return std.fs.path.dirname(app) orelse app;
- }
- const bin = "/bin";
- if (std.mem.endsWith(u8, dir, bin)) return dir[0 .. dir.len - bin.len];
- return dir;
-}
-
/// The tty, SDL and macOS builds are sibling frontends of the same program.
/// Their installed names differ only by `-gui` (and, for cross builds, share
/// the same `-os-arch` tail), or not at all when one of them is the app bundle
-/// — so any of them must recognise any other as an outer pardes. Requiring the
-/// same install prefix retains the executable-identity check: an unrelated
-/// ancestor merely named `pardes` is not enough.
+/// — so any of them must recognise any other as an outer pardes. Paths are
+/// deliberately ignored: the GUI may be installed system-wide while the tty
+/// frontend is installed in the user's bin directory.
fn samePardesExecutable(a_raw: []const u8, b_raw: []const u8) bool {
const a = stripDeleted(a_raw);
const b = stripDeleted(b_raw);
- if (std.mem.eql(u8, a, b)) return true;
- const a_dir = installPrefix(std.fs.path.dirname(a) orelse return false);
- const b_dir = installPrefix(std.fs.path.dirname(b) orelse return false);
- if (!std.mem.eql(u8, a_dir, b_dir)) return false;
-
return sameFamily(std.fs.path.basename(a), std.fs.path.basename(b));
}
/// What is left of a family name after the frontend part: `` for `pardes` and
/// `pardes-gui`, `-linux-aarch64` for the cross-built spellings of both. Null
-/// when the name is not in the family at all — `not-pardes` and `pardesfoo`
-/// are other programs.
+/// when the name is not in the family at all — `not-pardes`, `pardesfoo`, and
+/// helper binaries such as `pardes-snap` are other programs.
fn familyTail(name: []const u8) ?[]const u8 {
const rest = if (std.mem.startsWith(u8, name, "pardes-gui"))
name["pardes-gui".len..]
@@ -160,23 +138,30 @@ fn familyTail(name: []const u8) ?[]const u8 {
name["pardes".len..]
else
return null;
- // `pardesfoo` shares a prefix and nothing else. A tail is a tail or empty.
- if (rest.len != 0 and rest[0] != '-') return null;
+ if (rest.len == 0) return rest;
+ // Build names have exactly `-os-arch` after the frontend. Validating both
+ // fields keeps sibling installs flexible without mistaking pardes-snap,
+ // pardes-perf, and the other helper executables for editor frontends.
+ if (rest[0] != '-') return null;
+ var fields = std.mem.splitScalar(u8, rest[1..], '-');
+ const os = fields.next() orelse return null;
+ const arch = fields.next() orelse return null;
+ if (fields.next() != null) return null;
+ if (std.meta.stringToEnum(std.Target.Os.Tag, os) == null) return null;
+ if (std.meta.stringToEnum(std.Target.Cpu.Arch, arch) == null) return null;
return rest;
}
-/// Two family names for the same build, given that they already share an
-/// install prefix. The tails have to agree — a linux binary and an x86_64 one
-/// in the same directory are two builds — unless one of them has no tail at
+/// Two executable names in the same family. The tails have to agree — a linux
+/// binary and an x86_64 one are two builds — unless one of them has no tail at
/// all, which is the untagged name the default build and, unavoidably, the app
/// bundle both produce: CFBundleExecutable is a fixed string, so the bundled
/// copy of `pardes-macos-aarch64` is called `pardes` and nothing in the name
-/// records what it was. Loosening it that far is safe because the prefix
-/// already had to match, and a foreign-arch ancestor cannot be running here.
+/// records what it was. A foreign-arch ancestor cannot be running here.
fn sameFamily(a: []const u8, b: []const u8) bool {
- if (std.mem.eql(u8, a, b)) return true;
const a_tail = familyTail(a) orelse return false;
const b_tail = familyTail(b) orelse return false;
+ if (std.mem.eql(u8, a, b)) return true;
return a_tail.len == 0 or b_tail.len == 0 or std.mem.eql(u8, a_tail, b_tail);
}
@@ -262,9 +247,19 @@ pub fn outer() ?libc.pid_t {
if (comptime !supported) return null;
var self_buf: [4096]u8 = undefined;
const self_exe = exeOf(libc.getpid(), &self_buf) orelse return null;
+ // A process harness may deliberately launch a fresh top-level pardes from
+ // inside another one. Its pid is a process-tree boundary, not an opt-out
+ // for the new session itself: pane shells below the child still detect it.
+ // This is what lets the snapshot harness exercise nested launches while
+ // the harness happens to be running in a real pardes pane.
+ const boundary = if (libc.getenv("PARDES_NESTED_BOUNDARY_PID")) |raw|
+ std.fmt.parseInt(libc.pid_t, std.mem.span(raw), 10) catch 0
+ else
+ 0;
var pid = libc.getppid();
var hops: usize = 0;
while (pid > 1 and hops < 64) : (hops += 1) {
+ if (pid == boundary) return null;
var buf: [4096]u8 = undefined;
if (exeOf(pid, &buf)) |exe| if (samePardesExecutable(exe, self_exe)) return pid;
pid = parentOf(pid) orelse return null;
@@ -543,9 +538,11 @@ test "tty and GUI sibling executables recognise each other" {
"/work/zig-out/bin/pardes-linux-aarch64",
"/work/zig-out/bin/pardes-gui-linux-aarch64 (deleted)",
));
- try std.testing.expect(!samePardesExecutable(
- "/usr/bin/pardes",
- "/work/zig-out/bin/pardes-gui",
+ // Installation paths do not define the family. This is the ordinary
+ // system-GUI/user-TTY pairing and the reason this comparison uses names.
+ try std.testing.expect(samePardesExecutable(
+ "/home/who/.local/bin/pardes",
+ "/usr/bin/pardes-gui",
));
try std.testing.expect(!samePardesExecutable(
"/work/zig-out/bin/pardes-linux-aarch64",
@@ -555,9 +552,17 @@ test "tty and GUI sibling executables recognise each other" {
"/work/zig-out/bin/not-pardes",
"/work/zig-out/bin/not-pardes-gui",
));
+ try std.testing.expect(!samePardesExecutable(
+ "/one/bin/not-pardes",
+ "/two/bin/not-pardes",
+ ));
+ try std.testing.expect(!samePardesExecutable(
+ "/work/zig-out/bin/pardes-snap",
+ "/usr/bin/pardes",
+ ));
}
-test "the app bundle is the same build as the binary installed beside it" {
+test "the app bundle is in the same executable family" {
// What `pardes foo.zig` typed into the bundle's own shell has to resolve:
// the ancestor is zig-out/pardes.app/..., this process is zig-out/bin/...,
// and nothing below zig-out is shared.
@@ -577,15 +582,12 @@ test "the app bundle is the same build as the binary installed beside it" {
"/work/zig-out/pardes.app/Contents/MacOS/pardes",
"/work/zig-out/bin/pardes-macos-aarch64",
));
- // A different install is still a different program, however alike the
- // paths look — this is the whole point of comparing anything at all.
- try std.testing.expect(!samePardesExecutable(
+ // Installation location does not matter here either.
+ try std.testing.expect(samePardesExecutable(
"/work/zig-out/pardes.app/Contents/MacOS/pardes",
"/opt/zig-out/bin/pardes",
));
- // The wrapper is only transparent when it IS the wrapper: `Contents/MacOS`
- // under something that is not a bundle keeps its own directory.
- try std.testing.expect(!samePardesExecutable(
+ try std.testing.expect(samePardesExecutable(
"/work/zig-out/pardes/Contents/MacOS/pardes",
"/work/zig-out/bin/pardes",
));
diff --git a/src/pardes.zig b/src/pardes.zig
index 3639af0f..6eb8a740 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -137,6 +137,10 @@ test {
const pane_tail = " " ++ config.pane_builtins_str;
const file_pane_tail = " " ++ config.file_pane_builtins_str;
+// Kept separate from the path so a click still expands to the exact filename.
+// It belongs to the live, read-only prefix rather than the editable command
+// tail: saving removes it without rewriting anything the user typed there.
+const dirty_marker = " *";
// Version-1 dumps originally persisted only the whole rendered tag. These were
// the two canonical tails before New joined every pane; the compatibility
// parser recognizes them as defaults while new dumps carry an explicit tail.
@@ -767,6 +771,37 @@ test "legacy default tag tails upgrade while custom tails remain owned" {
try std.testing.expectEqualStrings(" Keep Del", image_doc.tagSlice());
}
+test "an unsaved file marker sits between its path and builtins until Save" {
+ const gpa = std.testing.allocator;
+ const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 100, .rows = 30 });
+ defer p.deinit();
+ while (p.nextEffect()) |_| {}
+ const pane = try p.hxOpenFileContent("before\n");
+
+ const clean = try p.tagText(p.scratch.allocator(), pane);
+ try std.testing.expect(std.mem.indexOf(u8, clean, "/hxcase.txt *") == null);
+
+ file_pane.setContent(p, &pane.file.?, try gpa.dupe(u8, "after\n"));
+ const dirty = try p.tagText(p.scratch.allocator(), pane);
+ const marker_at = std.mem.indexOf(u8, dirty, "/hxcase.txt *") orelse return error.MissingDirtyMarker;
+ const save_at = std.mem.indexOf(u8, dirty, "Save") orelse return error.MissingSaveBuiltin;
+ try std.testing.expect(marker_at < save_at);
+
+ try std.testing.expect(p.executeBuiltinLine(0, "Save"));
+ const saved = try p.tagText(p.scratch.allocator(), pane);
+ try std.testing.expect(std.mem.indexOf(u8, saved, "/hxcase.txt *") == null);
+
+ file_pane.changed(p, 0, "external\n");
+ const reloaded = try p.tagText(p.scratch.allocator(), pane);
+ try std.testing.expect(std.mem.indexOf(u8, reloaded, "/hxcase.txt *") == null);
+
+ // A generated output is file-shaped but has nothing it can save.
+ pane.file.?.output = .{ .from = .search };
+ file_pane.setContent(p, &pane.file.?, try gpa.dupe(u8, "result\n"));
+ const output = try p.tagText(p.scratch.allocator(), pane);
+ try std.testing.expect(std.mem.indexOf(u8, output, "/hxcase.txt *") == null);
+}
+
test "unknown Exec from an image writes to a terminal in the image directory" {
const gpa = std.testing.allocator;
const p = try Pardes.init(gpa, .{});
@@ -5668,7 +5703,11 @@ pub const Pardes = struct {
.{ pv.page + 1, pv.page_count, @tagName(pv.fit), @tagName(pv.tint), pv.path },
);
if (pane.image) |*state| return image_pane.tagPrefix(arena, state);
- if (pane.file) |f| return arena.dupe(u8, f.path);
+ if (pane.file) |f| {
+ if (output_pane.fileTraits(f.output).saves and f.revision != f.saved_revision)
+ return std.fmt.allocPrint(arena, "{s}{s}", .{ f.path, dirty_marker });
+ return arena.dupe(u8, f.path);
+ }
return arena.dupe(u8, pane.cwdSlice());
}
@@ -12308,7 +12347,7 @@ pub const Pardes = struct {
.generic;
const current = p.tagPrefix(pane) catch return;
if (std.mem.startsWith(u8, src.tag, current))
- return p.restoreTailAt(pane, src.tag, current, tail_class);
+ return p.restoreTailAt(pane, src.tag, savedPrefix(src.tag, current, tail_class), tail_class);
if (src.kind != .image) return;
const saved = src.image.?;
if (pane.image) |*state| if (image_pane.legacySavedPrefix(state, src.tag)) |legacy|
@@ -12322,7 +12361,8 @@ pub const Pardes = struct {
/// retaining genuinely edited bytes.
fn restoreTail(p: *Pardes, pane: *Pane, saved_tag: []const u8) void {
const pfx = p.tagPrefix(pane) catch return;
- p.restoreTailAt(pane, saved_tag, pfx, tailClass(pane));
+ const class = tailClass(pane);
+ p.restoreTailAt(pane, saved_tag, savedPrefix(saved_tag, pfx, class), class);
}
const TailClass = enum { generic, savable_file };
@@ -12332,6 +12372,17 @@ pub const Pardes = struct {
return .generic;
}
+ /// A dirty marker may be present in the rendered compatibility tag of an
+ /// untouched file. It is live prefix chrome, not a custom command tail;
+ /// consume it while recovering old dumps so it disappears after Save.
+ fn savedPrefix(saved_tag: []const u8, live: []const u8, class: TailClass) []const u8 {
+ if (class != .savable_file or !std.mem.startsWith(u8, saved_tag, live)) return live;
+ const marked_len = live.len + dirty_marker.len;
+ if (saved_tag.len >= marked_len and std.mem.eql(u8, saved_tag[live.len..marked_len], dirty_marker))
+ return saved_tag[0..marked_len];
+ return live;
+ }
+
fn restoreTailAt(
_: *Pardes,
pane: *Pane,
diff --git a/src/tutor.txt b/src/tutor.txt
index 2fb89ea0..5a8335d3 100644
--- a/src/tutor.txt
+++ b/src/tutor.txt
@@ -129,7 +129,8 @@
THE TAG: each pane has a one-line tag: its directory or file path +
builtins. (The mode is the box at the tag's left end, not a word in the
tag.) Every pane shows "New" for an empty temporary file in its column.
- File panes show "Save New Del" by default; Save writes the current file
+ File panes show "Save New Del" by default; an unsaved edit puts `*` after
+ the filename. Save writes the current file
to disk, Del closes the window. Clicking a tag
edits it in insert mode: type straight in, Enter looks / Tab executes
body focuses that same tag in NORMAL mode, parked on the FIRST WORD of
diff --git a/test/e2e_harness.zig b/test/e2e_harness.zig
index 4767871f..6f2f5ef1 100644
--- a/test/e2e_harness.zig
+++ b/test/e2e_harness.zig
@@ -14,6 +14,7 @@ extern "c" fn forkpty(
) c_int;
extern "c" fn execvp(file: [*:0]const u8, argv: [*:null]const ?[*:0]const u8) c_int;
extern "c" fn _exit(status: c_int) noreturn;
+extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int;
/// Monotonic milliseconds; the harness's only clock (std.time lost
/// milliTimestamp in 0.16).
@@ -87,6 +88,14 @@ pub const Harness = struct {
argv[0] = exe;
for (args, 0..) |a, i| argv[i + 1] = a;
+ // The app under test is a fresh outer session even when this harness
+ // itself is running in a pardes pane. Unlike --nested, the boundary
+ // still lets pane shells below that app detect and talk to it, which
+ // nested.snap exercises directly.
+ var boundary_buf: [32:0]u8 = undefined;
+ const boundary = try std.fmt.bufPrintSentinel(&boundary_buf, "{d}", .{@as(u32, @intCast(libc.getpid()))}, 0);
+ if (setenv("PARDES_NESTED_BOUNDARY_PID", boundary, 1) != 0) return error.SetEnvFailed;
+
var master: c_int = undefined;
const ws = posix.winsize{ .row = rows, .col = cols, .xpixel = 0, .ypixel = 0 };
const pid = forkpty(&master, null, null, &ws);
diff --git a/test/snapshots/lsp-rename.golden b/test/snapshots/lsp-rename.golden
index c15092b7..5237554e 100644
--- a/test/snapshots/lsp-rename.golden
+++ b/test/snapshots/lsp-rename.golden
@@ -1,6 +1,6 @@
== snap renamed grid=110x24 cursor=17,9
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/lsp-rename/cwd/rename.zig Save New Del
+| /tmp/pardes-snap/lsp-rename/cwd/rename.zig * Save New Del
| 1 const std = @import("std");
| 2
| 3 fn renamed_helper(x: u32) u32 {
@@ -25,7 +25,7 @@
|
== snap undone grid=110x24 cursor=17,9
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/lsp-rename/cwd/rename.zig Save New Del
+| /tmp/pardes-snap/lsp-rename/cwd/rename.zig * Save New Del
| 1 const std = @import("std");
| 2
| 3 fn helper(x: u32) u32 {
@@ -50,7 +50,7 @@
|
== snap redone grid=110x24 cursor=17,9
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/lsp-rename/cwd/rename.zig Save New Del
+| /tmp/pardes-snap/lsp-rename/cwd/rename.zig * Save New Del
| 1 const std = @import("std");
| 2
| 3 fn renamed_helper(x: u32) u32 {
diff --git a/test/snapshots/lspcomplete.golden b/test/snapshots/lspcomplete.golden
index 8a76f824..d64449e1 100644
--- a/test/snapshots/lspcomplete.golden
+++ b/test/snapshots/lspcomplete.golden
@@ -41,7 +41,7 @@
|
== snap indent grid=120x40 cursor=11,11
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del
+|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig * Save New Del
| 1 const Shade = enum {
| 2 verdigris,
| 3 cinnabar,
@@ -82,7 +82,7 @@
|
== snap commentindent grid=120x40 cursor=19,13
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del
+|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig * Save New Del
| 1 const Shade = enum {
| 2 verdigris,
| 3 cinnabar,
@@ -123,7 +123,7 @@
|
== snap athedot grid=120x40 cursor=16,9
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del
+|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig * Save New Del
| 1 const Shade = enum {
| 2 verdigris,
| 3 cinnabar,
@@ -164,7 +164,7 @@
|
== snap completion grid=120x40 cursor=16,9
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del
+|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig * Save New Del
| 1 const Shade = enum {
| 2 verdigris,
| 3 cinnabar,
@@ -205,7 +205,7 @@
| 2 a.zig:3:5-12 cinnabar cinnabar,
== snap refill grid=120x40 cursor=16,9
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del
+|^ /tmp/pardes-snap/lspcomplete/cwd/a.zig * Save New Del
| 1 const Shade = enum {
| 2 verdigris,
| 3 cinnabar,
@@ -246,7 +246,7 @@
| 2 a.zig:3:5-12 cinnabar cinnabar,
== snap step grid=120x40 cursor=19,3
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/lspcomplete/cwd/a.zig Save New Del
+| /tmp/pardes-snap/lspcomplete/cwd/a.zig * Save New Del
| 1 const Shade = enum {
| 2 verdigris,
| 3 cinnabar,
@@ -287,7 +287,7 @@
| 2 a.zig:3:5-12 cinnabar cinnabar,
== snap txtindent grid=120x40 cursor=15,2
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-|^ /tmp/pardes-snap/lspcomplete/cwd/notes.txt Save New Del
+|^ /tmp/pardes-snap/lspcomplete/cwd/notes.txt * Save New Del
| 1 abc. def ghi
| 2
|
@@ -328,7 +328,7 @@
|
== snap nosemi grid=100x18 cursor=29,8
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-|^ /tmp/pardes-snap/lspcomplete/cwd/c.zig Save New Del
+|^ /tmp/pardes-snap/lspcomplete/cwd/c.zig * Save New Del
| 1 const Shade = enum {
| 2 verdigris,
| 3 cinnabar,
@@ -347,7 +347,7 @@
| 2 c.zig:3:5-12 cinnabar cinnabar,
== snap nobrace grid=100x18 cursor=16,9
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-|^ /tmp/pardes-snap/lspcomplete/cwd/d.zig Save New Del
+|^ /tmp/pardes-snap/lspcomplete/cwd/d.zig * Save New Del
| 1 const Shade = enum {
| 2 verdigris,
| 3 cinnabar,
diff --git a/test/snapshots/lspcompletemoved.golden b/test/snapshots/lspcompletemoved.golden
index bd23bfcd..53489f56 100644
--- a/test/snapshots/lspcompletemoved.golden
+++ b/test/snapshots/lspcompletemoved.golden
@@ -1,6 +1,6 @@
== snap typedthrough grid=120x20 cursor=16,13
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-|^ /tmp/pardes-snap/lspcompletemoved/cwd/a.zig Save New Del
+|^ /tmp/pardes-snap/lspcompletemoved/cwd/a.zig * Save New Del
| 1 const Shade = enum {
| 2 verdigris,
| 3 cinnabar,
diff --git a/test/snapshots/lsprelpath.golden b/test/snapshots/lsprelpath.golden
index 4fd48958..d263a9bd 100644
--- a/test/snapshots/lsprelpath.golden
+++ b/test/snapshots/lsprelpath.golden
@@ -1,6 +1,6 @@
== snap absrow grid=100x34 cursor=16,6
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-|^ /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig Save New Del
+|^ /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig * Save New Del
| 1 const shade = @import("../shade.zig");
| 2
| 3 pub fn pick(s: shade.Shade) u8 {
@@ -35,7 +35,7 @@
| 2 /tmp/pardes-snap/lsprelpath/cwd/shade.zig:3:5-12 cinnabar cinnabar,
== snap abslook grid=100x34 cursor=19,18
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig Save New Del
+| /tmp/pardes-snap/lsprelpath/cwd/sub/parent.zig * Save New Del
| 1 const shade = @import("../shade.zig");
| 2
| 3 pub fn pick(s: shade.Shade) u8 {
@@ -105,7 +105,7 @@
|33: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-74 #bdbdbd,#080808, 75-99 d,#080808,
== snap subrow grid=100x34 cursor=16,6
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-|^ /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig Save New Del
+|^ /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig * Save New Del
| 1 const tint = @import("inner/tint.zig");
| 2
| 3 pub fn hue(t: tint.Tint) u8 {
@@ -140,7 +140,7 @@
| 2 inner/tint.zig:3:5-10 indigo indigo,
== snap sublook grid=100x34 cursor=15,18
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig Save New Del
+| /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig * Save New Del
| 1 const tint = @import("inner/tint.zig");
| 2
| 3 pub fn hue(t: tint.Tint) u8 {
@@ -210,7 +210,7 @@
|33: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-43 #bdbdbd,#080808, 44-99 d,#080808,
== snap clicked grid=100x34 cursor=16,19
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig Save New Del
+| /tmp/pardes-snap/lsprelpath/cwd/sub/subdir.zig * Save New Del
| 1 const tint = @import("inner/tint.zig");
| 2
| 3 pub fn hue(t: tint.Tint) u8 {
diff --git a/test/snapshots/msg.golden b/test/snapshots/msg.golden
index ea8a8ee9..30b20629 100644
--- a/test/snapshots/msg.golden
+++ b/test/snapshots/msg.golden
@@ -166,7 +166,7 @@
|
== snap msgbuiltin grid=70x16 cursor=29,4
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/msg/cwd/a.txt Save New Del
+| /tmp/pardes-snap/msg/cwd/a.txt * Save New Del
| 1 AGAIN
| 2
| 3 Msg hand-written report
diff --git a/test/snapshots/pipe.golden b/test/snapshots/pipe.golden
index 080eb71c..6ca8c9fe 100644
--- a/test/snapshots/pipe.golden
+++ b/test/snapshots/pipe.golden
@@ -13,7 +13,7 @@
| |tr a-z A-Z
== snap filtered grid=180x12 cursor=17,2
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/pipe/cwd/pipe.txt Save New Del
+| /tmp/pardes-snap/pipe/cwd/pipe.txt * Save New Del
| 1 ALPHA BETA
| 2 gamma delta
|
@@ -26,7 +26,7 @@
|
== snap undone grid=180x12 cursor=17,2
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/pipe/cwd/pipe.txt Save New Del
+| /tmp/pardes-snap/pipe/cwd/pipe.txt * Save New Del
| 1 alpha beta
| 2 gamma delta
|
diff --git a/test/snapshots/selregex.golden b/test/snapshots/selregex.golden
index 59dc5d86..90f64f72 100644
--- a/test/snapshots/selregex.golden
+++ b/test/snapshots/selregex.golden
@@ -225,7 +225,7 @@
|23: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-159 d,#080808,
== snap commented grid=160x24 cursor=14,2
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/selregex/cwd/m.zig Save New Del
+| /tmp/pardes-snap/selregex/cwd/m.zig * Save New Del
| 1 // const one = 1;
| 2 // const two = 2;
| 3 // const three = 3;
@@ -250,7 +250,7 @@
|
== snap uncommented grid=160x24 cursor=11,2
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/selregex/cwd/m.zig Save New Del
+| /tmp/pardes-snap/selregex/cwd/m.zig * Save New Del
| 1 const one = 1;
| 2 const two = 2;
| 3 const three = 3;
diff --git a/test/snapshots/tagbottom.golden b/test/snapshots/tagbottom.golden
index 486390e2..8a1222d3 100644
--- a/test/snapshots/tagbottom.golden
+++ b/test/snapshots/tagbottom.golden
@@ -350,7 +350,7 @@
| 7 mmm
| 8 nnn
| 9 ooo
-| /tmp/pardes-snap/tagbottom/cwd/top.txt bottom.txt Save New Del
+| /tmp/pardes-snap/tagbottom/cwd/top.txt * bottom.txt Save New Del
| 1 ZZONE
| 2 ZZTWO
|
diff --git a/test/snapshots/tagcmd.golden b/test/snapshots/tagcmd.golden
index bb341ac8..2f0bf3b3 100644
--- a/test/snapshots/tagcmd.golden
+++ b/test/snapshots/tagcmd.golden
@@ -1,6 +1,6 @@
== snap edited grid=100x30 cursor=13,2
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/tagcmd/cwd/c.txt Save New Del
+| /tmp/pardes-snap/tagcmd/cwd/c.txt * Save New Del
| 1 ZZEDITline 1
| 2 line 2
| 3 line 3
@@ -31,7 +31,7 @@
|
== snap colon grid=100x30 cursor=68,1
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/tagcmd/cwd/c.txt Save New Del
+| /tmp/pardes-snap/tagcmd/cwd/c.txt * Save New Del
| 1 ZZEDITline 1
| 2 line 2
| 3 line 3
@@ -62,7 +62,7 @@
|
== snap word grid=100x30 cursor=72,1
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/tagcmd/cwd/c.txt Save New Del
+| /tmp/pardes-snap/tagcmd/cwd/c.txt * Save New Del
| 1 ZZEDITline 1
| 2 line 2
| 3 line 3
@@ -93,7 +93,7 @@
|
== snap saved grid=100x30 cursor=13,2
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/tagcmd/cwd/c.txt Save New Del
+| /tmp/pardes-snap/tagcmd/cwd/c.txt Save New Del
| 1 ZZEDITline 1
| 2 line 2
| 3 line 3
@@ -133,7 +133,7 @@ line 6
== end file
== snap escaped grid=100x30 cursor=13,2
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/tagcmd/cwd/c.txt Save New Del
+| /tmp/pardes-snap/tagcmd/cwd/c.txt Save New Del
| 1 ZZEDITline 1
| 2 line 2
| 3 line 3
diff --git a/test/snapshots/tagpath.golden b/test/snapshots/tagpath.golden
index f0c7c15c..8c8b16a9 100644
--- a/test/snapshots/tagpath.golden
+++ b/test/snapshots/tagpath.golden
@@ -155,7 +155,7 @@
|29: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-99 d,#080808,
== snap yanked-path grid=100x30 cursor=41,2
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/tagpath/cwd/c.txt Save New Del
+| /tmp/pardes-snap/tagpath/cwd/c.txt * Save New Del
| 1 l/tmp/pardes-snap/tagpath/cwd/c.txtine 1
| 2 line 2
| 3 line 3
@@ -186,7 +186,7 @@
|
== snap drag-cross grid=100x30 cursor=41,1
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/tagpath/cwd/c.txt Save New Del
+| /tmp/pardes-snap/tagpath/cwd/c.txt * Save New Del
| 1 l/tmp/pardes-snap/tagpath/cwd/c.txtine 1
| 2 line 2
| 3 line 3
@@ -217,7 +217,7 @@
|
== style drag-cross
|0: 0-56 #949494,#121212, 57-99 d,#121212,
-|1: 0-1 #ffffff,#626262, 2-10 #949494,#121212, 11-41 #080808,#80a0ff, 42-79 #949494,#121212, 80-99 d,#121212,
+|1: 0-1 #ffffff,#626262, 2-10 #949494,#121212, 11-41 #080808,#80a0ff, 42-81 #949494,#121212, 82-99 d,#121212,
|2: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-46 #bdbdbd,#080808, 47-99 d,#080808,
|3: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808,
|4: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-12 #bdbdbd,#080808, 13-99 d,#080808,
@@ -248,7 +248,7 @@
|29: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-99 d,#080808,
== snap path-inert grid=100x30 cursor=11,1
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/tagpath/cwd/c.txt Save New Del
+| /tmp/pardes-snap/tagpath/cwd/c.txt * Save New Del
| 1 l/tmp/pardes-snap/tagpath/cwd/c.txtine 1
| 2 line 2
| 3 line 3
diff --git a/test/snapshots/yankpaste.golden b/test/snapshots/yankpaste.golden
index 612c81f0..b7757431 100644
--- a/test/snapshots/yankpaste.golden
+++ b/test/snapshots/yankpaste.golden
@@ -1,6 +1,6 @@
== snap linewise-below grid=100x30 cursor=13,5
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/yankpaste/cwd/f.txt Save New Del
+| /tmp/pardes-snap/yankpaste/cwd/f.txt * Save New Del
| 1 line 1
| 2 line 2
| 3 line 3
@@ -31,7 +31,7 @@
|
== snap linewise-above grid=100x30 cursor=13,6
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/yankpaste/cwd/f.txt Save New Del
+| /tmp/pardes-snap/yankpaste/cwd/f.txt * Save New Del
| 1 line 1
| 2 line 2
| 3 line 3
@@ -62,7 +62,7 @@
|
== snap charwise-inline grid=100x30 cursor=10,2
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/yankpaste/cwd/f.txt Save New Del
+| /tmp/pardes-snap/yankpaste/cwd/f.txt * Save New Del
| 1 liline 1
| 2 line 2
| 3 line 3
@@ -93,7 +93,7 @@
|
== snap blankline grid=100x30 cursor=7,4
|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill
-| /tmp/pardes-snap/yankpaste/cwd/f.txt Save New Del
+| /tmp/pardes-snap/yankpaste/cwd/f.txt * Save New Del
| 1 liline 1
| 2
| 3