From d5743f2a58a03539d3c34bee3b2ad18b96b2da55 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 10:44:30 -0300 Subject: A refusal over unsaved panes keeps its list in +Unsaved and shows a short notice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit's refusal (and Restore's, Del's, Delcol's, get's) put its full text on a notice that went: a long list of names, gone before it was read. The list now goes into one +Unsaved pane, filled again by each refusal, a row a pane (`: Modified`) and the `Exit again to discard` line; the notice is short, `3 unsaved panes — Exit again to discard`. The 9P `unsaved` records and the write's err are as they were. Co-Authored-By: Claude Opus 5.5 --- src/Output.zig | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) (limited to 'src/Output.zig') diff --git a/src/Output.zig b/src/Output.zig index a75718bf..c6012d06 100644 --- a/src/Output.zig +++ b/src/Output.zig @@ -29,6 +29,9 @@ pub const Origin = union(enum) { search, errors, pdf_links, + /// The panes a refusal (Exit, Restore, Del, Delcol, get) is over, kept + /// in +Unsaved rather than a notice that goes. + unsaved, }; pub const max_arg = dump.max_origin_arg; @@ -183,6 +186,7 @@ pub fn traits(o: Origin) Traits { .search => .{ .name = config.search_buffer, .steps = true, .locations = true }, .errors => .{ .name = config.errors_buffer, .doc = true }, .pdf_links => .{ .name = config.pdf_links_buffer, .steps = true, .locations = true }, + .unsaved => .{ .name = config.unsaved_buffer, .doc = true }, .cmd => |b| builtins.registry.outputTraits(b) orelse unreachable, .query => |k| switch (k) { .hover => .{ .name = config.hover_buffer }, @@ -242,6 +246,7 @@ pub fn word(o: Origin) []const u8 { .search => "/", .errors => config.errors_buffer, .pdf_links => config.pdf_links_buffer, + .unsaved => config.unsaved_buffer, }; } @@ -250,6 +255,7 @@ pub fn fromWord(w: []const u8) ?Origin { if (std.mem.eql(u8, w, "/")) return .search; if (std.mem.eql(u8, w, config.errors_buffer)) return .errors; if (std.mem.eql(u8, w, config.pdf_links_buffer)) return .pdf_links; + if (std.mem.eql(u8, w, config.unsaved_buffer)) return .unsaved; // A dump from before the rename names them by their old words. if (std.mem.eql(u8, w, "ThemeSel")) return .{ .cmd = .Themes }; if (builtins.capabilities.font_picker) if (std.mem.eql(u8, w, "FontSel")) return .{ .cmd = .Fonts }; @@ -315,7 +321,7 @@ fn sortResults(arena: std.mem.Allocator, from: Origin, content: []u8, anchor: ?u => {}, else => return anchor, }, - .errors, .pdf_links => return anchor, + .errors, .pdf_links, .unsaved => return anchor, } if (content.len == 0) return anchor; const path_only = std.meta.eql(from, Origin{ .cmd = .Find }); @@ -559,6 +565,27 @@ pub fn recentText(p: *Pardes, w: *std.Io.Writer, look_rows: bool) !void { } } +/// A refusal's list of unsaved panes, kept in the one +Unsaved: filled +/// again when it is there (as +Search is), else opened below `id`. Best +/// effort: with no room for it, the notice and the log still say it. +pub fn openUnsaved(p: *Pardes, id: usize, content: []u8) void { + for (p.panes) |slot| { + const rp = slot orelse continue; + const rf = if (rp.file) |*f| f else continue; + const o = rf.output orelse continue; + if (o.from != .unsaved) continue; + File.setContent(p, rf, content); + resetBody(p, rp); + return; + } + const pane = p.panes[id] orelse return p.gpa.free(content); + const dir = if (pane.file) |f| (std.fs.path.dirname(f.path) orelse "/") else pane.cwdSlice(); + const free = p.freeSlot() orelse return p.gpa.free(content); + _ = open(p, free, dir, .unsaved, "", content) catch return; + exec.placeNew(p, id, id, free, .doc); + p.active = id; +} + /// Recent: the one +Recent, filled again when it is there (as Find and /// Grep's +Search is), else opened below the asking pane. pub fn openRecent(p: *Pardes, id: usize) !void { -- cgit v1.3