aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/terminal/c/grid_ref.zig
blob: d029c59517d07e6f76182a166b4ea593323c4d1f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
const std = @import("std");
const testing = std.testing;
const lib = @import("../lib.zig");
const page = @import("../page.zig");
const PageList = @import("../PageList.zig");
const size = @import("../size.zig");
const stylepkg = @import("../style.zig");
const cell_c = @import("cell.zig");
const row_c = @import("row.zig");
const style_c = @import("style.zig");
const Result = @import("result.zig").Result;

/// C: GhosttyGridRef
///
/// A sized struct that holds a reference to a position in the terminal grid.
/// The ref points to a specific cell position within the terminal's
/// internal page structure.
pub const CGridRef = extern struct {
    size: usize = @sizeOf(CGridRef),
    node: ?*PageList.List.Node = null,
    x: size.CellCountInt = 0,
    y: size.CellCountInt = 0,

    pub fn fromPin(pin: PageList.Pin) CGridRef {
        return .{
            .node = pin.node,
            .x = pin.x,
            .y = pin.y,
        };
    }

    fn toPin(self: CGridRef) ?PageList.Pin {
        return .{
            .node = self.node orelse return null,
            .x = self.x,
            .y = self.y,
        };
    }
};

pub fn grid_ref_cell(
    ref: *const CGridRef,
    out: ?*cell_c.CCell,
) callconv(lib.calling_conv) Result {
    const p = ref.toPin() orelse return .invalid_value;
    if (out) |o| o.* = @bitCast(p.rowAndCell().cell.*);
    return .success;
}

pub fn grid_ref_row(
    ref: *const CGridRef,
    out: ?*row_c.CRow,
) callconv(lib.calling_conv) Result {
    const p = ref.toPin() orelse return .invalid_value;
    if (out) |o| o.* = @bitCast(p.rowAndCell().row.*);
    return .success;
}

pub fn grid_ref_graphemes(
    ref: *const CGridRef,
    out_buf: ?[*]u32,
    buf_len: usize,
    out_len: *usize,
) callconv(lib.calling_conv) Result {
    const p = ref.toPin() orelse return .invalid_value;
    const cell = p.rowAndCell().cell;

    if (!cell.hasText()) {
        out_len.* = 0;
        return .success;
    }

    const cp = cell.codepoint();
    const extra = if (cell.hasGrapheme()) p.grapheme(cell) else null;
    const total = 1 + if (extra) |e| e.len else 0;

    if (out_buf == null or buf_len < total) {
        out_len.* = total;
        return .out_of_space;
    }

    const buf = out_buf.?[0..buf_len];
    buf[0] = cp;
    if (extra) |e| for (e, 1..) |c, i| {
        buf[i] = c;
    };

    out_len.* = total;
    return .success;
}

pub fn grid_ref_style(
    ref: *const CGridRef,
    out: ?*style_c.Style,
) callconv(lib.calling_conv) Result {
    const p = ref.toPin() orelse return .invalid_value;
    if (out) |o| {
        const cell = p.rowAndCell().cell;
        if (cell.style_id == stylepkg.default_id) {
            o.* = .fromStyle(.{});
        } else {
            o.* = .fromStyle(p.node.data.styles.get(
                p.node.data.memory,
                cell.style_id,
            ).*);
        }
    }
    return .success;
}

test "grid_ref_cell null node" {
    const ref = CGridRef{};
    var out: cell_c.CCell = undefined;
    try testing.expectEqual(Result.invalid_value, grid_ref_cell(&ref, &out));
}

test "grid_ref_row null node" {
    const ref = CGridRef{};
    var out: row_c.CRow = undefined;
    try testing.expectEqual(Result.invalid_value, grid_ref_row(&ref, &out));
}

test "grid_ref_cell null out" {
    const ref = CGridRef{};
    try testing.expectEqual(Result.invalid_value, grid_ref_cell(&ref, null));
}

test "grid_ref_row null out" {
    const ref = CGridRef{};
    try testing.expectEqual(Result.invalid_value, grid_ref_row(&ref, null));
}

test "grid_ref_graphemes null node" {
    const ref = CGridRef{};
    var len: usize = undefined;
    try testing.expectEqual(Result.invalid_value, grid_ref_graphemes(&ref, null, 0, &len));
}

test "grid_ref_graphemes null buf returns out_of_space" {
    const ref = CGridRef{};
    var len: usize = undefined;
    // With null node this returns invalid_value before checking the buffer,
    // so we can only test null node here. Full buffer tests require a real page.
    try testing.expectEqual(Result.invalid_value, grid_ref_graphemes(&ref, null, 0, &len));
}

test "grid_ref_style null node" {
    const ref = CGridRef{};
    var out: style_c.Style = undefined;
    try testing.expectEqual(Result.invalid_value, grid_ref_style(&ref, &out));
}

test "grid_ref_style null out" {
    const ref = CGridRef{};
    try testing.expectEqual(Result.invalid_value, grid_ref_style(&ref, null));
}