mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-25 06:18:37 +00:00
libghostty: selectWordBetween in C
This commit is contained in:
@@ -76,6 +76,40 @@ int main() {
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
print_selection(terminal, "word", &selection);
|
||||
|
||||
//! [selection-word-between]
|
||||
// Double-click-and-drag style selection. Suppose the user double-clicks
|
||||
// "git" and drags to "status". The pointer may pass over whitespace, so
|
||||
// select the nearest word between the original click and current drag point
|
||||
// in both directions, then combine the outer word bounds.
|
||||
GhosttyGridRef click_ref = ref_at(terminal, 2, 0); // the "git" in "git status"
|
||||
GhosttyGridRef drag_ref = ref_at(terminal, 6, 0); // the "status" in "git status"
|
||||
|
||||
GhosttyTerminalSelectWordBetweenOptions start_word_opts =
|
||||
GHOSTTY_INIT_SIZED(GhosttyTerminalSelectWordBetweenOptions);
|
||||
start_word_opts.start = click_ref;
|
||||
start_word_opts.end = drag_ref;
|
||||
|
||||
GhosttySelection start_word = GHOSTTY_INIT_SIZED(GhosttySelection);
|
||||
result = ghostty_terminal_select_word_between(
|
||||
terminal, &start_word_opts, &start_word);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
GhosttyTerminalSelectWordBetweenOptions end_word_opts =
|
||||
GHOSTTY_INIT_SIZED(GhosttyTerminalSelectWordBetweenOptions);
|
||||
end_word_opts.start = drag_ref;
|
||||
end_word_opts.end = click_ref;
|
||||
|
||||
GhosttySelection end_word = GHOSTTY_INIT_SIZED(GhosttySelection);
|
||||
result = ghostty_terminal_select_word_between(
|
||||
terminal, &end_word_opts, &end_word);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
GhosttySelection drag_selection = GHOSTTY_INIT_SIZED(GhosttySelection);
|
||||
drag_selection.start = start_word.start;
|
||||
drag_selection.end = end_word.end;
|
||||
print_selection(terminal, "double-click drag", &drag_selection);
|
||||
//! [selection-word-between]
|
||||
|
||||
// Triple-click style line selection. With semantic prompt boundaries enabled,
|
||||
// this selects only the input area rather than the leading "$ " prompt.
|
||||
GhosttyTerminalSelectLineOptions line = GHOSTTY_INIT_SIZED(GhosttyTerminalSelectLineOptions);
|
||||
|
||||
@@ -107,6 +107,33 @@ typedef struct {
|
||||
size_t boundary_codepoints_len;
|
||||
} GhosttyTerminalSelectWordOptions;
|
||||
|
||||
/**
|
||||
* Options for deriving the nearest word selection between two grid references.
|
||||
*
|
||||
* This is a sized struct. Use GHOSTTY_INIT_SIZED() to initialize it.
|
||||
* If boundary_codepoints is NULL and boundary_codepoints_len is 0, Ghostty's
|
||||
* default word-boundary codepoints are used. If boundary_codepoints_len is
|
||||
* non-zero, boundary_codepoints must not be NULL.
|
||||
*
|
||||
* @ingroup selection
|
||||
*/
|
||||
typedef struct {
|
||||
/** Size of this struct in bytes. Must be set to sizeof(GhosttyTerminalSelectWordBetweenOptions). */
|
||||
size_t size;
|
||||
|
||||
/** Starting grid reference for the inclusive search range. */
|
||||
GhosttyGridRef start;
|
||||
|
||||
/** Ending grid reference for the inclusive search range. */
|
||||
GhosttyGridRef end;
|
||||
|
||||
/** Optional word-boundary codepoints as uint32_t scalar values. */
|
||||
const uint32_t* boundary_codepoints;
|
||||
|
||||
/** Number of entries in boundary_codepoints. */
|
||||
size_t boundary_codepoints_len;
|
||||
} GhosttyTerminalSelectWordBetweenOptions;
|
||||
|
||||
/**
|
||||
* Options for deriving a line selection from a terminal grid reference.
|
||||
*
|
||||
@@ -235,6 +262,40 @@ GHOSTTY_API GhosttyResult ghostty_terminal_select_word(
|
||||
const GhosttyTerminalSelectWordOptions* options,
|
||||
GhosttySelection* out_selection);
|
||||
|
||||
/**
|
||||
* Derive the nearest word selection snapshot between two terminal grid refs.
|
||||
*
|
||||
* Starting at options->start, this searches toward options->end (inclusive)
|
||||
* and returns the first selectable word found using Ghostty's word-selection
|
||||
* rules.
|
||||
*
|
||||
* This is useful for implementing double-click-and-drag selection in a UI. If
|
||||
* a user double-clicks one word and drags across spaces or punctuation toward
|
||||
* another word, selecting only the word directly under the current pointer can
|
||||
* flicker or collapse when the pointer is between words. Instead, ask for the
|
||||
* nearest word between the original click and the drag point, ask again in the
|
||||
* reverse direction, and combine the two word bounds into the drag selection.
|
||||
*
|
||||
* @snippet c-vt-selection/src/main.c selection-word-between
|
||||
*
|
||||
* The returned selection is not installed as the terminal's current
|
||||
* selection. It is a snapshot with the same lifetime rules as GhosttySelection.
|
||||
*
|
||||
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param options Word-between-selection options
|
||||
* @param[out] out_selection On success, receives the derived selection
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if there is no
|
||||
* selectable word content between the valid refs, or
|
||||
* GHOSTTY_INVALID_VALUE if the terminal, options, refs, codepoint
|
||||
* pointer, or output pointer are invalid.
|
||||
*
|
||||
* @ingroup selection
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_terminal_select_word_between(
|
||||
GhosttyTerminal terminal,
|
||||
const GhosttyTerminalSelectWordBetweenOptions* options,
|
||||
GhosttySelection* out_selection);
|
||||
|
||||
/**
|
||||
* Derive a line selection snapshot from a terminal grid reference.
|
||||
*
|
||||
|
||||
@@ -240,6 +240,7 @@ comptime {
|
||||
@export(&c.terminal_get, .{ .name = "ghostty_terminal_get" });
|
||||
@export(&c.terminal_get_multi, .{ .name = "ghostty_terminal_get_multi" });
|
||||
@export(&c.terminal_select_word, .{ .name = "ghostty_terminal_select_word" });
|
||||
@export(&c.terminal_select_word_between, .{ .name = "ghostty_terminal_select_word_between" });
|
||||
@export(&c.terminal_select_line, .{ .name = "ghostty_terminal_select_line" });
|
||||
@export(&c.terminal_select_all, .{ .name = "ghostty_terminal_select_all" });
|
||||
@export(&c.terminal_select_output, .{ .name = "ghostty_terminal_select_output" });
|
||||
|
||||
@@ -171,6 +171,7 @@ pub const terminal_mode_set = terminal.mode_set;
|
||||
pub const terminal_get = terminal.get;
|
||||
pub const terminal_get_multi = terminal.get_multi;
|
||||
pub const terminal_select_word = selection.word;
|
||||
pub const terminal_select_word_between = selection.word_between;
|
||||
pub const terminal_select_line = selection.line;
|
||||
pub const terminal_select_all = selection.all;
|
||||
pub const terminal_select_output = selection.output;
|
||||
|
||||
@@ -43,6 +43,15 @@ pub const SelectWordOptions = extern struct {
|
||||
boundary_codepoints_len: usize = 0,
|
||||
};
|
||||
|
||||
/// C: GhosttyTerminalSelectWordBetweenOptions
|
||||
pub const SelectWordBetweenOptions = extern struct {
|
||||
size: usize = @sizeOf(SelectWordBetweenOptions),
|
||||
start: grid_ref.CGridRef,
|
||||
end: grid_ref.CGridRef,
|
||||
boundary_codepoints: ?[*]const u32 = null,
|
||||
boundary_codepoints_len: usize = 0,
|
||||
};
|
||||
|
||||
/// C: GhosttyTerminalSelectLineOptions
|
||||
pub const SelectLineOptions = extern struct {
|
||||
size: usize = @sizeOf(SelectLineOptions),
|
||||
@@ -77,6 +86,33 @@ pub fn word(
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn word_between(
|
||||
terminal: terminal_c.Terminal,
|
||||
options: ?*const SelectWordBetweenOptions,
|
||||
out_selection: ?*CSelection,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
const t = terminal_c.zigTerminal(terminal) orelse return .invalid_value;
|
||||
const opts = options orelse return .invalid_value;
|
||||
if (opts.size < @sizeOf(SelectWordBetweenOptions)) return .invalid_value;
|
||||
const out = out_selection orelse return .invalid_value;
|
||||
|
||||
const boundary_codepoints = codepointSlice(
|
||||
opts.boundary_codepoints,
|
||||
opts.boundary_codepoints_len,
|
||||
) catch return .invalid_value;
|
||||
|
||||
const screen = t.screens.active;
|
||||
const start = opts.start.toPin() orelse return .invalid_value;
|
||||
const end = opts.end.toPin() orelse return .invalid_value;
|
||||
out.* = .fromZig(screen.selectWordBetween(
|
||||
start,
|
||||
end,
|
||||
boundary_codepoints orelse &selection_codepoints.default_word_boundaries,
|
||||
) orelse
|
||||
return .no_value);
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn line(
|
||||
terminal: terminal_c.Terminal,
|
||||
options: ?*const SelectLineOptions,
|
||||
|
||||
@@ -1438,6 +1438,28 @@ test "selection derivation helpers" {
|
||||
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,
|
||||
};
|
||||
@@ -1457,6 +1479,8 @@ test "selection derivation helpers" {
|
||||
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" {
|
||||
|
||||
@@ -31,6 +31,7 @@ pub const structs: std.StaticStringMap(StructInfo) = structs: {
|
||||
.{ "GhosttyFormatterTerminalOptions", StructInfo.init(formatter.TerminalOptions) },
|
||||
.{ "GhosttySelection", StructInfo.init(selection.CSelection) },
|
||||
.{ "GhosttyTerminalSelectWordOptions", StructInfo.init(selection.SelectWordOptions) },
|
||||
.{ "GhosttyTerminalSelectWordBetweenOptions", StructInfo.init(selection.SelectWordBetweenOptions) },
|
||||
.{ "GhosttyTerminalSelectLineOptions", StructInfo.init(selection.SelectLineOptions) },
|
||||
.{ "GhosttyFormatterTerminalExtra", StructInfo.init(formatter.TerminalOptions.Extra) },
|
||||
.{ "GhosttyFormatterScreenExtra", StructInfo.init(formatter.ScreenOptions.Extra) },
|
||||
|
||||
Reference in New Issue
Block a user