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/File.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/File.zig')
| -rw-r--r-- | src/File.zig | 78 |
1 files changed, 40 insertions, 38 deletions
diff --git a/src/File.zig b/src/File.zig index eb1c6a7c..239aa4ba 100644 --- a/src/File.zig +++ b/src/File.zig @@ -17,16 +17,17 @@ const Pane = panes.Pane; const Output = panes.Output; const Mini = panes.Mini; const Terminal = panes.Terminal; +const Text = panes.Text; const SYNTAX_CONTEXT_AFTER_ROWS: usize = 2; /// Content and primary selection at one file edit boundary. Keeping only the -/// primary avoids putting Pane.max_selections ranges in every history entry. +/// primary avoids putting Text.max_selections ranges in every history entry. pub const Snapshot = struct { content: []u8, cur_row: i32, cur_col: i32, - vsel: Pane.CharSel, + vsel: Text.CharSel, }; pub const History = struct { @@ -652,27 +653,28 @@ pub fn cursorLines(arena: std.mem.Allocator, pane: *Pane, f: *State) ![]const [] /// Use the file's line index only when `text` is its complete live content. /// Edit-buffer fragments and other temporary text retain modal's scan path. -fn contentIndex(pane: *Pane, text: []const u8) ?[]const usize { +fn contentIndex(t: *Text, text: []const u8) ?[]const usize { + const pane = t.pane(); const f = if (pane.file) |*file| file else return null; if (text.ptr != f.content.ptr or text.len != f.content.len) return null; return lineIndex(pane.gpa, f) catch null; } -pub fn textOffset(pane: *Pane, text: []const u8, cursor: modal.Cursor) usize { - const index = contentIndex(pane, text) orelse return modal.offsetAt(text, cursor); +pub fn textOffset(t: *Text, text: []const u8, cursor: modal.Cursor) usize { + const index = contentIndex(t, text) orelse return modal.offsetAt(text, cursor); const row = @min(cursor.row, index.len - 1); const start = index[row]; const end = if (row + 1 < index.len) index[row + 1] - 1 else text.len; return start + modal.graphemeStart(text[start..end], @min(cursor.col, end - start)); } -pub fn textLineStart(pane: *Pane, text: []const u8, row: usize) usize { - const index = contentIndex(pane, text) orelse return modal.lineStartOffset(text, row); +pub fn textLineStart(t: *Text, text: []const u8, row: usize) usize { + const index = contentIndex(t, text) orelse return modal.lineStartOffset(text, row); return if (row >= index.len) text.len else index[row]; } -pub fn textLine(pane: *Pane, text: []const u8, row: usize) []const u8 { - const index = contentIndex(pane, text) orelse return modal.lineSlice(text, row); +pub fn textLine(t: *Text, text: []const u8, row: usize) []const u8 { + const index = contentIndex(t, text) orelse return modal.lineSlice(text, row); if (row >= index.len) return ""; const start = index[row]; const end = if (row + 1 < index.len) index[row + 1] - 1 else text.len; @@ -686,7 +688,7 @@ test "indexed text rows preserve fragment and allocation failure semantics" { const pane = try p.setTestFile("first\n\tλ界\n\nlast\n"); const text = pane.file.?.content; allocator.fail_index = allocator.alloc_index; - for (0..lineCount(text) + 2) |row| try std.testing.expectEqualStrings(modal.lineSlice(text, row), textLine(pane, text, row)); + for (0..lineCount(text) + 2) |row| try std.testing.expectEqualStrings(modal.lineSlice(text, row), textLine(&pane.body, text, row)); try std.testing.expectEqual(@as(usize, 0), pane.file.?.line_starts.len); allocator.fail_index = std.math.maxInt(usize); _ = try lineIndex(p.gpa, &pane.file.?); @@ -695,19 +697,19 @@ test "indexed text rows preserve fragment and allocation failure semantics" { for ([_][]const u8{ text, text[2..], text[0..8], "other\nrows\n\n", "" }) |fragment| { for (0..lineCount(fragment) + 2) |row| try std.testing.expectEqualStrings( modal.lineSlice(fragment, row), - textLine(pane, fragment, row), + textLine(&pane.body, fragment, row), ); } try std.testing.expectEqual(allocations, allocator.allocations); } -pub fn textLineCount(pane: *Pane, text: []const u8) usize { - const index = contentIndex(pane, text) orelse return modal.cursorLineCount(text); +pub fn textLineCount(t: *Text, text: []const u8) usize { + const index = contentIndex(t, text) orelse return modal.cursorLineCount(text); return index.len; } -pub fn textPosition(pane: *Pane, text: []const u8, offset: usize) modal.Cursor { - const index = contentIndex(pane, text) orelse return modal.positionAt(text, offset); +pub fn textPosition(t: *Text, text: []const u8, offset: usize) modal.Cursor { + const index = contentIndex(t, text) orelse return modal.positionAt(text, offset); const bounded = @min(offset, text.len); const row = std.sort.upperBound(usize, index, bounded, struct { fn cmp(key: usize, item: usize) std.math.Order { @@ -734,8 +736,8 @@ pub fn open(p: *Pardes, id: usize, path: []const u8, line: usize) !*Pane { const total = lineCount(content); const scroll: usize = if (line > 0 and line <= total) line - 1 else 0; pane.file = .{ .path = path_copy, .content = content, .scroll = scroll, .history = history }; - pane.cur_pinned = true; - pane.cur_row = @intCast(scroll); + pane.body.cur_pinned = true; + pane.body.cur_row = @intCast(scroll); p.installPane(id, pane); if (filesystem.localPath(path) != null) p.emit(.{ .watch = .{ .pane = @intCast(id), .on = true } }); return pane; @@ -794,8 +796,8 @@ pub fn restore(p: *Pardes, id: usize, src: dump.Pane) !*Pane { .history = history, }; pane.file.?.tree_context = saved.tree_context and supportsContext(pane); - pane.cur_pinned = true; - pane.cur_row = @intCast(src.scroll); + pane.body.cur_pinned = true; + pane.body.cur_row = @intCast(src.scroll); pane.cols = @max(1, src.cols); pane.rows = @max(1, src.rows); if (output == null and filesystem.localPath(path) != null) p.emit(.{ .watch = .{ .pane = @intCast(id), .on = true, .mode = .baseline_disk } }); @@ -858,12 +860,12 @@ pub fn restoreSnap(pane: *Pane, f: *State, snap: Snapshot) void { const n = nlines(pane.gpa, f); const row: usize = @min(@as(usize, @intCast(@max(0, snap.cur_row))), n - 1); const llen = modal.lineSlice(f.content, row).len; - pane.cur_row = @intCast(row); - pane.cur_col = @intCast(@min(@as(usize, @intCast(@max(0, snap.cur_col))), llen)); - pane.vsel = snap.vsel; - pane.msel.active = false; - pane.cur_pinned = true; - pane.sticky_col = -1; + pane.body.cur_row = @intCast(row); + pane.body.cur_col = @intCast(@min(@as(usize, @intCast(@max(0, snap.cur_col))), llen)); + pane.body.vsel = snap.vsel; + pane.body.msel.active = false; + pane.body.cur_pinned = true; + pane.body.sticky_col = -1; pane.ensureCursorVisible(); } @@ -883,9 +885,9 @@ pub fn pushUndo(p: *Pardes, pane: *Pane) void { if (history.undo_len > 0 and std.mem.eql(u8, history.undo[history.undo_len - 1].content, f.content)) return; const snap: Snapshot = .{ .content = p.gpa.dupe(u8, f.content) catch return, - .cur_row = pane.cur_row, - .cur_col = pane.cur_col, - .vsel = pane.vsel, + .cur_row = pane.body.cur_row, + .cur_col = pane.body.cur_col, + .vsel = pane.body.vsel, }; pushHistory(p.gpa, &history.undo, &history.undo_len, snap); for (history.redo[0..history.redo_len]) |item| p.gpa.free(item.content); @@ -898,9 +900,9 @@ pub fn undo(p: *Pardes, pane: *Pane) void { if (history.undo_len == 0) return; const current: Snapshot = .{ .content = p.gpa.dupe(u8, f.content) catch return, - .cur_row = pane.cur_row, - .cur_col = pane.cur_col, - .vsel = pane.vsel, + .cur_row = pane.body.cur_row, + .cur_col = pane.body.cur_col, + .vsel = pane.body.vsel, }; pushHistory(p.gpa, &history.redo, &history.redo_len, current); history.undo_len -= 1; @@ -915,9 +917,9 @@ pub fn redo(p: *Pardes, pane: *Pane) void { if (history.redo_len == 0) return; const current: Snapshot = .{ .content = p.gpa.dupe(u8, f.content) catch return, - .cur_row = pane.cur_row, - .cur_col = pane.cur_col, - .vsel = pane.vsel, + .cur_row = pane.body.cur_row, + .cur_col = pane.body.cur_col, + .vsel = pane.body.vsel, }; pushHistory(p.gpa, &history.undo, &history.undo_len, current); history.redo_len -= 1; @@ -939,9 +941,9 @@ pub fn changed(p: *Pardes, id: u8, bytes: []const u8) void { // restoreSnap only consumes cursor/selection from this synthetic snapshot. restoreSnap(pane, f, .{ .content = undefined, - .cur_row = pane.cur_row, - .cur_col = pane.cur_col, - .vsel = pane.vsel, + .cur_row = pane.body.cur_row, + .cur_col = pane.body.cur_col, + .vsel = pane.body.vsel, }); } @@ -1126,7 +1128,7 @@ pub fn drawGutter(p: *Pardes, pane: *Pane, r: pardes.Rect, tx: u16, tw: u16, bod const prefix_width = gutterWidth(pane); const goff = pane.scroll(); const gcur = Terminal.gridCursor(pane); - const gcrow = if (pane.cur_pinned) pane.cur_row else @as(i32, gcur.y) + goff; + const gcrow = if (pane.body.cur_pinned) pane.body.cur_row else @as(i32, gcur.y) + goff; const cur_line: i32 = if (active and !pane.tag_edit) gcrow else std.math.minInt(i32); // the body's first row, the way renderPane derives it (Tagbottom) const body_y = p.bodyTop(r); |
