mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-25 06:18:37 +00:00
libghostty: expose get/set active selection state
Add terminal set/get support for the active screen selection through the existing option and data APIs. Setting a selection copies the C snapshot into terminal-owned tracked state, while passing NULL clears the current selection. Getting the selection now returns an untracked GhosttySelection snapshot or GHOSTTY_NO_VALUE when there is no selection. The C header documents the different lifetimes for set and get so embedders know when input and returned grid references remain valid.
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
#include <ghostty/vt/kitty_graphics.h>
|
||||
#include <ghostty/vt/screen.h>
|
||||
#include <ghostty/vt/point.h>
|
||||
#include <ghostty/vt/selection.h>
|
||||
#include <ghostty/vt/style.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -592,6 +593,21 @@ typedef enum GHOSTTY_ENUM_TYPED {
|
||||
* Input type: size_t*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_APC_MAX_BYTES_KITTY = 20,
|
||||
|
||||
/**
|
||||
* Set the active screen selection.
|
||||
*
|
||||
* The value must point to a GhosttySelection whose grid references are
|
||||
* valid for this terminal's active screen at the time of the call. The
|
||||
* terminal copies the selection immediately and converts it to
|
||||
* terminal-owned tracked state, so the GhosttySelection struct and its
|
||||
* untracked grid references do not need to outlive this call.
|
||||
*
|
||||
* Passing NULL clears the active screen selection.
|
||||
*
|
||||
* Input type: GhosttySelection*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_SELECTION = 21,
|
||||
GHOSTTY_TERMINAL_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttyTerminalOption;
|
||||
|
||||
@@ -868,6 +884,23 @@ typedef enum GHOSTTY_ENUM_TYPED {
|
||||
* Output type: GhosttyKittyGraphics *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_KITTY_GRAPHICS = 30,
|
||||
|
||||
/**
|
||||
* The active screen's current selection.
|
||||
*
|
||||
* On success, writes an untracked snapshot of the terminal-owned selection
|
||||
* to the caller-provided GhosttySelection. The GhosttySelection struct is
|
||||
* caller-owned and may be kept, but the grid references inside it are
|
||||
* untracked borrowed references into the active screen. They are only valid
|
||||
* until the next mutating terminal call, such as ghostty_terminal_set(),
|
||||
* ghostty_terminal_vt_write(), ghostty_terminal_resize(), or
|
||||
* ghostty_terminal_reset().
|
||||
*
|
||||
* Returns GHOSTTY_NO_VALUE when there is no active selection.
|
||||
*
|
||||
* Output type: GhosttySelection *
|
||||
*/
|
||||
GHOSTTY_TERMINAL_DATA_SELECTION = 31,
|
||||
GHOSTTY_TERMINAL_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttyTerminalData;
|
||||
|
||||
|
||||
@@ -13,4 +13,12 @@ pub const CSelection = extern struct {
|
||||
const end_pin = self.end.toPin() orelse return null;
|
||||
return Selection.init(start_pin, end_pin, self.rectangle);
|
||||
}
|
||||
|
||||
pub fn fromZig(sel: Selection) CSelection {
|
||||
return .{
|
||||
.start = .fromPin(sel.start()),
|
||||
.end = .fromPin(sel.end()),
|
||||
.rectangle = sel.rectangle,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ 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;
|
||||
@@ -314,6 +315,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 {
|
||||
@@ -336,6 +338,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,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -443,6 +446,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;
|
||||
}
|
||||
@@ -576,6 +587,7 @@ pub const TerminalData = enum(c_int) {
|
||||
kitty_image_medium_temp_file = 28,
|
||||
kitty_image_medium_shared_mem = 29,
|
||||
kitty_graphics = 30,
|
||||
selection = 31,
|
||||
|
||||
/// Output type expected for querying the data of the given kind.
|
||||
pub fn OutType(comptime self: TerminalData) type {
|
||||
@@ -604,6 +616,7 @@ pub const TerminalData = enum(c_int) {
|
||||
.kitty_image_medium_shared_mem,
|
||||
=> bool,
|
||||
.kitty_graphics => KittyGraphics,
|
||||
.selection => selection_c.CSelection,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -713,6 +726,9 @@ 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,
|
||||
),
|
||||
}
|
||||
|
||||
return .success;
|
||||
@@ -1325,6 +1341,54 @@ 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 "grid_ref" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
Reference in New Issue
Block a user