summaryrefslogtreecommitdiff
path: root/src/output_pane.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/output_pane.zig')
-rw-r--r--src/output_pane.zig71
1 files changed, 71 insertions, 0 deletions
diff --git a/src/output_pane.zig b/src/output_pane.zig
index 45eb0931..8e08285f 100644
--- a/src/output_pane.zig
+++ b/src/output_pane.zig
@@ -20,6 +20,7 @@ const Pardes = pardes.Pardes;
const Pane = pardes.Pane;
const File = pardes.File;
const file_pane = @import("file_pane.zig");
+const modal = @import("modal.zig");
const builtins = @import("builtins.zig");
const Builtin = builtins.Builtin();
const config = @import("config.zig");
@@ -110,6 +111,9 @@ pub fn traits(o: Origin) Traits {
.search => .{ .name = config.search_buffer, .steps = true },
.cmd => |b| switch (b) {
.Help => .{ .name = config.help_buffer },
+ // the focus history, one location per row: not a search, but the
+ // same kind of list, so n/N walk it and a row is a look target
+ .Jumplist => .{ .name = config.jumps_buffer, .steps = true },
// Find (file names) and Grep (file contents) both list locations;
// no other builtin opens a buffer, and the day one does it lands
// here rather than in a call site.
@@ -188,6 +192,73 @@ pub fn open(p: *Pardes, id: usize, dir: []const u8, from: Origin, arg: []const u
return pane;
}
+/// The Jumplist builtin: the focus history (Pardes.jumps) written out as text,
+/// one row per location, oldest first — the same `location text` shape every
+/// results buffer here has, which is what buys n/N stepping and Look-on-a-row
+/// for nothing: the leading word is an ordinary look target, and the ordinary
+/// look path is what goes there.
+///
+/// A RENDERING, never a second list. The rows are spelled from the stack at
+/// the moment you ask and go stale the moment you jump, exactly like a search
+/// result — which is also why this opens a fresh buffer per press instead of
+/// refreshing one the way Help does: Help is a document, this is a snapshot.
+///
+/// How a location is spelled is the rule runSearch already follows: a REAL
+/// file names itself (its path is absolute, so the row resolves from any
+/// pane's directory), and everything else — a terminal, an output buffer, an
+/// image — has no file to point at and gets `@pN`. The trailing text is the
+/// content line for anything holding text, else the pane's directory: enough
+/// to recognise the place without opening it.
+pub fn openJumps(p: *Pardes, id: usize) void {
+ const pane = p.panes[id] orelse return;
+ const arena = p.scratch.allocator();
+ var out: std.ArrayList(u8) = .empty;
+ for (p.jumps[0..p.njumps]) |j| {
+ const jp = p.panes[j.pane] orelse continue;
+ var idbuf: [16]u8 = undefined;
+ const has_path = if (jp.file) |f| f.output == null else false;
+ const loc: []const u8 = if (has_path)
+ jp.file.?.path
+ else
+ std.fmt.bufPrint(&idbuf, config.pane_addr ++ "{d}", .{j.pane}) catch continue;
+ const what: []const u8 = if (jp.file) |f|
+ std.mem.trim(u8, modal.lineSlice(f.content, j.line -| 1), " \t\r")
+ else if (jp.image) |iv|
+ iv.path
+ else
+ jp.cwdSlice();
+ // one line of a file can be the whole file: cut it, but never mid
+ // codepoint — a partial UTF-8 sequence reaches the renderer as a row
+ // and there is nothing sane for it to draw (grepText cuts the same way)
+ var cut = @min(what.len, 120);
+ while (cut > 0 and cut < what.len and what[cut] & 0xc0 == 0x80) cut -= 1;
+ // line 0 is a place with no spot in it (a shell whose cursor is still
+ // the program's): it is written WITHOUT the suffix, which is the same
+ // thing a bare path has always meant to a look.
+ const row = if (j.line == 0)
+ std.fmt.allocPrint(arena, "{s} {s}\n", .{ loc, what[0..cut] }) catch return
+ else
+ std.fmt.allocPrint(arena, "{s}:{d}:{d} {s}\n", .{ loc, j.line, j.col, what[0..cut] }) catch return;
+ out.appendSlice(arena, row) catch return;
+ }
+ const content = p.gpa.dupe(u8, out.items) catch return;
+ const dir = if (pane.file) |f| (std.fs.path.dirname(f.path) orelse "/") else pane.cwdSlice();
+ const free = p.freeSlot() orelse {
+ p.gpa.free(content);
+ return;
+ };
+ const np = open(p, free, dir, .{ .cmd = .Jumplist }, "", content) catch {
+ p.gpa.free(content);
+ return;
+ };
+ p.placeDoc(id, free, np);
+ // focus stays with the pane that asked, exactly as it does after a search:
+ // n/N are read there, and they step the buffer they just armed.
+ p.active = id;
+ pane.search_pane = free;
+ pane.search_row = null;
+}
+
/// The Help builtin: every builtin reachable from `prefix` (empty = all
/// of them) with the keys that run it, written into an output buffer
/// (acme's +Errors) — ordinary text, so the names in it are LIVE: middle-