diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-27 23:06:11 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 95a3a1bb822e583988bfe0f312d0ef219b27a454 (patch) | |
| tree | 95306e8d3f36f8af05b3ea919f6cdef1e94a028b /src/pardes.zig | |
| parent | c8f0c25a854b8e169db08a84d1fdcd915a70aaa0 (diff) | |
| download | pardes-95a3a1bb822e583988bfe0f312d0ef219b27a454.tar.gz pardes-95a3a1bb822e583988bfe0f312d0ef219b27a454.zip | |
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 <[email protected]>
Diffstat (limited to 'src/pardes.zig')
| -rw-r--r-- | src/pardes.zig | 239 |
1 files changed, 120 insertions, 119 deletions
diff --git a/src/pardes.zig b/src/pardes.zig index 97860cfe..15b2c49b 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -240,6 +240,7 @@ pub const pdf_raster_policy: PdfRasterPolicy = switch (platform) { }; pub const Pane = panes.Pane; +const Text = panes.Text; pub const MAX_PANES = 16; pub const BodyLayer = @import("body_layer.zig").Layer; @@ -875,19 +876,19 @@ test "Togglettymode builtin and keyboard chord share mode transitions including try std.testing.expect(std.mem.indexOf(u8, Pardes.curTail(pane), " Mode ") != null); p.seedTail(pane); try std.testing.expect(pane.appendTag(" Togglettymode")); - try std.testing.expectEqual(Pane.Mode.tty, pane.mode); + try std.testing.expectEqual(Text.Mode.tty, pane.body.mode); try std.testing.expect(p.executeBuiltinLine(0, "Togglettymode")); - try std.testing.expectEqual(Pane.Mode.normal, pane.mode); + try std.testing.expectEqual(Text.Mode.normal, pane.body.mode); p.update(.{ .key = .{ .cp = p.opts.tty_toggle, .ctrl = true } }); - try std.testing.expectEqual(Pane.Mode.tty, pane.mode); + try std.testing.expectEqual(Text.Mode.tty, pane.body.mode); p.enterTagEdit(pane, -1); - try std.testing.expectEqual(Pane.Mode.insert, pane.mode); + try std.testing.expectEqual(Text.Mode.insert, pane.body.mode); try std.testing.expect(p.executeBuiltinLine(0, "Togglettymode")); - try std.testing.expectEqual(Pane.Mode.normal, pane.mode); + try std.testing.expectEqual(Text.Mode.normal, pane.body.mode); try std.testing.expect(!pane.tag_edit); p.enterTagEdit(pane, -1); p.update(.{ .key = .{ .cp = p.opts.tty_toggle, .ctrl = true } }); - try std.testing.expectEqual(Pane.Mode.tty, pane.mode); + try std.testing.expectEqual(Text.Mode.tty, pane.body.mode); try std.testing.expect(!pane.tag_edit); p.acknowledgePanelPresentation(&.{}); @@ -896,10 +897,10 @@ test "Togglettymode builtin and keyboard chord share mode transitions including const y = p.rects[0].y; p.update(.{ .mouse = .{ .button = .middle, .kind = .press, .col = x, .row = y } }); p.update(.{ .mouse = .{ .button = .middle, .kind = .release, .col = x, .row = y } }); - try std.testing.expectEqual(Pane.Mode.normal, pane.mode); + try std.testing.expectEqual(Text.Mode.normal, pane.body.mode); const file = try p.setTestFile("unchanged\n"); try std.testing.expect(p.executeBuiltinLine(0, "Togglettymode")); - try std.testing.expectEqual(Pane.Mode.normal, file.mode); + try std.testing.expectEqual(Text.Mode.normal, file.body.mode); try std.testing.expectEqualStrings("unchanged\n", file.file.?.content); } @@ -1143,7 +1144,7 @@ test "Tty spawns a raw shell in the caller's directory" { try std.testing.expect(free != 0); const nt = p.panes[free] orelse return error.NoNewPane; try std.testing.expect(nt.isTerminal()); - try std.testing.expect(nt.mode == .tty); + try std.testing.expect(nt.body.mode == .tty); var found = false; while (p.nextEffect()) |effect| switch (effect) { @@ -1165,7 +1166,7 @@ test "Tty9p marks only the new Linux terminal for a mounted shell" { try std.testing.expect(p.active != 0); try std.testing.expect(!p.panes[0].?.v9fs_on_spawn); const pane = p.panes[p.active].?; - try std.testing.expect(pane.isTerminal() and pane.mode == .tty); + try std.testing.expect(pane.isTerminal() and pane.body.mode == .tty); try std.testing.expect(pane.v9fs_on_spawn); try std.testing.expect(!pane.greet); try std.testing.expect(!exec.takesCommandLine(p, p.active)); @@ -1753,7 +1754,7 @@ test "owned cwd EOF failure retains the pane and Del retries its close" { p.update(.{ .eof = .{ .pane = 0 } }); try std.testing.expectEqual(parent, p.panes[0].?); try std.testing.expectEqual(parent, child.cwd.inherited); - try std.testing.expectEqual(Pane.Mode.normal, parent.mode); + try std.testing.expectEqual(Text.Mode.normal, parent.body.mode); try std.testing.expect(std.mem.indexOf(u8, parent.msg[0..parent.msg_len], "Del retries close") != null); try std.testing.expectEqual(@as(usize, 0), p.effects_len); allocator.fail_index = std.math.maxInt(usize); @@ -2626,7 +2627,7 @@ test "Unicode display cells map back to body and tag byte cursors" { // Both halves map to its one byte boundary. p.update(.{ .mouse = .{ .button = config.select_button, .kind = .press, .col = text_x + 2, .row = body_y } }); p.update(.{ .mouse = .{ .button = config.select_button, .kind = .release, .col = text_x + 2, .row = body_y } }); - try std.testing.expectEqual(@as(i32, 1), pane.cur_col); + try std.testing.expectEqual(@as(i32, 1), pane.body.cur_col); // A click in the wide path glyph likewise becomes a byte cursor at the // grapheme start; arrow motion then advances by the full UTF-8 cluster. @@ -2660,8 +2661,8 @@ test "tabbed file aligns syntax cursor and mouse at the nearest insertion gap" { @memset(f.highlights, @intFromEnum(syntax.Syn.none)); @memset(f.highlights[1..7], @intFromEnum(syntax.Syn.keyword)); f.syntax_dirty = false; - pane.cur_row = 0; - pane.cur_col = 1; + pane.body.cur_row = 0; + pane.body.cur_col = 1; var frame = std.heap.ArenaAllocator.init(gpa); defer frame.deinit(); @@ -2678,14 +2679,14 @@ test "tabbed file aligns syntax cursor and mouse at the nearest insertion gap" { const click_x = text_x + config.tab_width + 2; p.update(.{ .mouse = .{ .button = config.select_button, .kind = .press, .col = click_x, .row = body_y } }); p.update(.{ .mouse = .{ .button = config.select_button, .kind = .release, .col = click_x, .row = body_y } }); - try std.testing.expectEqual(@as(i32, 3), pane.cur_col); + try std.testing.expectEqual(@as(i32, 3), pane.body.cur_col); const line = modal.lineSlice(f.content, 0); const virtual: u16 = 3; const blank_x = text_x + @as(u16, @intCast(panes.File.displayWidth(line))) + virtual; p.update(.{ .mouse = .{ .button = config.select_button, .kind = .press, .col = blank_x, .row = body_y } }); p.update(.{ .mouse = .{ .button = config.select_button, .kind = .release, .col = blank_x, .row = body_y } }); - try std.testing.expectEqual(@as(i32, @intCast(line.len)), pane.cur_col); + try std.testing.expectEqual(@as(i32, @intCast(line.len)), pane.body.cur_col); _ = frame.reset(.retain_capacity); const virtual_surface = try p.render(frame.allocator()); @@ -3887,7 +3888,7 @@ pub const Pardes = struct { }, .tty, .tty_shell => { _ = try p.newShell(0, ""); - p.panes[0].?.mode = .tty; + p.panes[0].?.body.mode = .tty; p.oneColumn(); // A bare tty is still an editor: it opens with somewhere to put // text under the shell. The shell keeps the keyboard. @@ -4108,8 +4109,8 @@ pub const Pardes = struct { if (comptime pdf_enabled) if (pane.pdf) |*pv| panes.Pdf.deinitPane(p, pane, pv); if (pane.file) |*file| panes.File.deinit(p, pane, file); if (pane.ovl) |o| p.gpa.free(o.text); - for (pane.ed_undo[0..pane.ed_undo_len]) |sn| if (sn.ovl) |o| p.gpa.free(o.text); - for (pane.ed_redo[0..pane.ed_redo_len]) |sn| if (sn.ovl) |o| p.gpa.free(o.text); + for (pane.body.ed_undo[0..pane.body.ed_undo_len]) |sn| if (sn.ovl) |o| p.gpa.free(o.text); + for (pane.body.ed_redo[0..pane.body.ed_redo_len]) |sn| if (sn.ovl) |o| p.gpa.free(o.text); panes.Terminal.deinitEmulator(pane, p.gpa); pane.clearCwd(); pane.fs.deinit(p.gpa); @@ -4159,7 +4160,7 @@ pub const Pardes = struct { errdefer p.gpa.free(content); const np = try panes.Output.open(p, free, paneDir(src), .{ .cmd = .New }, "", content); np.cwd = .{ .inherited = src }; - np.cur_pinned = true; + np.body.cur_pinned = true; return np; } @@ -4170,7 +4171,7 @@ pub const Pardes = struct { const content = try p.gpa.dupe(u8, ""); errdefer p.gpa.free(content); const np = try panes.Output.open(p, free, dir, .{ .cmd = .New }, "", content); - np.cur_pinned = true; + np.body.cur_pinned = true; return np; } @@ -4685,7 +4686,7 @@ pub const Pardes = struct { ctlfs.pty.noteMarks(p, o.pane, pane); }, .eof => |e| p.removePane(e.pane, null) catch |err| { - if (p.panes[e.pane]) |pane| pane.mode = .normal; + if (p.panes[e.pane]) |pane| pane.body.mode = .normal; p.reportError(e.pane, "terminal exited; Del retries close", err); }, .lsp_resp => |r| p.lspResponse(r.id, r.rows), @@ -4839,12 +4840,12 @@ pub const Pardes = struct { const edit0: i32 = @intCast((p.tagPrefix(pane) catch return).len); p.seedTail(pane); if (!pane.tag_init) return; - if (!pane.tag_edit) pane.tag_mode = pane.mode; + if (!pane.tag_edit) pane.tag_mode = pane.body.mode; pane.tag_edit = true; pane.tag_sel = false; // a one-line tag has no use for normal mode: always insert - pane.mode = .insert; - pane.normal.clear(); + pane.body.mode = .insert; + pane.body.normal.clear(); const end = edit0 + @as(i32, @intCast(pane.tag_tail_len)); const tail = pane.tagSlice(); const lead: i32 = @intCast(tail.len - std.mem.trimStart(u8, tail, " ").len); @@ -4870,8 +4871,8 @@ pub const Pardes = struct { pub fn exitTagEdit(pane: *Pane) void { if (pane.tag_edit and pane.prompt == .none) pane.tag_saved_col = pane.tag_col; if (pane.isTerminal()) { - if (pane.tag_edit) pane.mode = pane.tag_mode; - } else pane.mode = .normal; + if (pane.tag_edit) pane.body.mode = pane.tag_mode; + } else pane.body.mode = .normal; pane.tag_edit = false; pane.tag_sel = false; pane.tag_name_len = null; @@ -5054,7 +5055,7 @@ pub const Pardes = struct { const id = p.active; const body_mode = pane.tag_mode; exitTagEdit(pane); // leave the tag FIRST: Del frees the pane - pane.mode = body_mode; + pane.body.mode = body_mode; exec.runBuiltin(p, cmd, id, "", txt); return; } @@ -5075,7 +5076,7 @@ pub const Pardes = struct { else cur)); pane.tag_sel = false; - pane.mode = .insert; + pane.body.mode = .insert; return; } const dir: ?Builtin = for (config.window_keys) |wk| { @@ -5096,7 +5097,7 @@ pub const Pardes = struct { const dest = p.panes[p.active] orelse return; exitTagEdit(pane); // the pane we left is fully out of its tag... p.enterTagEdit(dest, -1); - if (dest.tag_edit) dest.mode = .normal; + if (dest.tag_edit) dest.body.mode = .normal; return; } if (lineMotion(text, cur, key)) |r| setTagRange(pane, text, r); @@ -5244,7 +5245,7 @@ pub const Pardes = struct { p.exitHeader(); p.active = d; p.enterTagEdit(pane, -1); - if (pane.tag_edit) pane.mode = .normal; + if (pane.tag_edit) pane.body.mode = .normal; return; } if (hit(key, config.topbar_up)) { @@ -5268,19 +5269,19 @@ pub const Pardes = struct { pub fn cycleMode(p: *Pardes, id: usize) void { const pane = p.panes[id] orelse return; if (pane.file == null and !pane.isTerminal()) return; - const previous = if (pane.tag_edit) pane.tag_mode else pane.mode; + const previous = if (pane.tag_edit) pane.tag_mode else pane.body.mode; p.active = id; p.exitHeader(); if (pane.tag_edit) exitTagEdit(pane); - pane.mode = previous; + pane.body.mode = previous; switch (previous) { .tty => { - pane.mode = .normal; - pane.normal.clear(); + pane.body.mode = .normal; + pane.body.normal.clear(); }, - .normal => edit.enterInsert(p, pane, .at, 1), + .normal => edit.enterInsert(p, &pane.body, .at, 1), .insert => { - edit.exitInsert(p, pane); + edit.exitInsert(p, &pane.body); if (pane.isTerminal()) panes.Terminal.enterTty(p, id); }, } @@ -5293,9 +5294,9 @@ pub const Pardes = struct { p.active = id; p.exitHeader(); if (pane.tag_edit) exitTagEdit(pane); - if (pane.mode == .tty) { - pane.mode = .normal; - pane.normal.clear(); + if (pane.body.mode == .tty) { + pane.body.mode = .normal; + pane.body.normal.clear(); } else panes.Terminal.enterTty(p, id); } @@ -5312,7 +5313,7 @@ pub const Pardes = struct { } if (p.topbar_col != null) return p.topbarKey(key); const pane = p.panes[p.active] orelse return; - if (pane.isTerminal() and pane.mode == .tty and !pane.tag_edit) { + if (pane.isTerminal() and pane.body.mode == .tty and !pane.tag_edit) { p.leader_on = false; p.ctrl_w_pending = false; if (key.ctrl and !key.alt and key.cp == p.opts.tty_toggle) @@ -5350,7 +5351,7 @@ pub const Pardes = struct { } // insert mode owns Ctrl-w (delete word backward, helix); the focus // prefix applies outside raw tty mode - if (hit(key, config.window_prefix) and pane.mode != .insert) { + if (hit(key, config.window_prefix) and pane.body.mode != .insert) { p.ctrl_w_pending = true; return; } @@ -5403,27 +5404,27 @@ pub const Pardes = struct { .none, .del_side => unreachable, } exitTagEdit(pane); - pane.mode = .normal; - pane.normal.clear(); + pane.body.mode = .normal; + pane.body.normal.clear(); return; } if (pane.tag_edit) { - if (pane.mode == .normal) p.tagNormalKey(pane, key) else p.tagInsertKey(pane, key); + if (pane.body.mode == .normal) p.tagNormalKey(pane, key) else p.tagInsertKey(pane, key); const pn = p.panes[p.active] orelse return; if (normal.selRegexArmed(pn)) |a| normal.applySelRegex(p, pn, a.pat, a.split); return; } - if (pane.mode == .normal and (hit(key, config.look_key) or hit(key, config.exec_key))) { + if (pane.body.mode == .normal and (hit(key, config.look_key) or hit(key, config.exec_key))) { const cmd = if (hit(key, config.look_key)) config.look_cmd else config.exec_cmd; - edit.pinPaneCursor(p, pane); + edit.pinPaneCursor(p, &pane.body); const explicit = (p.native_images and pane.hasPdfSelection()) or - (pane.vsel.active and pane.vsel.explicit) or pane.msel.active; + (pane.body.vsel.active and pane.body.vsel.explicit) or pane.body.msel.active; if (explicit) { if (exec.chordEachSel(p, pane, cmd)) return; if (edit.currentSelText(p, pane)) |txt| { - pane.vsel.active = false; - pane.msel.active = false; - pane.select = false; + pane.body.vsel.active = false; + pane.body.msel.active = false; + pane.body.select = false; exec.runBuiltin(p, cmd, p.active, "", txt); return; } @@ -5433,14 +5434,14 @@ pub const Pardes = struct { exec.runBuiltin(p, cmd, p.active, "", word); return; } - if (pane.mode == .normal and pane.hasPdf()) return normal.handlePdfNormal(p, pane, key); - switch (pane.mode) { + if (pane.body.mode == .normal and pane.hasPdf()) return normal.handlePdfNormal(p, pane, key); + switch (pane.body.mode) { .normal => { - normal.handleNormal(p, pane, key); + normal.handleNormal(p, &pane.body, key); }, .insert => { - if (hit(key, config.escape)) return edit.exitInsert(p, pane); - edit.handleInsert(p, pane, key); + if (hit(key, config.escape)) return edit.exitInsert(p, &pane.body); + edit.handleInsert(p, &pane.body, key); }, .tty => panes.Terminal.forwardKey(p, p.active, key), } @@ -5529,8 +5530,8 @@ pub const Pardes = struct { }; pane.tag_edit = true; pane.tag_sel = false; - pane.mode = .insert; - pane.normal.clear(); + pane.body.mode = .insert; + pane.body.normal.clear(); pane.tag_col = @intCast(prefix_len + pane.tag_tail_len); } @@ -5551,8 +5552,8 @@ pub const Pardes = struct { if (!zig_buf and std.mem.indexOfAny(u8, arg, " \t\r\n\"\\") != null) return; } const off = if (pane.file) |f| modal.offsetAt(f.content, .{ - .row = @intCast(@max(0, pane.cur_row)), - .col = @intCast(@max(0, pane.cur_col)), + .row = @intCast(@max(0, pane.body.cur_row)), + .col = @intCast(@max(0, pane.body.cur_col)), }) else 0; p.lsp_seq +%= 1; p.lsp_wait = .{ @@ -5562,8 +5563,8 @@ pub const Pardes = struct { .serial = pane.serial, .revision = if (pane.file) |f| f.revision else 0, .arg = .from(arg), - .row = pane.cur_row, - .col = pane.cur_col, + .row = pane.body.cur_row, + .col = pane.body.cur_col, }; p.emit(.{ .lsp = .{ .id = p.lsp_seq, @@ -5666,8 +5667,8 @@ pub const Pardes = struct { const replacement = p.gpa.alloc(u8, final_len) catch return false; const old_cursor = modal.offsetAt(f.content, .{ - .row = @intCast(@max(0, pane.cur_row)), - .col = @intCast(@max(0, pane.cur_col)), + .row = @intCast(@max(0, pane.body.cur_row)), + .col = @intCast(@max(0, pane.body.cur_col)), }); const mapped_cursor = mapLspEditOffset(edits, old_cursor); var read_at: usize = 0; @@ -5681,15 +5682,15 @@ pub const Pardes = struct { } @memcpy(replacement[write_at..], f.content[read_at..]); - edit.pushUndo(p, pane); + edit.pushUndo(p, &pane.body); panes.File.setContent(p, f, replacement); const cursor = modal.positionAt(f.content, mapped_cursor); - pane.cur_row = @intCast(cursor.row); - pane.cur_col = @intCast(cursor.col); - pane.vsel.active = false; - pane.msel.active = false; - pane.select = false; - pane.sticky_col = -1; + pane.body.cur_row = @intCast(cursor.row); + pane.body.cur_col = @intCast(cursor.col); + pane.body.vsel.active = false; + pane.body.msel.active = false; + pane.body.select = false; + pane.body.sticky_col = -1; pane.ensureCursorVisible(); return true; } @@ -5718,8 +5719,8 @@ pub const Pardes = struct { } else return; } if (rows.len == 0) { - if (w.kind == .completion and pane.mode == .insert and pane.nsel == 0 and - pane.cur_row == w.row and pane.cur_col == w.col) edit.insertTab(p, pane); + if (w.kind == .completion and pane.body.mode == .insert and pane.body.nsel == 0 and + pane.body.cur_row == w.row and pane.body.cur_col == w.col) edit.insertTab(p, &pane.body); return; } @@ -5789,7 +5790,7 @@ pub const Pardes = struct { errdefer p.gpa.destroy(history); const pane = try p.newDocPane(id); pane.file = .{ .path = path, .content = content, .history = history }; - pane.cur_pinned = true; + pane.body.cur_pinned = true; return pane; } @@ -5806,7 +5807,7 @@ pub const Pardes = struct { p.panes[0] = null; pane.file = .{ .path = path, .content = copy, .history = history }; p.installPane(0, pane); - pane.cur_pinned = true; + pane.body.cur_pinned = true; p.active = 0; p.sync(); return pane; @@ -6015,8 +6016,8 @@ pub const Pardes = struct { const now: Loc = .{ .pane = @intCast(p.active), .serial = pane.serial, - .line = if (pane.pdfPage()) |page| @intCast(page + 1) else if (pane.cur_pinned) @intCast(@max(0, pane.cur_row) + 1) else 0, - .col = if (pane.pdfPage() != null) 0 else if (pane.cur_pinned) @intCast(@max(0, pane.cur_col) + 1) else 0, + .line = if (pane.pdfPage()) |page| @intCast(page + 1) else if (pane.body.cur_pinned) @intCast(@max(0, pane.body.cur_row) + 1) else 0, + .col = if (pane.pdfPage() != null) 0 else if (pane.body.cur_pinned) @intCast(@max(0, pane.body.cur_col) + 1) else 0, }; if (p.njumps > 0 and p.samePlace(p.jumps[p.jcur], now)) { p.jumps[p.jcur] = now; // same visit; the entry IS where you are @@ -6289,7 +6290,7 @@ pub const Pardes = struct { if (prompt_col < printed.dropped) continue; const caret = printed.left + (prompt_col - printed.dropped); if (caret < tx + tw) - s.cursor = .{ .x = @intCast(caret), .y = row, .bar = pane.mode == .insert }; + s.cursor = .{ .x = @intCast(caret), .y = row, .bar = pane.body.mode == .insert }; } } @@ -6717,7 +6718,7 @@ pub const Pardes = struct { const typed = panes.File.displayWidth(text[0..@min(pane.tag_col - prompt0, text.len)]); const dropped = shown - panes.File.displayWidth(kept); if (typed < dropped) continue; - p.surface.tag_layers[layer_index].cursor = .{ .col = @intCast(pad + typed - dropped), .bar = pane.mode == .insert }; + p.surface.tag_layers[layer_index].cursor = .{ .col = @intCast(pad + typed - dropped), .bar = pane.body.mode == .insert }; } } if (p.columnBarHeight() > 0) for (0..p.ncol) |column| { @@ -6820,7 +6821,7 @@ pub const Pardes = struct { .font_role = .tagline, }; s.fill(r.x, tag_y, config.GUTTER, BOX_H, box_style); - const box_mode = if (pane.tag_edit) pane.tag_mode else pane.mode; + const box_mode = if (pane.tag_edit) pane.tag_mode else pane.body.mode; s.set(r.x, tag_y, switch (box_mode) { .normal => config.tag_normal, .insert => config.tag_insert, @@ -6929,7 +6930,7 @@ pub const Pardes = struct { if (active and pane.tag_edit) { // bar while typing, block for `:` normal mode (same rule as a body) const col = panes.File.rawDisplayCol(tag, pane.tag_col) -| pane.tag_scroll; - if (col < tw) s.cursor = .{ .x = tx + @as(u16, @intCast(col)), .y = tag_y, .bar = pane.mode == .insert }; + if (col < tw) s.cursor = .{ .x = tx + @as(u16, @intCast(col)), .y = tag_y, .bar = pane.body.mode == .insert }; } for (pane.sel, 0..) |selection, button| { @@ -7039,7 +7040,7 @@ test "raw terminal cursor obeys visibility without hiding modal and tag cursors" defer p.deinit(); while (p.nextEffect()) |_| {} const pane = p.panes[0].?; - pane.mode = .tty; + pane.body.mode = .tty; p.update(.{ .output = .{ .pane = 0, .bytes = "abc" } }); var frame = std.heap.ArenaAllocator.init(gpa); defer frame.deinit(); @@ -7051,10 +7052,10 @@ test "raw terminal cursor obeys visibility without hiding modal and tag cursors" p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[?25l" } }); try std.testing.expect((try p.render(frame.allocator())).cursor == null); - pane.mode = .normal; - pane.cur_pinned = true; - pane.cur_row = 0; - pane.cur_col = 1; + pane.body.mode = .normal; + pane.body.cur_pinned = true; + pane.body.cur_row = 0; + pane.body.cur_col = 1; const modal_cursor = (try p.render(frame.allocator())).cursor orelse return error.MissingCursor; try std.testing.expectEqual(rect.x + config.GUTTER + 1, modal_cursor.x); try std.testing.expectEqual(body_y, modal_cursor.y); @@ -7062,7 +7063,7 @@ test "raw terminal cursor obeys visibility without hiding modal and tag cursors" const tag_cursor = (try p.render(frame.allocator())).cursor orelse return error.MissingCursor; try std.testing.expectEqual(if (p.settings.tag_bottom) rect.y + rect.h - BOX_H else rect.y, tag_cursor.y); Pardes.exitTagEdit(pane); - pane.mode = .tty; + pane.body.mode = .tty; try std.testing.expect((try p.render(frame.allocator())).cursor == null); p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[?25h\x1b[?1049h\x1b[2;5H" } }); @@ -7086,7 +7087,7 @@ test "raw terminal cursor follows its active row only while that row is visible" while (p.nextEffect()) |_| {} p.settings.tag_bottom = tag_bottom; const pane = p.panes[0].?; - pane.mode = .tty; + pane.body.mode = .tty; for (0..60) |_| p.update(.{ .output = .{ .pane = 0, .bytes = "row\r\n" } }); p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[3;4H" } }); var frame = std.heap.ArenaAllocator.init(gpa); @@ -7129,7 +7130,7 @@ test "Esc back into a tty leaves its view at the prompt" { try std.testing.expectEqual(shell, p.active); p.update(.{ .key = .{ .cp = Key.escape, .shift = true } }); - try std.testing.expectEqual(Pane.Mode.tty, sp.mode); + try std.testing.expectEqual(Text.Mode.tty, sp.body.mode); // tty mode follows output to the bottom, so a screenful and a half of it // rides the view down and leaves that pin far up in the scrollback. @@ -7368,7 +7369,7 @@ test "the bare tty startup opens a text pane under the shell, unfocused" { const shell = p.panes[p.col_panes[0][0]].?; const scratch = p.panes[p.col_panes[0][1]].?; try std.testing.expect(shell.isTerminal()); - try std.testing.expectEqual(Pane.Mode.tty, shell.mode); + try std.testing.expectEqual(Text.Mode.tty, shell.body.mode); try std.testing.expect(!scratch.isTerminal()); try std.testing.expectEqualStrings("", scratch.file.?.content); @@ -7391,7 +7392,7 @@ test "tty output follows only from the bottom" { const shell = p.active; const sp = p.panes[shell].?; p.update(.{ .key = .{ .cp = Key.escape, .shift = true } }); - try std.testing.expectEqual(Pane.Mode.tty, sp.mode); + try std.testing.expectEqual(Text.Mode.tty, sp.body.mode); for (0..60) |i| { var buf: [32]u8 = undefined; @@ -7445,7 +7446,7 @@ test "Esc back into a file leaves its view where it was" { for (0..20) |_| p.update(.{ .key = .{ .cp = 'j' } }); p.sync(); const view = dp.file.?.scroll; - const row = dp.cur_row; + const row = dp.body.cur_row; try std.testing.expect(view > 0); p.update(.{ .key = .{ .cp = Key.escape } }); // out to the shell @@ -7458,10 +7459,10 @@ test "Esc back into a file leaves its view where it was" { // Nothing moved. The old landing recentred the line under the cursor, so // coming back repainted the whole screen to show what was already on it. try std.testing.expectEqual(view, dp.file.?.scroll); - try std.testing.expectEqual(row, dp.cur_row); + try std.testing.expectEqual(row, dp.body.cur_row); // ...and the cursor is still on screen, which is all `.nearest` promises. - try std.testing.expect(dp.cur_row >= @as(i32, @intCast(view))); - try std.testing.expect(dp.cur_row < @as(i32, @intCast(view)) + @as(i32, dp.rows)); + try std.testing.expect(dp.body.cur_row >= @as(i32, @intCast(view))); + try std.testing.expect(dp.body.cur_row < @as(i32, @intCast(view)) + @as(i32, dp.rows)); // The other arm still centres: a look target, a `:NN`, a search hit. look.focusPaneLine(p, doc, .{ .line = @intCast(row + 1), .col = 1 }, .center); @@ -7488,7 +7489,7 @@ test "raw tty Escape only leaves at a shell prompt" { p.update(.{ .key = .{ .cp = Key.escape, .shift = true } }); try std.testing.expectEqual(@as(usize, 0), p.active); try std.testing.expectEqualStrings("", drainWrites(p, &buf)); - try std.testing.expectEqual(Pane.Mode.tty, pane.mode); + try std.testing.expectEqual(Text.Mode.tty, pane.body.mode); p.update(.{ .key = .{ .cp = Key.escape } }); // back into the shell try std.testing.expectEqual(shell, p.active); @@ -7497,7 +7498,7 @@ test "raw tty Escape only leaves at a shell prompt" { p.update(.{ .key = .{ .cp = Key.escape } }); // bare Escape leaves at a prompt try std.testing.expectEqual(@as(usize, 0), p.active); try std.testing.expectEqualStrings("", drainWrites(p, &buf)); - try std.testing.expectEqual(Pane.Mode.tty, pane.mode); + try std.testing.expectEqual(Text.Mode.tty, pane.body.mode); } test "raw tty Ctrl-B switches to editor mode without sending child input" { @@ -7511,7 +7512,7 @@ test "raw tty Ctrl-B switches to editor mode without sending child input" { _ = drainWrites(p, &buf); p.update(.{ .key = .{ .cp = 'b', .ctrl = true } }); try std.testing.expectEqual(shell, p.active); - try std.testing.expectEqual(Pane.Mode.normal, p.panes[shell].?.mode); + try std.testing.expectEqual(Text.Mode.normal, p.panes[shell].?.body.mode); try std.testing.expectEqualStrings("", drainWrites(p, &buf)); } @@ -7599,7 +7600,7 @@ test "raw tty forwards Ctrl letters and former global shortcuts to the child" { }; try std.testing.expectEqualStrings(encoded, drainWrites(p, &buf)); try std.testing.expectEqual(@as(usize, 0), p.active); - try std.testing.expectEqual(Pane.Mode.tty, p.panes[0].?.mode); + try std.testing.expectEqual(Text.Mode.tty, p.panes[0].?.body.mode); try std.testing.expect(!p.ctrl_w_pending and p.clip_pending == null); } // ...and the two paste chords are the exception: Ctrl-V types the register @@ -7745,13 +7746,13 @@ test "leaving tty hides the prompt and keeps the command typed at it" { // The BODY is what you look at, and it follows the mode: in tty the pane // is the program's own screen, so the prompt is there. - sp.mode = .tty; + sp.body.mode = .tty; try std.testing.expectEqualStrings( "user@box ~/src $ grep -rn TODO src/", bodyRowOf(p, sp, "grep") orelse return error.MissingPromptRow, ); - sp.mode = .normal; + sp.body.mode = .normal; try std.testing.expectEqualStrings( "grep -rn TODO src/", bodyRowOf(p, sp, "grep") orelse return error.MissingPromptRow, @@ -7788,7 +7789,7 @@ test "entering tty walks the shell cursor to the column clicked past the prompt" .bytes = "\x1b]133;A;cl=line\x1b\\prompt> \x1b]133;B\x1b\\0123456789", } }); p.sync(); - sp.mode = .normal; + sp.body.mode = .normal; p.shell_rows.stale = true; // The command shows LEFT-HUGGED, so its column 3 is the '3'... @@ -7802,9 +7803,9 @@ test "entering tty walks the shell cursor to the column clicked past the prompt" // ...but the shell's own cursor lives on the real grid, eight cells // further right, behind the prompt this pane is not showing. - sp.cur_row = row; - sp.cur_col = 3; - sp.cur_pinned = true; + sp.body.cur_row = row; + sp.body.cur_col = 3; + sp.body.cur_pinned = true; while (p.nextEffect()) |_| {} panes.Terminal.enterTty(p, shell); @@ -7897,8 +7898,8 @@ test "LSP failure clears only its matching wait without completion indentation" const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); defer p.deinit(); const pane = try p.setTestFile("x"); - pane.mode = .insert; - pane.cur_col = 1; + pane.body.mode = .insert; + pane.body.cur_col = 1; p.lspRequest(p.active, .completion, ""); const old_id = p.lsp_wait.?.id; @@ -7909,13 +7910,13 @@ test "LSP failure clears only its matching wait without completion indentation" p.update(.{ .lsp_resp = .{ .id = current_id, .rows = null } }); try std.testing.expect(p.lsp_wait == null); try std.testing.expectEqualStrings("x", pane.file.?.content); - try std.testing.expectEqual(@as(i32, 1), pane.cur_col); + try std.testing.expectEqual(@as(i32, 1), pane.body.cur_col); p.lspRequest(p.active, .completion, ""); p.update(.{ .lsp_resp = .{ .id = p.lsp_wait.?.id, .rows = "" } }); try std.testing.expect(p.lsp_wait == null); try std.testing.expectEqualStrings("x ", pane.file.?.content); - try std.testing.expectEqual(@as(i32, 4), pane.cur_col); + try std.testing.expectEqual(@as(i32, 4), pane.body.cur_col); } test "board heap: every allocation failure during boot is a clean OutOfMemory" { @@ -7935,25 +7936,25 @@ test "jump history clears selections and lands at the recorded cursor" { p.jumps[0] = .{ .pane = 0, .serial = pane.serial, .line = 3, .col = 8 }; p.jumps[1] = .{ .pane = 0, .serial = pane.serial, .line = 1, .col = 1 }; for ([_]u21{ 'o', 'i' }, 0..) |key, index| { - pane.setRange(pane.file.?.content, 0, .{ .anchor = 1, .head = 5 }, true); + pane.body.setRange(pane.file.?.content, 0, .{ .anchor = 1, .head = 5 }, true); pane.sel[0] = .{ .state = .done, .c0 = 2, .c1 = 5, .r0 = 1, .r1 = 1 }; try edit.capturePointerSelection(p, pane, 0); - pane.select = true; + pane.body.select = true; p.update(.{ .key = .{ .cp = key, .ctrl = true } }); try std.testing.expectEqual(index, p.jcur); try std.testing.expectEqual(@as(usize, 2), p.njumps); try std.testing.expectEqual(@as(u32, 3), p.jumps[0].line); - try std.testing.expect(!pane.vsel.active and !pane.msel.active and !pane.select); - try std.testing.expectEqual(@as(u8, 0), pane.nsel); + try std.testing.expect(!pane.body.vsel.active and !pane.body.msel.active and !pane.body.select); + try std.testing.expectEqual(@as(u8, 0), pane.body.nsel); try std.testing.expectEqual(.none, pane.sel[0].state); try std.testing.expect(pane.pointer_selections[0] == null); - try std.testing.expectEqual(@as(i32, if (index == 0) 2 else 0), pane.cur_row); - try std.testing.expectEqual(@as(i32, if (index == 0) 7 else 0), pane.cur_col); + try std.testing.expectEqual(@as(i32, if (index == 0) 2 else 0), pane.body.cur_row); + try std.testing.expectEqual(@as(i32, if (index == 0) 7 else 0), pane.body.cur_col); } - pane.setRange(pane.file.?.content, 0, .{ .anchor = 1, .head = 5 }, true); - const selected = pane.primaryRange(pane.file.?.content, 0); + pane.body.setRange(pane.file.?.content, 0, .{ .anchor = 1, .head = 5 }, true); + const selected = pane.body.primaryRange(pane.file.?.content, 0); p.jumpBy(1); // No forward entry: selection stays untouched. - try std.testing.expectEqualDeep(selected, pane.primaryRange(pane.file.?.content, 0)); + try std.testing.expectEqualDeep(selected, pane.body.primaryRange(pane.file.?.content, 0)); for ([_]Loc{ .{ .pane = 15, .serial = pane.serial, .line = 1, .col = 1 }, .{ .pane = 0, .serial = pane.serial + 1, .line = 1, .col = 1 }, @@ -7961,7 +7962,7 @@ test "jump history clears selections and lands at the recorded cursor" { p.jumps[0] = missing; p.jumpBy(-1); try std.testing.expectEqual(@as(usize, 1), p.jcur); - try std.testing.expectEqualDeep(selected, pane.primaryRange(pane.file.?.content, 0)); + try std.testing.expectEqualDeep(selected, pane.body.primaryRange(pane.file.?.content, 0)); } } |
