summaryrefslogtreecommitdiff
path: root/src/Output.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Output.zig')
-rw-r--r--src/Output.zig104
1 files changed, 98 insertions, 6 deletions
diff --git a/src/Output.zig b/src/Output.zig
index 553dcc95..3b8e6a7a 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,10 @@ 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 };
if (std.meta.stringToEnum(Builtin, w)) |b|
if (builtins.registry.outputTraits(b) != null) return .{ .cmd = b };
if (std.meta.stringToEnum(lsp.Kind, w)) |k| return .{ .query = k };
@@ -312,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 });
@@ -406,6 +415,16 @@ pub fn open(p: *Pardes, id: usize, dir: []const u8, from: Origin, arg: []const u
return pane;
}
+/// Find and Grep share one +Search a directory, each run replacing what
+/// the last showed, as acme reuses a directory's +Errors; a pane's own
+/// search (LookWord list) keeps a pane a pattern.
+fn fileSearch(from: Origin) bool {
+ return switch (from) {
+ .cmd => |c| c == .Find or c == .Grep,
+ else => false,
+ };
+}
+
pub fn fillResults(p: *Pardes, id: usize, dir: []const u8, from: Origin, arg: []const u8, content: []u8, initial_anchor: ?usize) !void {
var result_content = content;
errdefer p.gpa.free(result_content);
@@ -448,9 +467,12 @@ pub fn fillResults(p: *Pardes, id: usize, dir: []const u8, from: Origin, arg: []
const rp = slot orelse continue;
const rf = if (rp.file) |*f| f else continue;
const o = if (rf.output) |*x| x else continue;
- if (!std.meta.eql(o.from, from)) continue;
- if (by_arg and !std.mem.eql(u8, o.arg(), arg)) continue;
+ const shared = fileSearch(from) and fileSearch(o.from);
+ if (!shared and !std.meta.eql(o.from, from)) continue;
+ if (!shared and by_arg and !std.mem.eql(u8, o.arg(), arg)) continue;
if (!std.mem.eql(u8, std.fs.path.dirname(rf.path) orelse "", dir)) continue;
+ o.from = from;
+ p.fs.refilled = rp.serial;
try setArg(o, arg);
if (std.mem.eql(u8, rf.content, result_content)) {
p.gpa.free(result_content);
@@ -492,6 +514,12 @@ pub fn openJumps(p: *Pardes, id: usize) !void {
var out: std.Io.Writer.Allocating = .init(p.gpa);
errdefer out.deinit();
for (p.jumps[0..p.njumps]) |j| {
+ // A closed file's entry: marked, and a look at it opens it again.
+ if (j.serial == 0) {
+ const e = p.recentByHash(j.path_hash) orelse continue;
+ try out.writer.print("{s}:{d}:{d} (closed)\n", .{ e.path, @max(1, j.line), @max(1, j.col) });
+ continue;
+ }
const jp = p.panes[j.pane] orelse continue;
var idbuf: [16]u8 = undefined;
const pdf_path: ?[]const u8 = if (comptime pardes.pdf_enabled) jp.pdfPath() else null;
@@ -519,6 +547,70 @@ pub fn openJumps(p: *Pardes, id: usize) !void {
try openStepped(p, id, .{ .cmd = .Jumplist }, content);
}
+/// The recent list as rows: `<path>:<line>:<col> open|closed`, most recent
+/// first; a look at a row opens the file there, as a +Jumps row does.
+pub fn recentText(p: *Pardes, w: *std.Io.Writer, look_rows: bool) !void {
+ for (p.recent.entries.items) |e| {
+ var is_open = false;
+ for (p.panes) |slot| if (slot) |pane| if (pane.file) |f| if (f.output == null and std.mem.eql(u8, f.path, e.path)) {
+ is_open = true;
+ };
+ const state: []const u8 = if (is_open) "open" else "closed";
+ if (!look_rows)
+ try w.print("{s} {s}\n", .{ state, e.path })
+ else if (e.line == 0)
+ try w.print("{s} {s}\n", .{ e.path, state })
+ else
+ try w.print("{s}:{d}:{d} {s}\n", .{ e.path, e.line, e.col, state });
+ }
+}
+
+/// 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 {
+ var out: std.Io.Writer.Allocating = .init(p.gpa);
+ errdefer out.deinit();
+ try recentText(p, &out.writer, true);
+ const content = try out.toOwnedSlice();
+ for (p.panes, 0..) |slot, i| {
+ const rp = slot orelse continue;
+ const rf = if (rp.file) |*f| f else continue;
+ const o = rf.output orelse continue;
+ if (!std.meta.eql(o.from, Origin{ .cmd = .Recent })) continue;
+ File.setContent(p, rf, content);
+ resetBody(p, rp);
+ p.fs.refilled = rp.serial;
+ if (p.panes[id]) |pane| {
+ pane.search_pane = i;
+ pane.search_row = null;
+ }
+ look.armLookWalk(p, i);
+ return;
+ }
+ try openStepped(p, id, .{ .cmd = .Recent }, content);
+}
+
pub fn openThemes(p: *Pardes, id: usize) !void {
var out: std.Io.Writer.Allocating = .init(p.gpa);
errdefer out.deinit();
@@ -528,13 +620,13 @@ pub fn openThemes(p: *Pardes, id: usize) !void {
try out.writer.print(comptime config.Runtime.findAction(.theme).?.word ++ " {s}\n", .{t.name});
}
const content = try out.toOwnedSlice();
- try openStepped(p, id, .{ .cmd = .ThemeSel }, content);
+ try openStepped(p, id, .{ .cmd = .Themes }, content);
}
pub fn isWalkRow(file: *const File.State, row: usize, line: []const u8) bool {
const state = file.output orelse return true;
if (usesLocationsConfig(state.from)) return isResultRow(file, row);
- if (!std.meta.eql(state.from, Origin{ .cmd = .ThemeSel })) return true;
+ if (!std.meta.eql(state.from, Origin{ .cmd = .Themes })) return true;
// Section labels are for reading, not stops in the command walk.
return std.mem.startsWith(u8, std.mem.trimStart(u8, line, " \t"), comptime config.Runtime.findAction(.theme).?.word ++ " ");
}
@@ -548,7 +640,7 @@ pub fn openFonts(p: *Pardes, id: usize) !void {
for (font_list) |f|
try out.writer.print(comptime config.Runtime.findAction(.font).?.word ++ " {s}\n", .{f.name});
const content = try out.toOwnedSlice();
- try openStepped(p, id, .{ .cmd = .FontSel }, content);
+ try openStepped(p, id, .{ .cmd = .Fonts }, content);
}
}