This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

libghostty: selectWordBetween in C

author
Mitchell Hashimoto
date (May 24, 2026, 1:53 PM -0700) commit eb777b80 parent e8f53539 change-id vllrvwpy
+158
+34
example/c-vt-selection/src/main.c
··· 76 76 assert(result == GHOSTTY_SUCCESS); 77 77 print_selection(terminal, "word", &selection); 78 78 79 + //! [selection-word-between] 80 + // Double-click-and-drag style selection. Suppose the user double-clicks 81 + // "git" and drags to "status". The pointer may pass over whitespace, so 82 + // select the nearest word between the original click and current drag point 83 + // in both directions, then combine the outer word bounds. 84 + GhosttyGridRef click_ref = ref_at(terminal, 2, 0); // the "git" in "git status" 85 + GhosttyGridRef drag_ref = ref_at(terminal, 6, 0); // the "status" in "git status" 86 + 87 + GhosttyTerminalSelectWordBetweenOptions start_word_opts = 88 + GHOSTTY_INIT_SIZED(GhosttyTerminalSelectWordBetweenOptions); 89 + start_word_opts.start = click_ref; 90 + start_word_opts.end = drag_ref; 91 + 92 + GhosttySelection start_word = GHOSTTY_INIT_SIZED(GhosttySelection); 93 + result = ghostty_terminal_select_word_between( 94 + terminal, &start_word_opts, &start_word); 95 + assert(result == GHOSTTY_SUCCESS); 96 + 97 + GhosttyTerminalSelectWordBetweenOptions end_word_opts = 98 + GHOSTTY_INIT_SIZED(GhosttyTerminalSelectWordBetweenOptions); 99 + end_word_opts.start = drag_ref; 100 + end_word_opts.end = click_ref; 101 + 102 + GhosttySelection end_word = GHOSTTY_INIT_SIZED(GhosttySelection); 103 + result = ghostty_terminal_select_word_between( 104 + terminal, &end_word_opts, &end_word); 105 + assert(result == GHOSTTY_SUCCESS); 106 + 107 + GhosttySelection drag_selection = GHOSTTY_INIT_SIZED(GhosttySelection); 108 + drag_selection.start = start_word.start; 109 + drag_selection.end = end_word.end; 110 + print_selection(terminal, "double-click drag", &drag_selection); 111 + //! [selection-word-between] 112 + 79 113 // Triple-click style line selection. With semantic prompt boundaries enabled, 80 114 // this selects only the input area rather than the leading "$ " prompt. 81 115 GhosttyTerminalSelectLineOptions line = GHOSTTY_INIT_SIZED(GhosttyTerminalSelectLineOptions);
+61
include/ghostty/vt/selection.h
··· 108 108 } GhosttyTerminalSelectWordOptions; 109 109 110 110 /** 111 + * Options for deriving the nearest word selection between two grid references. 112 + * 113 + * This is a sized struct. Use GHOSTTY_INIT_SIZED() to initialize it. 114 + * If boundary_codepoints is NULL and boundary_codepoints_len is 0, Ghostty's 115 + * default word-boundary codepoints are used. If boundary_codepoints_len is 116 + * non-zero, boundary_codepoints must not be NULL. 117 + * 118 + * @ingroup selection 119 + */ 120 + typedef struct { 121 + /** Size of this struct in bytes. Must be set to sizeof(GhosttyTerminalSelectWordBetweenOptions). */ 122 + size_t size; 123 + 124 + /** Starting grid reference for the inclusive search range. */ 125 + GhosttyGridRef start; 126 + 127 + /** Ending grid reference for the inclusive search range. */ 128 + GhosttyGridRef end; 129 + 130 + /** Optional word-boundary codepoints as uint32_t scalar values. */ 131 + const uint32_t* boundary_codepoints; 132 + 133 + /** Number of entries in boundary_codepoints. */ 134 + size_t boundary_codepoints_len; 135 + } GhosttyTerminalSelectWordBetweenOptions; 136 + 137 + /** 111 138 * Options for deriving a line selection from a terminal grid reference. 112 139 * 113 140 * This is a sized struct. Use GHOSTTY_INIT_SIZED() to initialize it. ··· 233 260 GHOSTTY_API GhosttyResult ghostty_terminal_select_word( 234 261 GhosttyTerminal terminal, 235 262 const GhosttyTerminalSelectWordOptions* options, 263 + GhosttySelection* out_selection); 264 + 265 + /** 266 + * Derive the nearest word selection snapshot between two terminal grid refs. 267 + * 268 + * Starting at options->start, this searches toward options->end (inclusive) 269 + * and returns the first selectable word found using Ghostty's word-selection 270 + * rules. 271 + * 272 + * This is useful for implementing double-click-and-drag selection in a UI. If 273 + * a user double-clicks one word and drags across spaces or punctuation toward 274 + * another word, selecting only the word directly under the current pointer can 275 + * flicker or collapse when the pointer is between words. Instead, ask for the 276 + * nearest word between the original click and the drag point, ask again in the 277 + * reverse direction, and combine the two word bounds into the drag selection. 278 + * 279 + * @snippet c-vt-selection/src/main.c selection-word-between 280 + * 281 + * The returned selection is not installed as the terminal's current 282 + * selection. It is a snapshot with the same lifetime rules as GhosttySelection. 283 + * 284 + * @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE) 285 + * @param options Word-between-selection options 286 + * @param[out] out_selection On success, receives the derived selection 287 + * @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if there is no 288 + * selectable word content between the valid refs, or 289 + * GHOSTTY_INVALID_VALUE if the terminal, options, refs, codepoint 290 + * pointer, or output pointer are invalid. 291 + * 292 + * @ingroup selection 293 + */ 294 + GHOSTTY_API GhosttyResult ghostty_terminal_select_word_between( 295 + GhosttyTerminal terminal, 296 + const GhosttyTerminalSelectWordBetweenOptions* options, 236 297 GhosttySelection* out_selection); 237 298 238 299 /**
+1
src/lib_vt.zig
··· 240 240 @export(&c.terminal_get, .{ .name = "ghostty_terminal_get" }); 241 241 @export(&c.terminal_get_multi, .{ .name = "ghostty_terminal_get_multi" }); 242 242 @export(&c.terminal_select_word, .{ .name = "ghostty_terminal_select_word" }); 243 + @export(&c.terminal_select_word_between, .{ .name = "ghostty_terminal_select_word_between" }); 243 244 @export(&c.terminal_select_line, .{ .name = "ghostty_terminal_select_line" }); 244 245 @export(&c.terminal_select_all, .{ .name = "ghostty_terminal_select_all" }); 245 246 @export(&c.terminal_select_output, .{ .name = "ghostty_terminal_select_output" });
+1
src/terminal/c/main.zig
··· 171 171 pub const terminal_get = terminal.get; 172 172 pub const terminal_get_multi = terminal.get_multi; 173 173 pub const terminal_select_word = selection.word; 174 + pub const terminal_select_word_between = selection.word_between; 174 175 pub const terminal_select_line = selection.line; 175 176 pub const terminal_select_all = selection.all; 176 177 pub const terminal_select_output = selection.output;
+36
src/terminal/c/selection.zig
··· 43 43 boundary_codepoints_len: usize = 0, 44 44 }; 45 45 46 + /// C: GhosttyTerminalSelectWordBetweenOptions 47 + pub const SelectWordBetweenOptions = extern struct { 48 + size: usize = @sizeOf(SelectWordBetweenOptions), 49 + start: grid_ref.CGridRef, 50 + end: grid_ref.CGridRef, 51 + boundary_codepoints: ?[*]const u32 = null, 52 + boundary_codepoints_len: usize = 0, 53 + }; 54 + 46 55 /// C: GhosttyTerminalSelectLineOptions 47 56 pub const SelectLineOptions = extern struct { 48 57 size: usize = @sizeOf(SelectLineOptions), ··· 71 80 const pin = opts.ref.toPin() orelse return .invalid_value; 72 81 out.* = .fromZig(screen.selectWord( 73 82 pin, 83 + boundary_codepoints orelse &selection_codepoints.default_word_boundaries, 84 + ) orelse 85 + return .no_value); 86 + return .success; 87 + } 88 + 89 + pub fn word_between( 90 + terminal: terminal_c.Terminal, 91 + options: ?*const SelectWordBetweenOptions, 92 + out_selection: ?*CSelection, 93 + ) callconv(lib.calling_conv) Result { 94 + const t = terminal_c.zigTerminal(terminal) orelse return .invalid_value; 95 + const opts = options orelse return .invalid_value; 96 + if (opts.size < @sizeOf(SelectWordBetweenOptions)) return .invalid_value; 97 + const out = out_selection orelse return .invalid_value; 98 + 99 + const boundary_codepoints = codepointSlice( 100 + opts.boundary_codepoints, 101 + opts.boundary_codepoints_len, 102 + ) catch return .invalid_value; 103 + 104 + const screen = t.screens.active; 105 + const start = opts.start.toPin() orelse return .invalid_value; 106 + const end = opts.end.toPin() orelse return .invalid_value; 107 + out.* = .fromZig(screen.selectWordBetween( 108 + start, 109 + end, 74 110 boundary_codepoints orelse &selection_codepoints.default_word_boundaries, 75 111 ) orelse 76 112 return .no_value);
+24
src/terminal/c/terminal.zig
··· 1438 1438 word_opts.ref = empty_ref; 1439 1439 try testing.expectEqual(Result.no_value, selection_c.word(t, &word_opts, &out)); 1440 1440 1441 + var between_start_ref: grid_ref_c.CGridRef = .{}; 1442 + try testing.expectEqual(Result.success, grid_ref(t, .{ 1443 + .tag = .active, 1444 + .value = .{ .active = .{ .x = 20, .y = 1 } }, 1445 + }, &between_start_ref)); 1446 + 1447 + var between_end_ref: grid_ref_c.CGridRef = .{}; 1448 + try testing.expectEqual(Result.success, grid_ref(t, .{ 1449 + .tag = .active, 1450 + .value = .{ .active = .{ .x = 0, .y = 1 } }, 1451 + }, &between_end_ref)); 1452 + 1453 + var word_between_opts: selection_c.SelectWordBetweenOptions = .{ 1454 + .start = between_start_ref, 1455 + .end = between_end_ref, 1456 + }; 1457 + try testing.expectEqual(Result.success, selection_c.word_between(t, &word_between_opts, &out)); 1458 + try testing.expectEqual(@as(u16, 0), out.start.toPin().?.x); 1459 + try testing.expectEqual(@as(u16, 1), out.start.toPin().?.y); 1460 + try testing.expectEqual(@as(u16, 4), out.end.toPin().?.x); 1461 + try testing.expectEqual(@as(u16, 1), out.end.toPin().?.y); 1462 + 1441 1463 var line_opts: selection_c.SelectLineOptions = .{ 1442 1464 .ref = line_ref, 1443 1465 }; ··· 1457 1479 try testing.expectEqual(Result.invalid_value, selection_c.line(t, &line_opts, &out)); 1458 1480 try testing.expectEqual(Result.invalid_value, selection_c.word(t, null, &out)); 1459 1481 try testing.expectEqual(Result.invalid_value, selection_c.word(t, &word_opts, null)); 1482 + try testing.expectEqual(Result.invalid_value, selection_c.word_between(t, null, &out)); 1483 + try testing.expectEqual(Result.invalid_value, selection_c.word_between(t, &word_between_opts, null)); 1460 1484 } 1461 1485 1462 1486 test "selection_adjust mutates snapshot end" {
+1
src/terminal/c/types.zig
··· 31 31 .{ "GhosttyFormatterTerminalOptions", StructInfo.init(formatter.TerminalOptions) }, 32 32 .{ "GhosttySelection", StructInfo.init(selection.CSelection) }, 33 33 .{ "GhosttyTerminalSelectWordOptions", StructInfo.init(selection.SelectWordOptions) }, 34 + .{ "GhosttyTerminalSelectWordBetweenOptions", StructInfo.init(selection.SelectWordBetweenOptions) }, 34 35 .{ "GhosttyTerminalSelectLineOptions", StructInfo.init(selection.SelectLineOptions) }, 35 36 .{ "GhosttyFormatterTerminalExtra", StructInfo.init(formatter.TerminalOptions.Extra) }, 36 37 .{ "GhosttyFormatterScreenExtra", StructInfo.init(formatter.ScreenOptions.Extra) },