summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig57
1 files changed, 54 insertions, 3 deletions
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,