libghostty: selectWordBetween in C
parent
e8f5353912
commit
eb777b8036
|
|
@ -76,6 +76,40 @@ int main() {
|
||||||
assert(result == GHOSTTY_SUCCESS);
|
assert(result == GHOSTTY_SUCCESS);
|
||||||
print_selection(terminal, "word", &selection);
|
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,
|
// Triple-click style line selection. With semantic prompt boundaries enabled,
|
||||||
// this selects only the input area rather than the leading "$ " prompt.
|
// this selects only the input area rather than the leading "$ " prompt.
|
||||||
GhosttyTerminalSelectLineOptions line = GHOSTTY_INIT_SIZED(GhosttyTerminalSelectLineOptions);
|
GhosttyTerminalSelectLineOptions line = GHOSTTY_INIT_SIZED(GhosttyTerminalSelectLineOptions);
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,33 @@ typedef struct {
|
||||||
size_t boundary_codepoints_len;
|
size_t boundary_codepoints_len;
|
||||||
} GhosttyTerminalSelectWordOptions;
|
} 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.
|
* 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,
|
const GhosttyTerminalSelectWordOptions* options,
|
||||||
GhosttySelection* out_selection);
|
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.
|
* 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, .{ .name = "ghostty_terminal_get" });
|
||||||
@export(&c.terminal_get_multi, .{ .name = "ghostty_terminal_get_multi" });
|
@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, .{ .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_line, .{ .name = "ghostty_terminal_select_line" });
|
||||||
@export(&c.terminal_select_all, .{ .name = "ghostty_terminal_select_all" });
|
@export(&c.terminal_select_all, .{ .name = "ghostty_terminal_select_all" });
|
||||||
@export(&c.terminal_select_output, .{ .name = "ghostty_terminal_select_output" });
|
@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 = terminal.get;
|
||||||
pub const terminal_get_multi = terminal.get_multi;
|
pub const terminal_get_multi = terminal.get_multi;
|
||||||
pub const terminal_select_word = selection.word;
|
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_line = selection.line;
|
||||||
pub const terminal_select_all = selection.all;
|
pub const terminal_select_all = selection.all;
|
||||||
pub const terminal_select_output = selection.output;
|
pub const terminal_select_output = selection.output;
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,15 @@ pub const SelectWordOptions = extern struct {
|
||||||
boundary_codepoints_len: usize = 0,
|
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
|
/// C: GhosttyTerminalSelectLineOptions
|
||||||
pub const SelectLineOptions = extern struct {
|
pub const SelectLineOptions = extern struct {
|
||||||
size: usize = @sizeOf(SelectLineOptions),
|
size: usize = @sizeOf(SelectLineOptions),
|
||||||
|
|
@ -77,6 +86,33 @@ pub fn word(
|
||||||
return .success;
|
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(
|
pub fn line(
|
||||||
terminal: terminal_c.Terminal,
|
terminal: terminal_c.Terminal,
|
||||||
options: ?*const SelectLineOptions,
|
options: ?*const SelectLineOptions,
|
||||||
|
|
|
||||||
|
|
@ -1438,6 +1438,28 @@ test "selection derivation helpers" {
|
||||||
word_opts.ref = empty_ref;
|
word_opts.ref = empty_ref;
|
||||||
try testing.expectEqual(Result.no_value, selection_c.word(t, &word_opts, &out));
|
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 = .{
|
var line_opts: selection_c.SelectLineOptions = .{
|
||||||
.ref = line_ref,
|
.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.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, null, &out));
|
||||||
try testing.expectEqual(Result.invalid_value, selection_c.word(t, &word_opts, null));
|
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" {
|
test "selection_adjust mutates snapshot end" {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ pub const structs: std.StaticStringMap(StructInfo) = structs: {
|
||||||
.{ "GhosttyFormatterTerminalOptions", StructInfo.init(formatter.TerminalOptions) },
|
.{ "GhosttyFormatterTerminalOptions", StructInfo.init(formatter.TerminalOptions) },
|
||||||
.{ "GhosttySelection", StructInfo.init(selection.CSelection) },
|
.{ "GhosttySelection", StructInfo.init(selection.CSelection) },
|
||||||
.{ "GhosttyTerminalSelectWordOptions", StructInfo.init(selection.SelectWordOptions) },
|
.{ "GhosttyTerminalSelectWordOptions", StructInfo.init(selection.SelectWordOptions) },
|
||||||
|
.{ "GhosttyTerminalSelectWordBetweenOptions", StructInfo.init(selection.SelectWordBetweenOptions) },
|
||||||
.{ "GhosttyTerminalSelectLineOptions", StructInfo.init(selection.SelectLineOptions) },
|
.{ "GhosttyTerminalSelectLineOptions", StructInfo.init(selection.SelectLineOptions) },
|
||||||
.{ "GhosttyFormatterTerminalExtra", StructInfo.init(formatter.TerminalOptions.Extra) },
|
.{ "GhosttyFormatterTerminalExtra", StructInfo.init(formatter.TerminalOptions.Extra) },
|
||||||
.{ "GhosttyFormatterScreenExtra", StructInfo.init(formatter.ScreenOptions.Extra) },
|
.{ "GhosttyFormatterScreenExtra", StructInfo.init(formatter.ScreenOptions.Extra) },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue