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.zig54
1 files changed, 49 insertions, 5 deletions
diff --git a/src/output_pane.zig b/src/output_pane.zig
index 8e08285f..a0583506 100644
--- a/src/output_pane.zig
+++ b/src/output_pane.zig
@@ -80,6 +80,19 @@ pub const Traits = struct {
/// look path resolves, so the buffer IS helix's picker. Prose (a hover
/// blurb, a rename diff) has nowhere to step to.
steps: bool = false,
+ /// ...and what a step DOES with the row it lands on. Off, the row is a
+ /// LOCATION and its leading word is LOOKED. On, the row is a COMMAND LINE
+ /// and the whole of it is EXECUTED — so walking the list runs each row in
+ /// turn, which is what makes a picker over things that take effect
+ /// immediately (ThemeSel) a plain list of the words you would have typed.
+ /// Both go through the ordinary builtin (config.look_cmd / exec_cmd), so a
+ /// row does exactly what the matching mouse button on it would.
+ ///
+ /// Nothing about this is output-pane specific, which is why it is a column
+ /// here and not a branch in searchStep: `file_row` answers it too, so a
+ /// file pane whose lines happen to be commands is one word away from
+ /// behaving the same.
+ executes: bool = false,
/// an answer of exactly ONE row jumps straight there instead of opening
/// this buffer at all — helix: the gotos jump on a single location and
/// show a picker on several, a symbol list is always a picker.
@@ -114,6 +127,10 @@ pub fn traits(o: Origin) Traits {
// 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 },
+ // the theme ring, one `Theme <name>` per row. The only buffer whose
+ // rows are COMMANDS rather than locations, so n/N execute them:
+ // walking the list wears each theme, and stopping is picking one.
+ .ThemeSel => .{ .name = config.themes_buffer, .steps = true, .executes = 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.
@@ -210,7 +227,6 @@ pub fn open(p: *Pardes, id: usize, dir: []const u8, from: Origin, arg: []const u
/// 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| {
@@ -241,19 +257,47 @@ pub fn openJumps(p: *Pardes, id: usize) void {
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;
+ openStepped(p, id, .{ .cmd = .Jumplist }, out.items);
+}
+
+/// The ThemeSel builtin: the theme ring written out as one `Theme <name>` row
+/// per theme — the ordinary builtin with its argument, exactly the line you
+/// would type — into a buffer whose `executes` trait makes n/N RUN each row
+/// rather than look it. So walking the list is trying the themes on, and
+/// stopping on one is choosing it; there is no picker mode, no preview state
+/// and nothing to commit or cancel, because every step already did the thing.
+///
+/// The command's own name comes from the builtin rather than a literal: the
+/// word is derived from the struct in exactly one place (builtins.word), and a
+/// rename there must not leave rows here that name something that is gone.
+pub fn openThemes(p: *Pardes, id: usize) void {
+ const arena = p.scratch.allocator();
+ var out: std.ArrayList(u8) = .empty;
+ for (pardes.themes) |t| {
+ out.print(arena, comptime builtins.word(builtins.Theme) ++ " {s}\n", .{t.name}) catch return;
+ }
+ openStepped(p, id, .{ .cmd = .ThemeSel }, out.items);
+}
+
+/// Open a buffer n/N will walk, and arm them on it: the shared tail of every
+/// builtin that answers with a list. `text` is borrowed (the callers build it
+/// in the scratch arena) and copied into a gpa buffer the pane adopts.
+///
+/// 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.
+fn openStepped(p: *Pardes, id: usize, from: Origin, text: []const u8) void {
+ const pane = p.panes[id] orelse return;
+ const content = p.gpa.dupe(u8, text) 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 {
+ const np = open(p, free, dir, from, "", 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;