diff options
Diffstat (limited to 'src/pardes.zig')
| -rw-r--r-- | src/pardes.zig | 57 |
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, |
