aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/terminal/c/terminal.zig
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-01 23:05:23 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-01 23:05:51 +0300
commit5bd21b7ed468b5ff4d1a5b2ed7e211f401eb138a (patch)
tree672fa4625cca0ecb3e9376fc7f8e33d7b0e9deef /vendor/libghostty-vt/src/terminal/c/terminal.zig
parent32abb3776668ee768200019c170956f67fe33a86 (diff)
chore: update libghostty vendor
refs #276
Diffstat (limited to 'vendor/libghostty-vt/src/terminal/c/terminal.zig')
-rw-r--r--vendor/libghostty-vt/src/terminal/c/terminal.zig501
1 files changed, 492 insertions, 9 deletions
diff --git a/vendor/libghostty-vt/src/terminal/c/terminal.zig b/vendor/libghostty-vt/src/terminal/c/terminal.zig
index d3293f3b..5a5db2d6 100644
--- a/vendor/libghostty-vt/src/terminal/c/terminal.zig
+++ b/vendor/libghostty-vt/src/terminal/c/terminal.zig
@@ -3,7 +3,7 @@ const testing = std.testing;
const build_options = @import("terminal_options");
const lib = @import("../lib.zig");
const CAllocator = lib.alloc.Allocator;
-const ZigTerminal = @import("../Terminal.zig");
+pub const ZigTerminal = @import("../Terminal.zig");
const Stream = @import("../stream_terminal.zig").Stream;
const ScreenSet = @import("../ScreenSet.zig");
const PageList = @import("../PageList.zig");
@@ -19,6 +19,8 @@ const size_report = @import("../size_report.zig");
const cell_c = @import("cell.zig");
const row_c = @import("row.zig");
const grid_ref_c = @import("grid_ref.zig");
+const grid_ref_tracked_c = @import("grid_ref_tracked.zig");
+const selection_c = @import("selection.zig");
const style_c = @import("style.zig");
const color = @import("../color.zig");
const Result = @import("result.zig").Result;
@@ -34,6 +36,7 @@ const TerminalWrapper = struct {
terminal: *ZigTerminal,
stream: Stream,
effects: Effects = .{},
+ tracked_grid_refs: std.AutoArrayHashMapUnmanaged(*grid_ref_tracked_c.TrackedGridRef, void) = .{},
};
/// C callback state for terminal effects. Trampolines are always
@@ -208,6 +211,10 @@ const Effects = struct {
/// C: GhosttyTerminal
pub const Terminal = ?*TerminalWrapper;
+pub fn zigTerminal(terminal_: Terminal) ?*ZigTerminal {
+ return (terminal_ orelse return null).terminal;
+}
+
/// C: GhosttyTerminalOptions
pub const Options = extern struct {
cols: size.CellCountInt,
@@ -259,6 +266,11 @@ fn new_(
});
errdefer t.deinit(alloc);
+ // libghostty-vt embedders don't necessarily install Ghostty's shell
+ // integration, so don't assume OSC 133 prompts can be redrawn on resize.
+ // Shells can still opt in with OSC 133;A;redraw=1.
+ t.flags.shell_redraws_prompt = .false;
+
// Setup our stream with trampolines always installed so that
// setting C callbacks at any time takes effect immediately.
var handler: Stream.Handler = t.vtHandler();
@@ -313,6 +325,7 @@ pub const Option = enum(c_int) {
kitty_image_medium_shared_mem = 18,
apc_max_bytes = 19,
apc_max_bytes_kitty = 20,
+ selection = 21,
/// Input type expected for setting the option.
pub fn InType(comptime self: Option) type {
@@ -335,6 +348,7 @@ pub const Option = enum(c_int) {
.kitty_image_medium_shared_mem,
=> ?*const bool,
.apc_max_bytes, .apc_max_bytes_kitty => ?*const usize,
+ .selection => ?*const selection_c.CSelection,
};
}
};
@@ -442,6 +456,14 @@ fn setTyped(
wrapper.stream.handler.apc_handler.max_bytes.remove(.kitty);
}
},
+ .selection => {
+ if (value) |ptr| {
+ const sel = ptr.toZig() orelse return .invalid_value;
+ wrapper.terminal.screens.active.select(sel) catch return .out_of_memory;
+ } else {
+ wrapper.terminal.screens.active.clearSelection();
+ }
+ },
}
return .success;
}
@@ -575,13 +597,15 @@ pub const TerminalData = enum(c_int) {
kitty_image_medium_temp_file = 28,
kitty_image_medium_shared_mem = 29,
kitty_graphics = 30,
+ selection = 31,
+ viewport_active = 32,
/// Output type expected for querying the data of the given kind.
pub fn OutType(comptime self: TerminalData) type {
return switch (self) {
.invalid => void,
.cols, .rows, .cursor_x, .cursor_y => size.CellCountInt,
- .cursor_pending_wrap, .cursor_visible, .mouse_tracking => bool,
+ .cursor_pending_wrap, .cursor_visible, .mouse_tracking, .viewport_active => bool,
.active_screen => TerminalScreen,
.kitty_keyboard_flags => u8,
.scrollbar => TerminalScrollbar,
@@ -603,6 +627,7 @@ pub const TerminalData = enum(c_int) {
.kitty_image_medium_shared_mem,
=> bool,
.kitty_graphics => KittyGraphics,
+ .selection => selection_c.CSelection,
};
}
};
@@ -712,6 +737,10 @@ fn getTyped(
if (comptime !build_options.kitty_graphics) return .no_value;
out.* = &t.screens.active.kitty_images;
},
+ .selection => out.* = selection_c.CSelection.fromZig(
+ t.screens.active.selection orelse return .no_value,
+ ),
+ .viewport_active => out.* = t.screens.active.pages.viewport == .active,
}
return .success;
@@ -723,18 +752,56 @@ pub fn grid_ref(
out_ref: ?*grid_ref_c.CGridRef,
) callconv(lib.calling_conv) Result {
const t: *ZigTerminal = (terminal_ orelse return .invalid_value).terminal;
- const zig_pt: point.Point = switch (pt.tag) {
- .active => .{ .active = pt.value.active },
- .viewport => .{ .viewport = pt.value.viewport },
- .screen => .{ .screen = pt.value.screen },
- .history => .{ .history = pt.value.history },
- };
+ const zig_pt: point.Point = .fromC(pt);
const p = t.screens.active.pages.pin(zig_pt) orelse
return .invalid_value;
if (out_ref) |out| out.* = grid_ref_c.CGridRef.fromPin(p);
return .success;
}
+pub fn grid_ref_track(
+ terminal_: Terminal,
+ pt: point.Point.C,
+ out_ref: ?*grid_ref_tracked_c.CTrackedGridRef,
+) callconv(lib.calling_conv) Result {
+ const wrapper = terminal_ orelse return .invalid_value;
+ const out = out_ref orelse return .invalid_value;
+ out.* = null;
+
+ const t: *ZigTerminal = wrapper.terminal;
+ const list = &t.screens.active.pages;
+ const p = list.pin(.fromC(pt)) orelse return .invalid_value;
+ const tracked_pin = list.trackPin(p) catch return .out_of_memory;
+
+ const alloc = t.gpa();
+ const ref = alloc.create(grid_ref_tracked_c.TrackedGridRef) catch {
+ list.untrackPin(tracked_pin);
+ return .out_of_memory;
+ };
+ ref.* = .{
+ .alloc = alloc,
+ .terminal = wrapper,
+ .screen_key = t.screens.active_key,
+ .screen_generation = t.screens.generation(t.screens.active_key),
+ .pin = tracked_pin,
+ };
+
+ // Store the tracked ref in the terminal so that when we free
+ // the terminal the tracked ref can be detached safely.
+ wrapper.tracked_grid_refs.putNoClobber(
+ alloc,
+ ref,
+ {},
+ ) catch {
+ list.untrackPin(tracked_pin);
+ alloc.destroy(ref);
+ return .out_of_memory;
+ };
+
+ out.* = ref;
+ return .success;
+}
+
pub fn point_from_grid_ref(
terminal_: Terminal,
ref: *const grid_ref_c.CGridRef,
@@ -752,9 +819,11 @@ pub fn point_from_grid_ref(
pub fn free(terminal_: Terminal) callconv(lib.calling_conv) void {
const wrapper = terminal_ orelse return;
const t = wrapper.terminal;
+ const alloc = t.gpa();
+ for (wrapper.tracked_grid_refs.keys()) |ref| ref.terminal = null;
+ wrapper.tracked_grid_refs.deinit(alloc);
wrapper.stream.deinit();
- const alloc = t.gpa();
t.deinit(alloc);
alloc.destroy(t);
alloc.destroy(wrapper);
@@ -821,6 +890,10 @@ test "scroll_viewport" {
const zt = t.?.terminal;
+ var viewport_active: bool = false;
+ try testing.expectEqual(Result.success, get(t, .viewport_active, @ptrCast(&viewport_active)));
+ try testing.expect(viewport_active);
+
// Write "hello" on the first line
vt_write(t, "hello", 5);
@@ -835,6 +908,8 @@ test "scroll_viewport" {
// Scroll to top: "hello" should be visible again
scroll_viewport(t, .{ .tag = .top, .value = undefined });
+ try testing.expectEqual(Result.success, get(t, .viewport_active, @ptrCast(&viewport_active)));
+ try testing.expect(!viewport_active);
{
const str = try zt.plainString(testing.allocator);
defer testing.allocator.free(str);
@@ -843,6 +918,8 @@ test "scroll_viewport" {
// Scroll to bottom: viewport should be empty again
scroll_viewport(t, .{ .tag = .bottom, .value = undefined });
+ try testing.expectEqual(Result.success, get(t, .viewport_active, @ptrCast(&viewport_active)));
+ try testing.expect(viewport_active);
{
const str = try zt.plainString(testing.allocator);
defer testing.allocator.free(str);
@@ -851,6 +928,8 @@ test "scroll_viewport" {
// Scroll up by delta to bring "hello" back into view
scroll_viewport(t, .{ .tag = .delta, .value = .{ .delta = -3 } });
+ try testing.expectEqual(Result.success, get(t, .viewport_active, @ptrCast(&viewport_active)));
+ try testing.expect(!viewport_active);
{
const str = try zt.plainString(testing.allocator);
defer testing.allocator.free(str);
@@ -1298,6 +1377,410 @@ test "get invalid" {
try testing.expectEqual(Result.invalid_value, get(t, .invalid, null));
}
+test "set and get selection" {
+ var t: Terminal = null;
+ try testing.expectEqual(Result.success, new(
+ &lib.alloc.test_allocator,
+ &t,
+ .{
+ .cols = 80,
+ .rows = 24,
+ .max_scrollback = 0,
+ },
+ ));
+ defer free(t);
+
+ vt_write(t, "Hello", 5);
+
+ var start_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 0, .y = 0 } },
+ }, &start_ref));
+
+ var end_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 4, .y = 0 } },
+ }, &end_ref));
+
+ var out: selection_c.CSelection = undefined;
+ try testing.expectEqual(Result.no_value, get(t, .selection, @ptrCast(&out)));
+
+ const sel: selection_c.CSelection = .{
+ .start = start_ref,
+ .end = end_ref,
+ .rectangle = true,
+ };
+ try testing.expectEqual(Result.success, set(t, .selection, @ptrCast(&sel)));
+ try testing.expect(t.?.terminal.screens.active.selection.?.tracked());
+
+ try testing.expectEqual(Result.success, get(t, .selection, @ptrCast(&out)));
+ try testing.expect(out.start.toPin().?.eql(start_ref.toPin().?));
+ try testing.expect(out.end.toPin().?.eql(end_ref.toPin().?));
+ try testing.expect(out.rectangle);
+
+ try testing.expectEqual(Result.success, set(t, .selection, null));
+ try testing.expect(t.?.terminal.screens.active.selection == null);
+ try testing.expectEqual(Result.no_value, get(t, .selection, @ptrCast(&out)));
+}
+
+test "selection derivation helpers" {
+ var t: Terminal = null;
+ try testing.expectEqual(Result.success, new(
+ &lib.alloc.test_allocator,
+ &t,
+ .{
+ .cols = 80,
+ .rows = 24,
+ .max_scrollback = 0,
+ },
+ ));
+ defer free(t);
+
+ vt_write(t, " Hello \r\nWorld", 16);
+
+ var out: selection_c.CSelection = undefined;
+
+ var word_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 3, .y = 0 } },
+ }, &word_ref));
+
+ var empty_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 20, .y = 0 } },
+ }, &empty_ref));
+
+ var line_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 0, .y = 0 } },
+ }, &line_ref));
+
+ var word_opts: selection_c.SelectWordOptions = .{
+ .ref = word_ref,
+ };
+ try testing.expectEqual(Result.success, selection_c.word(t, &word_opts, &out));
+ try testing.expectEqual(@as(u16, 2), out.start.toPin().?.x);
+ try testing.expectEqual(@as(u16, 6), out.end.toPin().?.x);
+
+ word_opts.ref = empty_ref;
+ try testing.expectEqual(Result.no_value, selection_c.word(t, &word_opts, &out));
+
+ var between_start_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 20, .y = 1 } },
+ }, &between_start_ref));
+
+ var between_end_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 0, .y = 1 } },
+ }, &between_end_ref));
+
+ var word_between_opts: selection_c.SelectWordBetweenOptions = .{
+ .start = between_start_ref,
+ .end = between_end_ref,
+ };
+ try testing.expectEqual(Result.success, selection_c.word_between(t, &word_between_opts, &out));
+ try testing.expectEqual(@as(u16, 0), out.start.toPin().?.x);
+ try testing.expectEqual(@as(u16, 1), out.start.toPin().?.y);
+ try testing.expectEqual(@as(u16, 4), out.end.toPin().?.x);
+ try testing.expectEqual(@as(u16, 1), out.end.toPin().?.y);
+
+ var line_opts: selection_c.SelectLineOptions = .{
+ .ref = line_ref,
+ };
+ try testing.expectEqual(Result.success, selection_c.line(t, &line_opts, &out));
+ try testing.expectEqual(@as(u16, 2), out.start.toPin().?.x);
+ try testing.expectEqual(@as(u16, 6), out.end.toPin().?.x);
+
+ try testing.expectEqual(Result.success, selection_c.all(t, &out));
+ try testing.expectEqual(@as(u16, 2), out.start.toPin().?.x);
+ try testing.expectEqual(@as(u16, 0), out.start.toPin().?.y);
+ try testing.expectEqual(@as(u16, 4), out.end.toPin().?.x);
+ try testing.expectEqual(@as(u16, 1), out.end.toPin().?.y);
+
+ try testing.expectEqual(Result.no_value, selection_c.output(t, line_ref, &out));
+
+ line_opts.size = @sizeOf(usize) - 1;
+ try testing.expectEqual(Result.invalid_value, selection_c.line(t, &line_opts, &out));
+ try testing.expectEqual(Result.invalid_value, selection_c.word(t, null, &out));
+ try testing.expectEqual(Result.invalid_value, selection_c.word(t, &word_opts, null));
+ try testing.expectEqual(Result.invalid_value, selection_c.word_between(t, null, &out));
+ try testing.expectEqual(Result.invalid_value, selection_c.word_between(t, &word_between_opts, null));
+}
+
+test "selection_adjust mutates snapshot end" {
+ var t: Terminal = null;
+ try testing.expectEqual(Result.success, new(
+ &lib.alloc.test_allocator,
+ &t,
+ .{
+ .cols = 80,
+ .rows = 24,
+ .max_scrollback = 0,
+ },
+ ));
+ defer free(t);
+
+ vt_write(t, "Hello", 5);
+
+ var start_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 0, .y = 0 } },
+ }, &start_ref));
+
+ var end_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 1, .y = 0 } },
+ }, &end_ref));
+
+ var sel: selection_c.CSelection = .{
+ .start = start_ref,
+ .end = end_ref,
+ };
+ try testing.expectEqual(Result.success, selection_c.adjust(t, &sel, .right));
+ try testing.expectEqual(@as(u16, 0), sel.start.toPin().?.x);
+ try testing.expectEqual(@as(u16, 2), sel.end.toPin().?.x);
+
+ try testing.expectEqual(Result.success, selection_c.adjust(t, &sel, .left));
+ try testing.expectEqual(@as(u16, 0), sel.start.toPin().?.x);
+ try testing.expectEqual(@as(u16, 1), sel.end.toPin().?.x);
+
+ sel = .{
+ .start = end_ref,
+ .end = start_ref,
+ };
+ try testing.expectEqual(Result.success, selection_c.adjust(t, &sel, .right));
+ try testing.expectEqual(@as(u16, 1), sel.start.toPin().?.x);
+ try testing.expectEqual(@as(u16, 1), sel.end.toPin().?.x);
+}
+
+test "selection_order and selection_ordered" {
+ var t: Terminal = null;
+ try testing.expectEqual(Result.success, new(
+ &lib.alloc.test_allocator,
+ &t,
+ .{
+ .cols = 80,
+ .rows = 24,
+ .max_scrollback = 0,
+ },
+ ));
+ defer free(t);
+
+ vt_write(t, "Hello\r\nWorld", 12);
+
+ var start_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 3, .y = 0 } },
+ }, &start_ref));
+
+ var end_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 1, .y = 1 } },
+ }, &end_ref));
+
+ const sel: selection_c.CSelection = .{
+ .start = start_ref,
+ .end = end_ref,
+ .rectangle = true,
+ };
+
+ var order: selection_c.Order = undefined;
+ try testing.expectEqual(Result.success, selection_c.order(t, &sel, &order));
+ try testing.expectEqual(selection_c.Order.mirrored_forward, order);
+
+ var out: selection_c.CSelection = undefined;
+ try testing.expectEqual(Result.success, selection_c.ordered(t, &sel, .forward, &out));
+ try testing.expectEqual(@as(u16, 1), out.start.toPin().?.x);
+ try testing.expectEqual(@as(u16, 0), out.start.toPin().?.y);
+ try testing.expectEqual(@as(u16, 3), out.end.toPin().?.x);
+ try testing.expectEqual(@as(u16, 1), out.end.toPin().?.y);
+ try testing.expect(out.rectangle);
+
+ try testing.expectEqual(Result.success, selection_c.ordered(t, &sel, .reverse, &out));
+ try testing.expectEqual(@as(u16, 3), out.start.toPin().?.x);
+ try testing.expectEqual(@as(u16, 1), out.start.toPin().?.y);
+ try testing.expectEqual(@as(u16, 1), out.end.toPin().?.x);
+ try testing.expectEqual(@as(u16, 0), out.end.toPin().?.y);
+ try testing.expect(out.rectangle);
+}
+
+test "selection_contains" {
+ var t: Terminal = null;
+ try testing.expectEqual(Result.success, new(
+ &lib.alloc.test_allocator,
+ &t,
+ .{
+ .cols = 80,
+ .rows = 24,
+ .max_scrollback = 0,
+ },
+ ));
+ defer free(t);
+
+ vt_write(t, "Hello\r\nWorld", 12);
+
+ var start_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 3, .y = 0 } },
+ }, &start_ref));
+
+ var end_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 1, .y = 1 } },
+ }, &end_ref));
+
+ const linear: selection_c.CSelection = .{
+ .start = start_ref,
+ .end = end_ref,
+ };
+
+ var contains: bool = undefined;
+ try testing.expectEqual(Result.success, selection_c.contains(t, &linear, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 4, .y = 0 } },
+ }, &contains));
+ try testing.expect(contains);
+
+ try testing.expectEqual(Result.success, selection_c.contains(t, &linear, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 2, .y = 0 } },
+ }, &contains));
+ try testing.expect(!contains);
+
+ const rectangle: selection_c.CSelection = .{
+ .start = start_ref,
+ .end = end_ref,
+ .rectangle = true,
+ };
+
+ try testing.expectEqual(Result.success, selection_c.contains(t, &rectangle, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 2, .y = 0 } },
+ }, &contains));
+ try testing.expect(contains);
+}
+
+test "selection_equal" {
+ var t: Terminal = null;
+ try testing.expectEqual(Result.success, new(
+ &lib.alloc.test_allocator,
+ &t,
+ .{
+ .cols = 80,
+ .rows = 24,
+ .max_scrollback = 0,
+ },
+ ));
+ defer free(t);
+
+ var other_t: Terminal = null;
+ try testing.expectEqual(Result.success, new(
+ &lib.alloc.test_allocator,
+ &other_t,
+ .{
+ .cols = 80,
+ .rows = 24,
+ .max_scrollback = 0,
+ },
+ ));
+ defer free(other_t);
+
+ vt_write(t, "Hello", 5);
+ vt_write(other_t, "Hello", 5);
+
+ var start_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 0, .y = 0 } },
+ }, &start_ref));
+
+ var end_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 1, .y = 0 } },
+ }, &end_ref));
+
+ var other_end_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 2, .y = 0 } },
+ }, &other_end_ref));
+
+ var cross_terminal_ref: grid_ref_c.CGridRef = .{};
+ try testing.expectEqual(Result.success, grid_ref(other_t, .{
+ .tag = .active,
+ .value = .{ .active = .{ .x = 1, .y = 0 } },
+ }, &cross_terminal_ref));
+
+ const sel: selection_c.CSelection = .{
+ .start = start_ref,
+ .end = end_ref,
+ };
+ const equal_sel: selection_c.CSelection = .{
+ .start = start_ref,
+ .end = end_ref,
+ };
+ const different_endpoint: selection_c.CSelection = .{
+ .start = start_ref,
+ .end = other_end_ref,
+ };
+ const different_rectangle: selection_c.CSelection = .{
+ .start = start_ref,
+ .end = end_ref,
+ .rectangle = true,
+ };
+ const cross_terminal: selection_c.CSelection = .{
+ .start = start_ref,
+ .end = cross_terminal_ref,
+ };
+
+ var equal: bool = undefined;
+ try testing.expectEqual(Result.success, selection_c.equal(t, &sel, &equal_sel, &equal));
+ try testing.expect(equal);
+
+ try testing.expectEqual(Result.success, selection_c.equal(t, &sel, &different_endpoint, &equal));
+ try testing.expect(!equal);
+
+ try testing.expectEqual(Result.success, selection_c.equal(t, &sel, &different_rectangle, &equal));
+ try testing.expect(!equal);
+
+ try testing.expectEqual(Result.success, selection_c.equal(t, &sel, &cross_terminal, &equal));
+ try testing.expect(!equal);
+ try testing.expectEqual(Result.invalid_value, selection_c.equal(t, &sel, &equal_sel, null));
+}
+
+test "selection_order invalid values" {
+ var t: Terminal = null;
+ try testing.expectEqual(Result.success, new(
+ &lib.alloc.test_allocator,
+ &t,
+ .{
+ .cols = 80,
+ .rows = 24,
+ .max_scrollback = 0,
+ },
+ ));
+ defer free(t);
+
+ var order: selection_c.Order = undefined;
+ try testing.expectEqual(Result.invalid_value, selection_c.order(null, null, &order));
+ try testing.expectEqual(Result.invalid_value, selection_c.order(t, null, &order));
+}
+
test "grid_ref" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(