From 95a3a1bb822e583988bfe0f312d0ef219b27a454 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 27 Sep 2026 23:06:11 -0300 Subject: Give a pane's body its own Text holding the cursor, selections, mode and undo acme keeps what edits a text in its Text (dat.h:171-190) and the window holds a body and a tag of that type. The cursor, the selections, the modal state and the edit-buffer undo move off Pane into Text.zig, Pane holds them as its body, and the edit and normal-mode operations take the Text they edit. Nothing changes in behaviour; this is the step that lets the tag become a second Text. Co-Authored-By: Claude Opus 5.5 --- src/Pipe.zig | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) (limited to 'src/Pipe.zig') diff --git a/src/Pipe.zig b/src/Pipe.zig index c01098ff..c2b49f31 100644 --- a/src/Pipe.zig +++ b/src/Pipe.zig @@ -45,7 +45,7 @@ pub const PendingPipe = struct { command: []u8, cwd: []u8, inputs: []selection_pipe.Input, - ranges: [Pane.max_selections]modal.Selection, + ranges: [panes.Text.max_selections]modal.Selection, primary: u8, explicit: bool, how: modal.Normal.PipeBehavior, @@ -84,8 +84,8 @@ pub fn submitPipe(p: *Pardes, id: usize) void { const command = armed[marker.len..]; if (command.len == 0) return; - var ranges: [Pane.max_selections]modal.Selection = undefined; - const got = pane.ranges(f.content, 0, &ranges); + var ranges: [panes.Text.max_selections]modal.Selection = undefined; + const got = pane.body.ranges(f.content, 0, &ranges); // `!`/`A-!` take no stdin and run ONCE — see `PendingPipe.nranges`. const ninputs = if (prompt.how.pipes()) got.n else 1; const inputs = p.gpa.alloc(selection_pipe.Input, ninputs) catch return; @@ -130,7 +130,7 @@ pub fn submitPipe(p: *Pardes, id: usize) void { .inputs = inputs, .ranges = ranges, .primary = @intCast(got.pri), - .explicit = (pane.vsel.active and pane.vsel.explicit) or pane.msel.active, + .explicit = (pane.body.vsel.active and pane.body.vsel.explicit) or pane.body.msel.active, .how = prompt.how, .nranges = @intCast(got.n), }; @@ -255,7 +255,7 @@ pub fn pipeResponse( const final_len = std.math.add(usize, kept, total_output) catch return; const replacement = p.gpa.alloc(u8, final_len) catch return; - var new_ranges: [Pane.max_selections]modal.Selection = undefined; + var new_ranges: [panes.Text.max_selections]modal.Selection = undefined; var read_at: usize = 0; var write_at: usize = 0; for (0..n) |i| { @@ -277,10 +277,10 @@ pub fn pipeResponse( @memcpy(replacement[write_at..], f.content[read_at..]); // One async request is one history transaction, even at 64 cursors. - edit.pushUndo(p, pane); + edit.pushUndo(p, &pane.body); panes.File.setContent(p, f, replacement); - pane.setRanges(f.content, new_ranges[0..n], &.{}, wait.primary, wait.explicit); - pane.select = false; + pane.body.setRanges(f.content, new_ranges[0..n], &.{}, wait.primary, wait.explicit); + pane.body.select = false; pane.ensureCursorVisible(); } @@ -290,9 +290,9 @@ test "selection pipe prompt submits exact request and Escape cancels" { defer p.deinit(); while (p.nextEffect()) |_| {} const pane = try p.setTestFile("one\ntwo\n"); - pane.cur_row = 0; - pane.cur_col = 2; - pane.vsel = .{ .active = true, .row = 0, .col = 0, .explicit = true }; + pane.body.cur_row = 0; + pane.body.cur_col = 2; + pane.body.vsel = .{ .active = true, .row = 0, .col = 0, .explicit = true }; p.update(.{ .key = .{ .cp = '|' } }); try std.testing.expect(pane.tag_edit and pane.prompt == .pipe); @@ -327,7 +327,7 @@ test "selection pipe replaces all ranges atomically and undo restores them" { .{ .anchor = 0, .head = 2 }, .{ .anchor = 6, .head = 8 }, }; - pane.setRanges(pane.file.?.content, &ranges, &.{}, 0, true); + pane.body.setRanges(pane.file.?.content, &ranges, &.{}, 0, true); p.update(.{ .key = .{ .cp = '|' } }); p.update(.{ .key = .{ .cp = 'c', .text = "cat" } }); @@ -345,9 +345,9 @@ test "selection pipe replaces all ranges atomically and undo restores them" { p.update(.{ .pipe_resp = .{ .id = id, .success = true, .outputs = outputs } }); try std.testing.expectEqualSlices(u8, "AA bb \n", pane.file.?.content); try std.testing.expectEqual(@as(usize, 1), pane.file.?.history.undo_len); - try std.testing.expectEqual(@as(u8, 1), pane.nsel); - try std.testing.expectEqual(@as(i32, 1), pane.cur_col); - try std.testing.expectEqual(@as(i32, 6), pane.sels[0].col); + try std.testing.expectEqual(@as(u8, 1), pane.body.nsel); + try std.testing.expectEqual(@as(i32, 1), pane.body.cur_col); + try std.testing.expectEqual(@as(i32, 6), pane.body.sels[0].col); try std.testing.expect(!denied.has_induced_failure); p.update(.{ .key = .{ .cp = 'u' } }); @@ -378,7 +378,7 @@ test "the four shell behaviours put their output where helix puts it" { defer p.deinit(); while (p.nextEffect()) |_| {} const pane = try p.setTestFile("aa bb cc\n"); - pane.setRanges(pane.file.?.content, &.{.{ .anchor = 3, .head = 5 }}, &.{}, 0, true); + pane.body.setRanges(pane.file.?.content, &.{.{ .anchor = 3, .head = 5 }}, &.{}, 0, true); p.update(.{ .key = case.key }); for ("cmd") |c| p.update(.{ .key = .{ .cp = c, .text = &.{c} } }); @@ -399,7 +399,7 @@ test "a command with no stdin runs once and every cursor gets that one answer" { .{ .anchor = 0, .head = 2 }, .{ .anchor = 6, .head = 8 }, }; - pane.setRanges(pane.file.?.content, &ranges, &.{}, 0, true); + pane.body.setRanges(pane.file.?.content, &ranges, &.{}, 0, true); p.update(.{ .key = .{ .cp = '!' } }); // insert-output: no stdin for ("date") |c| p.update(.{ .key = .{ .cp = c, .text = &.{c} } }); @@ -421,8 +421,8 @@ test "selection pipe failure and stale completion never mutate the file" { defer p.deinit(); while (p.nextEffect()) |_| {} const pane = try p.setTestFile("abc\n"); - pane.cur_col = 2; - pane.vsel = .{ .active = true, .row = 0, .col = 0, .explicit = true }; + pane.body.cur_col = 2; + pane.body.vsel = .{ .active = true, .row = 0, .col = 0, .explicit = true }; p.update(.{ .key = .{ .cp = '|' } }); p.update(.{ .key = .{ .cp = 'c', .text = "cat" } }); @@ -448,8 +448,8 @@ test "a failed filter opens an errors buffer carrying the command's own words" { defer p.deinit(); while (p.nextEffect()) |_| {} const pane = try p.setTestFile("abc\n"); - pane.cur_col = 2; - pane.vsel = .{ .active = true, .row = 0, .col = 0, .explicit = true }; + pane.body.cur_col = 2; + pane.body.vsel = .{ .active = true, .row = 0, .col = 0, .explicit = true }; p.update(.{ .key = .{ .cp = '|' } }); for ("trr") |c| p.update(.{ .key = .{ .cp = c, .text = &.{c} } }); @@ -491,8 +491,8 @@ test "selection pipe rejects a reused pane slot and a superseded request" { defer p.deinit(); while (p.nextEffect()) |_| {} var pane = try p.setTestFile("old\n"); - pane.cur_col = 2; - pane.vsel = .{ .active = true, .row = 0, .col = 0, .explicit = true }; + pane.body.cur_col = 2; + pane.body.vsel = .{ .active = true, .row = 0, .col = 0, .explicit = true }; p.update(.{ .key = .{ .cp = '|' } }); p.update(.{ .key = .{ .cp = 'c', .text = "cat" } }); @@ -503,8 +503,8 @@ test "selection pipe rejects a reused pane slot and a superseded request" { try std.testing.expectEqualSlices(u8, "new\n", pane.file.?.content); try std.testing.expectEqual(@as(usize, 0), pane.file.?.history.undo_len); - pane.cur_col = 2; - pane.vsel = .{ .active = true, .row = 0, .col = 0, .explicit = true }; + pane.body.cur_col = 2; + pane.body.vsel = .{ .active = true, .row = 0, .col = 0, .explicit = true }; p.update(.{ .key = .{ .cp = '|' } }); p.update(.{ .key = .{ .cp = 'c', .text = "cat" } }); p.update(.{ .key = .{ .cp = Key.enter } }); @@ -533,7 +533,7 @@ test "selection pipe binding is file-normal-only" { p.update(.{ .key = .{ .cp = '|' } }); try std.testing.expect(pane.prompt != .pipe); pane.file.?.output = null; - pane.mode = .insert; + pane.body.mode = .insert; p.update(.{ .key = .{ .cp = '|', .text = "|" } }); try std.testing.expect(pane.prompt != .pipe); try std.testing.expectEqualSlices(u8, "|x", pane.file.?.content); -- cgit v1.3