diff options
Diffstat (limited to 'src/Pipe.zig')
| -rw-r--r-- | src/Pipe.zig | 96 |
1 files changed, 73 insertions, 23 deletions
diff --git a/src/Pipe.zig b/src/Pipe.zig index 4cabfd88..1ad7b826 100644 --- a/src/Pipe.zig +++ b/src/Pipe.zig @@ -9,6 +9,7 @@ const config = @import("config.zig"); const selection_pipe = @import("selection_pipe.zig"); const edit = @import("edit.zig"); const Pane = panes.Pane; +const Text = panes.Text; const Key = pardes.Key; const Pardes = pardes.Pardes; const Pipe = @This(); @@ -41,7 +42,14 @@ pub const PendingPipe = struct { id: u32, pane: usize, serial: u32, + /// The text piped: the pane's body or tag, or a header (`column` + /// names which). + what: Text.What, + column: usize, + /// What the answer is checked against before it is applied: a body's + /// file revision, any other text's contents. revision: u32, + sum: u64, command: []u8, cwd: []u8, inputs: []selection_pipe.Input, @@ -71,8 +79,12 @@ pub const PendingPipe = struct { pub fn submitPipe(p: *Pardes, id: usize) void { const pane = p.panes[id] orelse return; - const f = pane.file orelse return; - if (!panes.Output.fileTraits(f.output).saves) return; + const t = p.promptTarget(pane) orelse return; + if (t.what == .body) { + const f = pane.file orelse return; + if (!panes.Output.fileTraits(f.output).saves) return; + } + const content = edit.flatSurface(p, t) catch return; const how = switch (pane.prompt) { .pipe => |pipe| pipe, else => return, @@ -84,7 +96,7 @@ pub fn submitPipe(p: *Pardes, id: usize) void { if (command.len == 0) return; var ranges: [panes.Text.max_selections]modal.Selection = undefined; - const got = pane.body.ranges(f.content, 0, &ranges); + const got = t.ranges(content, 0, &ranges); // `!`/`A-!` take no stdin and run ONCE — see `PendingPipe.nranges`. const ninputs = if (how.pipes()) got.n else 1; const inputs = p.gpa.alloc(selection_pipe.Input, ninputs) catch return; @@ -93,8 +105,8 @@ pub fn submitPipe(p: *Pardes, id: usize) void { const bytes = if (how.pipes()) bytes: { const lo = @min(range.anchor, range.head); const hi = @max(range.anchor, range.head); - if (hi > f.content.len) break; - break :bytes f.content[lo..hi]; + if (hi > content.len) break; + break :bytes content[lo..hi]; } else ""; const copy = p.gpa.dupe(u8, bytes) catch break; inputs[i] = .{ .bytes = copy }; @@ -123,13 +135,16 @@ pub fn submitPipe(p: *Pardes, id: usize) void { .id = p.pipe.seq, .pane = id, .serial = pane.serial, - .revision = f.revision, + .what = t.what, + .column = p.header_column orelse 0, + .revision = if (pane.file) |f| f.revision else 0, + .sum = std.hash.Wyhash.hash(0, content), .command = command_copy, .cwd = cwd_copy, .inputs = inputs, .ranges = ranges, .primary = @intCast(got.pri), - .explicit = (pane.body.vsel.active and pane.body.vsel.explicit) or pane.body.msel.active, + .explicit = (t.vsel.active and t.vsel.explicit) or t.msel.active, .how = how, .nranges = @intCast(got.n), }; @@ -229,10 +244,20 @@ pub fn pipeResponse( const pane = p.panes[wait.pane] orelse return; if (pane.serial != wait.serial) return; - const f = if (pane.file) |*file| file else return; - if (!panes.Output.fileTraits(f.output).saves) return; - if (f.revision != wait.revision) - return p.reportError(wait.pane, "pipe", error.FileChangedWhileFiltering); + const t: *Text = switch (wait.what) { + .body => &pane.body, + .tag => &pane.tag, + .column => if (wait.column < p.ncol) &p.col_tags[wait.column] else return, + .workspace => &p.global_tag, + .input => return, + }; + const content = edit.flatSurface(p, t) catch return; + const changed = if (t.what == .body) changed: { + const f = pane.file orelse return; + if (!panes.Output.fileTraits(f.output).saves) return; + break :changed f.revision != wait.revision; + } else std.hash.Wyhash.hash(0, content) != wait.sum; + if (changed) return p.reportError(wait.pane, "pipe", error.FileChangedWhileFiltering); if (wait.how == .ignore) return; @@ -241,8 +266,8 @@ pub fn pipeResponse( var removed: usize = 0; var previous_end: usize = 0; for (0..n) |i| { - const cut = pipeCut(&wait, f.content, i) orelse return; - const output = pipeOutput(&wait, outputs, f.content, i); + const cut = pipeCut(&wait, content, i) orelse return; + const output = pipeOutput(&wait, outputs, content, i); if (output.len > selection_pipe.max_stdout_bytes) return; total_output = std.math.add(usize, total_output, output.len) catch return; if (total_output > selection_pipe.max_total_stdout_bytes) return; @@ -250,7 +275,7 @@ pub fn pipeResponse( removed = std.math.add(usize, removed, cut.hi - cut.lo) catch return; previous_end = cut.hi; } - const kept = f.content.len - removed; + const kept = content.len - removed; const final_len = std.math.add(usize, kept, total_output) catch return; const replacement = p.gpa.alloc(u8, final_len) catch return; @@ -259,9 +284,9 @@ pub fn pipeResponse( var write_at: usize = 0; for (0..n) |i| { const range = wait.ranges[i]; - const cut = pipeCut(&wait, f.content, i).?; - const output = pipeOutput(&wait, outputs, f.content, i); - @memcpy(replacement[write_at .. write_at + (cut.lo - read_at)], f.content[read_at..cut.lo]); + const cut = pipeCut(&wait, content, i).?; + const output = pipeOutput(&wait, outputs, content, i); + @memcpy(replacement[write_at .. write_at + (cut.lo - read_at)], content[read_at..cut.lo]); write_at += cut.lo - read_at; const out_start = write_at; @memcpy(replacement[write_at .. write_at + output.len], output); @@ -273,14 +298,14 @@ pub fn pipeResponse( .{ .anchor = out_start, .head = out_end }; read_at = cut.hi; } - @memcpy(replacement[write_at..], f.content[read_at..]); + @memcpy(replacement[write_at..], content[read_at..]); // One async request is one history transaction, even at 64 cursors. - edit.pushUndo(p, &pane.body); - panes.File.setContent(p, f, replacement); - pane.body.setRanges(f.content, new_ranges[0..n], &.{}, wait.primary, wait.explicit); - pane.body.select = false; - pane.ensureCursorVisible(); + edit.pushUndo(p, t); + edit.setEditText(p, t, replacement); + t.setRanges(edit.flatSurface(p, t) catch return, new_ranges[0..n], &.{}, wait.primary, wait.explicit); + t.select = false; + t.show(); } test "selection pipe prompt submits exact request and Escape cancels" { @@ -316,6 +341,31 @@ test "selection pipe prompt submits exact request and Escape cancels" { try std.testing.expect(nextPipeEffect(p) == null); } +test "| in a tag pipes the tag's own text, and undo there brings it back" { + const gpa = std.testing.allocator; + const p = try Pardes.init(gpa, .{ .tty_only = true }); + defer p.deinit(); + while (p.nextEffect()) |_| {} + const pane = try p.setTestFile("body\n"); + p.update(.{ .key = .{ .cp = ':' } }); // the tag, on Save + p.update(.{ .key = .{ .cp = 'e' } }); // Save selected + p.update(.{ .key = .{ .cp = '|' } }); + try std.testing.expect(pane.focus == .tag and pane.prompt == .pipe and pane.prompt_for == .tag); + p.update(.{ .key = .{ .cp = 't', .text = "tr a-z A-Z" } }); + p.update(.{ .key = .{ .cp = Key.enter } }); + try std.testing.expect(pane.focus == .tag and pane.prompt == .none); + const id = nextPipeEffect(p) orelse return error.MissingPipeEffect; + const request = p.pipe.pipeRequest(id) orelse return error.MissingPipeRequest; + try std.testing.expectEqual(@as(usize, 1), request.inputs.len); + try std.testing.expectEqualSlices(u8, "Save", request.inputs[0].bytes); + const outputs: []const []const u8 = &.{"SAVE"}; + p.update(.{ .pipe_resp = .{ .id = id, .success = true, .outputs = outputs } }); + try std.testing.expect(std.mem.startsWith(u8, pardes.tagline.curTail(pane), " SAVE ")); + try std.testing.expectEqualStrings("body\n", pane.file.?.content); + p.update(.{ .key = .{ .cp = 'u' } }); + try std.testing.expect(std.mem.startsWith(u8, pardes.tagline.curTail(pane), " Save ")); +} + test "selection pipe replaces all ranges atomically and undo restores them" { const gpa = std.testing.allocator; const p = try Pardes.init(gpa, .{ .tty_only = true }); |
