diff options
Diffstat (limited to 'term.zig')
| -rw-r--r-- | term.zig | 47 |
1 files changed, 44 insertions, 3 deletions
@@ -15,9 +15,10 @@ pub const Sel = struct { /// Modal input mode. normal/insert are the helix-style text-editing modes; tty /// forwards keys to the shell (terminal panes only — file panes never use it). /// normal -> helix NORMAL: hjkl/w/b/e/... move a block cursor; i/a/o -> insert; -/// Esc -> tty (terminals) or stays (files). +/// v/x select; Esc collapses the selection; Ctrl-b -> tty (terminals). /// insert -> keys insert text at the cursor (acme click-and-type); Esc -> normal. -/// tty -> keys -> pty as terminal sequences; a body left-click returns to normal. +/// tty -> keys -> pty as terminal sequences. The mouse stays usable here (it +/// selects/copies without dropping you back to normal); Ctrl-b exits. pub const Mode = enum { normal, insert, tty }; /// A modal line selection (helix `x` extends the selection down line by line). @@ -29,6 +30,16 @@ pub const LineSel = struct { r1: i32 = 0, }; +/// A modal character-range selection (helix `v` select mode). The anchor is here +/// (row/col in the same coords as the cursor: ABSOLUTE body row; content col for +/// files, visual col for terminals); the head is the cursor. Motions extend it; +/// d/c/y act on [anchor, cursor] inclusive. Mutually exclusive with LineSel. +pub const CharSel = struct { + active: bool = false, + row: i32 = 0, + col: i32 = 0, +}; + pub const EDIT_CAP = 128; // max chars in one insertion run pub const MAX_EDITS = 16; // max concurrent insertion runs per window @@ -40,6 +51,11 @@ pub const File = struct { /// tree-sitter syntax styles, one byte per `content` byte (0 = none). Empty /// when the file's language isn't highlighted. gpa-owned. See main.highlightFile. highlights: []u8 = &.{}, + /// dumb whole-content undo/redo: each entry is a gpa-owned snapshot of + /// `content`. main.pushUndo snapshots before an edit (one per insert session / + /// delete / paste); `u`/`U` move between them. Bounded (oldest dropped). + undo: std.ArrayList([]u8) = .empty, + redo: std.ArrayList([]u8) = .empty, }; /// A run of inserted ("char mode") text. `col` is the ORIGINAL tty column it is @@ -52,6 +68,17 @@ pub const Edit = struct { text: [EDIT_CAP]u8 = undefined, }; +/// A dumb whole-state snapshot of a terminal pane's char-mode insertions, for +/// undo/redo (the terminal analogue of File's content snapshots). Plain value — +/// the edits array has no pointers — so it copies by assignment, no allocation. +pub const EditSnap = struct { + edits: [MAX_EDITS]Edit, + nedits: usize, + cur_row: i32, + cur_col: i32, + cur_off: u16, +}; + /// Persistent per-terminal state: the pty handle, the ghostty emulator and its /// (stateful) VT parser, the background reader task, plus its layout weight and /// text selection. @@ -100,6 +127,9 @@ mode: Mode = .normal, pending: u21 = 0, /// modal line selection from `x` (helix extend-line). In absolute body rows. msel: LineSel = .{}, +/// modal character-range selection from `v` (helix select). Anchor is here, head +/// is the cursor. Mutually exclusive with `msel`. +vsel: CharSel = .{}, /// cursor and edits are anchored at ABSOLUTE body rows (visual row + scroll /// offset) so they ride the scrollback, not the screen. `cur_off` is the offset /// into the run sitting at (cur_row, cur_col). `cur_pinned` is false until the @@ -114,6 +144,11 @@ cur_col: i32 = 0, cur_off: u16 = 0, nedits: usize = 0, edits: [MAX_EDITS]Edit = @splat(.{}), +/// terminal char-mode undo/redo: snapshots of the edit state, one per edit op +/// (insert session / delete / paste). File panes use File.undo/redo instead; +/// these stay empty for them. See main.pushUndo/doUndo. +ed_undo: std.ArrayList(EditSnap) = .empty, +ed_redo: std.ArrayList(EditSnap) = .empty, /// A real shell pane (has a pty/emulator), as opposed to a file or image view. pub fn isTerminal(self: *const Term) bool { @@ -121,16 +156,22 @@ pub fn isTerminal(self: *const Term) bool { } pub fn deinit(self: *Term, io: std.Io, gpa: std.mem.Allocator) void { - if (self.file) |f| { + if (self.file) |*f| { gpa.free(f.path); gpa.free(f.content); if (f.highlights.len > 0) gpa.free(f.highlights); + for (f.undo.items) |s| gpa.free(s); + for (f.redo.items) |s| gpa.free(s); + f.undo.deinit(gpa); + f.redo.deinit(gpa); } else if (self.image) |iv| { gpa.free(iv.path); } else { _ = self.reader.cancel(io) catch {}; self.pty.close(io); } + self.ed_undo.deinit(gpa); + self.ed_redo.deinit(gpa); self.stream.deinit(); self.term.deinit(gpa); } |
