summaryrefslogtreecommitdiff
path: root/src/Pipe.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Pipe.zig')
-rw-r--r--src/Pipe.zig96
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 });