mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-25 06:18:37 +00:00
libghostty: add selection adjustment api
This commit is contained in:
@@ -77,6 +77,61 @@ typedef struct {
|
||||
bool rectangle;
|
||||
} GhosttySelection;
|
||||
|
||||
/**
|
||||
* Operation used to adjust a selection endpoint.
|
||||
*
|
||||
* Adjustment mutates the selection's logical end endpoint, not whichever
|
||||
* endpoint is visually bottom/right. This preserves keyboard and drag
|
||||
* behavior for both forward and reversed selections.
|
||||
*
|
||||
* @ingroup selection
|
||||
*/
|
||||
typedef enum GHOSTTY_ENUM_TYPED {
|
||||
/** Move left to the previous non-empty cell, wrapping upward. */
|
||||
GHOSTTY_SELECTION_ADJUST_LEFT = 0,
|
||||
|
||||
/** Move right to the next non-empty cell, wrapping downward. */
|
||||
GHOSTTY_SELECTION_ADJUST_RIGHT = 1,
|
||||
|
||||
/**
|
||||
* Move up one row at the current column, or to the beginning of the
|
||||
* line if already at the top.
|
||||
*/
|
||||
GHOSTTY_SELECTION_ADJUST_UP = 2,
|
||||
|
||||
/**
|
||||
* Move down to the next non-blank row at the current column, or to the
|
||||
* end of the line if none exists.
|
||||
*/
|
||||
GHOSTTY_SELECTION_ADJUST_DOWN = 3,
|
||||
|
||||
/** Move to the top-left cell of the screen. */
|
||||
GHOSTTY_SELECTION_ADJUST_HOME = 4,
|
||||
|
||||
/** Move to the right edge of the last non-blank row on the screen. */
|
||||
GHOSTTY_SELECTION_ADJUST_END = 5,
|
||||
|
||||
/**
|
||||
* Move up by one terminal page height, or to home if that would move
|
||||
* past the top.
|
||||
*/
|
||||
GHOSTTY_SELECTION_ADJUST_PAGE_UP = 6,
|
||||
|
||||
/**
|
||||
* Move down by one terminal page height, or to end if that would move
|
||||
* past the bottom.
|
||||
*/
|
||||
GHOSTTY_SELECTION_ADJUST_PAGE_DOWN = 7,
|
||||
|
||||
/** Move to the left edge of the current line. */
|
||||
GHOSTTY_SELECTION_ADJUST_BEGINNING_OF_LINE = 8,
|
||||
|
||||
/** Move to the right edge of the current line. */
|
||||
GHOSTTY_SELECTION_ADJUST_END_OF_LINE = 9,
|
||||
|
||||
GHOSTTY_SELECTION_ADJUST_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttySelectionAdjust;
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1123,6 +1123,35 @@ GHOSTTY_API GhosttyResult ghostty_terminal_get_multi(GhosttyTerminal terminal,
|
||||
void** values,
|
||||
size_t* out_written);
|
||||
|
||||
/**
|
||||
* Adjust a selection snapshot using terminal selection semantics.
|
||||
*
|
||||
* This mutates the caller-provided GhosttySelection in place. The logical end
|
||||
* endpoint is always moved, regardless of whether the selection is forward or
|
||||
* reversed visually. The input selection remains a snapshot: after adjustment,
|
||||
* call ghostty_terminal_set() with GHOSTTY_TERMINAL_OPT_SELECTION to install it
|
||||
* as the terminal-owned selection if desired.
|
||||
*
|
||||
* The selection's start and end grid refs must both be valid untracked
|
||||
* snapshots for the given terminal's currently active screen. In practice,
|
||||
* they must come from that terminal and screen, and no mutating terminal call
|
||||
* may have occurred since the refs were produced or reconstructed from
|
||||
* tracked refs. Passing refs from another terminal, another screen, or stale
|
||||
* refs violates this precondition.
|
||||
*
|
||||
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param selection Selection snapshot to adjust in place
|
||||
* @param adjustment The adjustment operation to apply
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal,
|
||||
* selection, selection references, or adjustment are invalid
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_terminal_selection_adjust(
|
||||
GhosttyTerminal terminal,
|
||||
GhosttySelection* selection,
|
||||
GhosttySelectionAdjust adjustment);
|
||||
|
||||
/**
|
||||
* Resolve a point in the terminal grid to a grid reference.
|
||||
*
|
||||
|
||||
@@ -239,6 +239,7 @@ comptime {
|
||||
@export(&c.terminal_mode_set, .{ .name = "ghostty_terminal_mode_set" });
|
||||
@export(&c.terminal_get, .{ .name = "ghostty_terminal_get" });
|
||||
@export(&c.terminal_get_multi, .{ .name = "ghostty_terminal_get_multi" });
|
||||
@export(&c.terminal_selection_adjust, .{ .name = "ghostty_terminal_selection_adjust" });
|
||||
@export(&c.terminal_grid_ref, .{ .name = "ghostty_terminal_grid_ref" });
|
||||
@export(&c.terminal_grid_ref_track, .{ .name = "ghostty_terminal_grid_ref_track" });
|
||||
@export(&c.terminal_point_from_grid_ref, .{ .name = "ghostty_terminal_point_from_grid_ref" });
|
||||
|
||||
@@ -4,6 +4,7 @@ const Selection = @This();
|
||||
const std = @import("std");
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const lib = @import("lib.zig");
|
||||
const page = @import("page.zig");
|
||||
const point = @import("point.zig");
|
||||
const PageList = @import("PageList.zig");
|
||||
@@ -389,18 +390,18 @@ pub fn containedRowCached(
|
||||
}
|
||||
|
||||
/// Possible adjustments to the selection.
|
||||
pub const Adjustment = enum {
|
||||
left,
|
||||
right,
|
||||
up,
|
||||
down,
|
||||
home,
|
||||
end,
|
||||
page_up,
|
||||
page_down,
|
||||
beginning_of_line,
|
||||
end_of_line,
|
||||
};
|
||||
pub const Adjustment = lib.Enum(lib.target, &.{
|
||||
"left",
|
||||
"right",
|
||||
"up",
|
||||
"down",
|
||||
"home",
|
||||
"end",
|
||||
"page_up",
|
||||
"page_down",
|
||||
"beginning_of_line",
|
||||
"end_of_line",
|
||||
});
|
||||
|
||||
/// Adjust the selection by some given adjustment. An adjustment allows
|
||||
/// a selection to be expanded slightly left, right, up, down, etc.
|
||||
|
||||
@@ -170,6 +170,7 @@ pub const terminal_mode_get = terminal.mode_get;
|
||||
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_selection_adjust = terminal.selection_adjust;
|
||||
pub const terminal_grid_ref = terminal.grid_ref;
|
||||
pub const terminal_grid_ref_track = terminal.grid_ref_track;
|
||||
pub const terminal_point_from_grid_ref = terminal.point_from_grid_ref;
|
||||
|
||||
@@ -7,6 +7,7 @@ const ZigTerminal = @import("../Terminal.zig");
|
||||
const Stream = @import("../stream_terminal.zig").Stream;
|
||||
const ScreenSet = @import("../ScreenSet.zig");
|
||||
const PageList = @import("../PageList.zig");
|
||||
const Selection = @import("../Selection.zig");
|
||||
const apc = @import("../apc.zig");
|
||||
const kitty = @import("../kitty/key.zig");
|
||||
const kitty_gfx_c = @import("kitty_graphics.zig");
|
||||
@@ -664,6 +665,26 @@ pub fn get_multi(
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn selection_adjust(
|
||||
terminal_: Terminal,
|
||||
selection: ?*selection_c.CSelection,
|
||||
adjustment: Selection.Adjustment,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(Selection.Adjustment, @intFromEnum(adjustment)) catch {
|
||||
log.warn("terminal_selection_adjust invalid adjustment value={d}", .{@intFromEnum(adjustment)});
|
||||
return .invalid_value;
|
||||
};
|
||||
}
|
||||
|
||||
const t: *ZigTerminal = (terminal_ orelse return .invalid_value).terminal;
|
||||
const sel_ptr = selection orelse return .invalid_value;
|
||||
var sel = sel_ptr.toZig() orelse return .invalid_value;
|
||||
sel.adjust(t.screens.active, adjustment);
|
||||
sel_ptr.* = .fromZig(sel);
|
||||
return .success;
|
||||
}
|
||||
|
||||
fn getTyped(
|
||||
terminal_: Terminal,
|
||||
comptime data: TerminalData,
|
||||
@@ -1389,6 +1410,54 @@ test "set and get selection" {
|
||||
try testing.expectEqual(Result.no_value, get(t, .selection, @ptrCast(&out)));
|
||||
}
|
||||
|
||||
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_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_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_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 "grid_ref" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
Reference in New Issue
Block a user