const std = @import("std"); const testing = std.testing; const lib = @import("../lib.zig"); const PageList = @import("../PageList.zig"); const point = @import("../point.zig"); const grid_ref_c = @import("grid_ref.zig"); const terminal_c = @import("terminal.zig"); const Result = @import("result.zig").Result; /// C: GhosttyTrackedGridRef /// /// An owned tracked reference to a position in the terminal grid. The /// underlying PageList pin is automatically updated as the PageList changes. pub const CTrackedGridRef = ?*TrackedGridRef; pub const TrackedGridRef = struct { alloc: std.mem.Allocator, terminal: terminal_c.Terminal, screen_key: terminal_c.TerminalScreen, screen_generation: usize, pin: *PageList.Pin, /// Return the PageList that owns this tracked ref's pin, or null if the /// owning screen has been removed/reinitialized since the ref was created. fn pageList(ref: *const TrackedGridRef) ?*PageList { const wrapper = ref.terminal orelse return null; const t = wrapper.terminal; if (t.screens.generation(ref.screen_key) != ref.screen_generation) return null; const screen = t.screens.get(ref.screen_key) orelse return null; return &screen.pages; } }; pub fn tracked_grid_ref_free(ref_: CTrackedGridRef) callconv(lib.calling_conv) void { const ref = ref_ orelse return; if (ref.terminal) |wrapper| { _ = wrapper.tracked_grid_refs.swapRemove(ref); } if (ref.pageList()) |list| list.untrackPin(ref.pin); ref.alloc.destroy(ref); } pub fn tracked_grid_ref_has_value(ref_: CTrackedGridRef) callconv(lib.calling_conv) bool { const ref = ref_ orelse return false; _ = ref.pageList() orelse return false; return !ref.pin.garbage; } pub fn tracked_grid_ref_snapshot( ref_: CTrackedGridRef, out_ref: ?*grid_ref_c.CGridRef, ) callconv(lib.calling_conv) Result { const ref = ref_ orelse return .invalid_value; _ = ref.pageList() orelse return .no_value; if (ref.pin.garbage) return .no_value; if (out_ref) |out| out.* = grid_ref_c.CGridRef.fromPin(ref.pin.*); return .success; } pub fn tracked_grid_ref_point( ref_: CTrackedGridRef, tag: point.Tag, out: ?*point.Coordinate, ) callconv(lib.calling_conv) Result { const ref = ref_ orelse return .invalid_value; const list = ref.pageList() orelse return .no_value; if (ref.pin.garbage) return .no_value; const pt = list.pointFromPin(tag, ref.pin.*) orelse return .no_value; if (out) |o| o.* = pt.coord(); return .success; } pub fn tracked_grid_ref_set( ref_: CTrackedGridRef, terminal_: terminal_c.Terminal, pt: point.Point.C, ) callconv(lib.calling_conv) Result { const ref = ref_ orelse return .invalid_value; const wrapper = terminal_ orelse return .invalid_value; if (ref.terminal != terminal_) return .invalid_value; const t = wrapper.terminal; const list = &t.screens.active.pages; const p = list.pin(point.Point.fromC(pt)) orelse return .invalid_value; const tracked_pin = list.trackPin(p) catch return .out_of_memory; if (ref.pageList()) |old_list| old_list.untrackPin(ref.pin); ref.screen_key = t.screens.active_key; ref.screen_generation = t.screens.generation(ref.screen_key); ref.pin = tracked_pin; return .success; } test "tracked_grid_ref snapshots after terminal scroll" { var terminal: terminal_c.Terminal = null; try testing.expectEqual(Result.success, terminal_c.new( &lib.alloc.test_allocator, &terminal, 5, 2, )); defer terminal_c.free(terminal); terminal_c.vt_write(terminal, "A", 1); var ref: CTrackedGridRef = null; try testing.expectEqual(Result.success, terminal_c.grid_ref_track( terminal, point.Point.cval(.{ .active = .{ .x = 0, .y = 0 } }), &ref, )); defer tracked_grid_ref_free(ref); terminal_c.vt_write(terminal, "\r\nB\r\nC", 6); try testing.expect(tracked_grid_ref_has_value(ref)); var snapshot: grid_ref_c.CGridRef = undefined; try testing.expectEqual(Result.success, tracked_grid_ref_snapshot(ref, &snapshot)); var buf: [1]u32 = undefined; var len: usize = undefined; try testing.expectEqual(Result.success, grid_ref_c.grid_ref_graphemes(&snapshot, &buf, buf.len, &len)); try testing.expectEqual(@as(usize, 1), len); try testing.expectEqual(@as(u32, 'A'), buf[0]); } test "tracked_grid_ref reports no value after reset" { var terminal: terminal_c.Terminal = null; try testing.expectEqual(Result.success, terminal_c.new( &lib.alloc.test_allocator, &terminal, 5, 2, )); defer terminal_c.free(terminal); terminal_c.vt_write(terminal, "A", 1); var ref: CTrackedGridRef = null; try testing.expectEqual(Result.success, terminal_c.grid_ref_track( terminal, point.Point.cval(.{ .active = .{ .x = 0, .y = 0 } }), &ref, )); defer tracked_grid_ref_free(ref); terminal_c.reset(terminal); try testing.expect(!tracked_grid_ref_has_value(ref)); var snapshot: grid_ref_c.CGridRef = undefined; try testing.expectEqual(Result.no_value, tracked_grid_ref_snapshot(ref, &snapshot)); } test "tracked_grid_ref reports no value after alternate screen reset" { var terminal: terminal_c.Terminal = null; try testing.expectEqual(Result.success, terminal_c.new( &lib.alloc.test_allocator, &terminal, 5, 2, )); defer terminal_c.free(terminal); terminal_c.vt_write(terminal, "\x1b[?1049hA", 9); var ref: CTrackedGridRef = null; try testing.expectEqual(Result.success, terminal_c.grid_ref_track( terminal, point.Point.cval(.{ .active = .{ .x = 0, .y = 0 } }), &ref, )); defer tracked_grid_ref_free(ref); terminal_c.vt_write(terminal, "\x1bc", 2); try testing.expect(!tracked_grid_ref_has_value(ref)); var snapshot: grid_ref_c.CGridRef = undefined; try testing.expectEqual(Result.no_value, tracked_grid_ref_snapshot(ref, &snapshot)); var coord: point.Coordinate = undefined; try testing.expectEqual(Result.no_value, tracked_grid_ref_point(ref, .active, &coord)); terminal_c.vt_write(terminal, "\x1b[?1049h", 8); try testing.expect(!tracked_grid_ref_has_value(ref)); try testing.expectEqual(Result.success, tracked_grid_ref_set( ref, terminal, point.Point.cval(.{ .active = .{ .x = 0, .y = 0 } }), )); try testing.expect(tracked_grid_ref_has_value(ref)); try testing.expectEqual(Result.success, tracked_grid_ref_snapshot(ref, &snapshot)); } test "tracked_grid_ref reports no value after terminal free" { var terminal: terminal_c.Terminal = null; try testing.expectEqual(Result.success, terminal_c.new( &lib.alloc.test_allocator, &terminal, 5, 2, )); terminal_c.vt_write(terminal, "A", 1); var ref: CTrackedGridRef = null; try testing.expectEqual(Result.success, terminal_c.grid_ref_track( terminal, point.Point.cval(.{ .active = .{ .x = 0, .y = 0 } }), &ref, )); terminal_c.free(terminal); try testing.expect(!tracked_grid_ref_has_value(ref)); var snapshot: grid_ref_c.CGridRef = undefined; try testing.expectEqual(Result.no_value, tracked_grid_ref_snapshot(ref, &snapshot)); var coord: point.Coordinate = undefined; try testing.expectEqual(Result.no_value, tracked_grid_ref_point(ref, .active, &coord)); try testing.expectEqual(Result.invalid_value, tracked_grid_ref_set( ref, terminal, point.Point.cval(.{ .active = .{ .x = 0, .y = 0 } }), )); tracked_grid_ref_free(ref); }