mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
libghostty: add hyperlink URI accessor to grid_ref API
Add ghostty_grid_ref_hyperlink_uri to extract the OSC 8 hyperlink URI from a cell at a grid reference position. Follows the same buffer pattern as ghostty_grid_ref_graphemes: callers pass a buffer and get back the byte length, or GHOSTTY_OUT_OF_SPACE with the required size if the buffer is too small. Cells without a hyperlink return success with length 0.
This commit is contained in:
@@ -109,6 +109,32 @@ GHOSTTY_API GhosttyResult ghostty_grid_ref_graphemes(const GhosttyGridRef *ref,
|
||||
size_t buf_len,
|
||||
size_t *out_len);
|
||||
|
||||
/**
|
||||
* Get the hyperlink URI for the cell at the grid reference's position.
|
||||
*
|
||||
* Writes the URI bytes into the provided buffer. If the cell has no
|
||||
* hyperlink, out_len is set to 0 and GHOSTTY_SUCCESS is returned.
|
||||
*
|
||||
* If the buffer is too small (or NULL), the function returns
|
||||
* GHOSTTY_OUT_OF_SPACE and writes the required number of bytes to
|
||||
* out_len. The caller can then retry with a sufficiently sized buffer.
|
||||
*
|
||||
* @param ref Pointer to the grid reference
|
||||
* @param buf Output buffer for the URI bytes (may be NULL)
|
||||
* @param buf_len Size of the output buffer in bytes
|
||||
* @param[out] out_len On success, the number of bytes written. On
|
||||
* GHOSTTY_OUT_OF_SPACE, the required buffer size in bytes.
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the ref's
|
||||
* node is NULL, GHOSTTY_OUT_OF_SPACE if the buffer is too small
|
||||
*
|
||||
* @ingroup grid_ref
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_grid_ref_hyperlink_uri(
|
||||
const GhosttyGridRef *ref,
|
||||
uint8_t *buf,
|
||||
size_t buf_len,
|
||||
size_t *out_len);
|
||||
|
||||
/**
|
||||
* Get the style of the cell at the grid reference's position.
|
||||
*
|
||||
|
||||
@@ -217,6 +217,7 @@ comptime {
|
||||
@export(&c.grid_ref_cell, .{ .name = "ghostty_grid_ref_cell" });
|
||||
@export(&c.grid_ref_row, .{ .name = "ghostty_grid_ref_row" });
|
||||
@export(&c.grid_ref_graphemes, .{ .name = "ghostty_grid_ref_graphemes" });
|
||||
@export(&c.grid_ref_hyperlink_uri, .{ .name = "ghostty_grid_ref_hyperlink_uri" });
|
||||
@export(&c.grid_ref_style, .{ .name = "ghostty_grid_ref_style" });
|
||||
@export(&c.build_info, .{ .name = "ghostty_build_info" });
|
||||
@export(&c.type_json, .{ .name = "ghostty_type_json" });
|
||||
|
||||
@@ -3,11 +3,13 @@ const testing = std.testing;
|
||||
const lib = @import("../lib.zig");
|
||||
const page = @import("../page.zig");
|
||||
const PageList = @import("../PageList.zig");
|
||||
const point = @import("../point.zig");
|
||||
const size = @import("../size.zig");
|
||||
const stylepkg = @import("../style.zig");
|
||||
const cell_c = @import("cell.zig");
|
||||
const row_c = @import("row.zig");
|
||||
const style_c = @import("style.zig");
|
||||
const terminal_c = @import("terminal.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
/// C: GhosttyGridRef
|
||||
@@ -89,6 +91,38 @@ pub fn grid_ref_graphemes(
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn grid_ref_hyperlink_uri(
|
||||
ref: *const CGridRef,
|
||||
out_buf: ?[*]u8,
|
||||
buf_len: usize,
|
||||
out_len: *usize,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
const p = ref.toPin() orelse return .invalid_value;
|
||||
const rac = p.node.data.getRowAndCell(p.x, p.y);
|
||||
const cell = rac.cell;
|
||||
|
||||
if (!cell.hyperlink) {
|
||||
out_len.* = 0;
|
||||
return .success;
|
||||
}
|
||||
|
||||
const link_id = p.node.data.lookupHyperlink(cell) orelse {
|
||||
out_len.* = 0;
|
||||
return .success;
|
||||
};
|
||||
const entry = p.node.data.hyperlink_set.get(p.node.data.memory, link_id);
|
||||
const uri = entry.uri.slice(p.node.data.memory);
|
||||
|
||||
if (out_buf == null or buf_len < uri.len) {
|
||||
out_len.* = uri.len;
|
||||
return .out_of_space;
|
||||
}
|
||||
|
||||
@memcpy(out_buf.?[0..uri.len], uri);
|
||||
out_len.* = uri.len;
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn grid_ref_style(
|
||||
ref: *const CGridRef,
|
||||
out: ?*style_c.Style,
|
||||
@@ -154,3 +188,63 @@ test "grid_ref_style null out" {
|
||||
const ref = CGridRef{};
|
||||
try testing.expectEqual(Result.invalid_value, grid_ref_style(&ref, null));
|
||||
}
|
||||
|
||||
test "grid_ref_hyperlink_uri null node" {
|
||||
const ref = CGridRef{};
|
||||
var len: usize = undefined;
|
||||
try testing.expectEqual(Result.invalid_value, grid_ref_hyperlink_uri(&ref, null, 0, &len));
|
||||
}
|
||||
|
||||
test "grid_ref_hyperlink_uri no hyperlink" {
|
||||
var terminal: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
terminal_c.vt_write(terminal, "hello", 5);
|
||||
|
||||
var ref: CGridRef = undefined;
|
||||
try testing.expectEqual(Result.success, terminal_c.grid_ref(
|
||||
terminal,
|
||||
point.Point.cval(.{ .active = .{ .x = 0, .y = 0 } }),
|
||||
&ref,
|
||||
));
|
||||
|
||||
var len: usize = undefined;
|
||||
try testing.expectEqual(Result.success, grid_ref_hyperlink_uri(&ref, null, 0, &len));
|
||||
try testing.expectEqual(@as(usize, 0), len);
|
||||
}
|
||||
|
||||
test "grid_ref_hyperlink_uri with hyperlink" {
|
||||
var terminal: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&terminal,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer terminal_c.free(terminal);
|
||||
|
||||
// Write OSC 8 hyperlink: \e]8;;uri\e\\text\e]8;;\e\\
|
||||
const seq = "\x1b]8;;https://example.com\x1b\\link\x1b]8;;\x1b\\";
|
||||
terminal_c.vt_write(terminal, seq, seq.len);
|
||||
|
||||
var ref: CGridRef = undefined;
|
||||
try testing.expectEqual(Result.success, terminal_c.grid_ref(
|
||||
terminal,
|
||||
point.Point.cval(.{ .active = .{ .x = 0, .y = 0 } }),
|
||||
&ref,
|
||||
));
|
||||
|
||||
// First query length with null buf
|
||||
var len: usize = undefined;
|
||||
try testing.expectEqual(Result.out_of_space, grid_ref_hyperlink_uri(&ref, null, 0, &len));
|
||||
try testing.expectEqual(@as(usize, 19), len); // "https://example.com"
|
||||
|
||||
// Now read with a properly sized buffer
|
||||
var buf: [256]u8 = undefined;
|
||||
try testing.expectEqual(Result.success, grid_ref_hyperlink_uri(&ref, &buf, buf.len, &len));
|
||||
try testing.expectEqualStrings("https://example.com", buf[0..len]);
|
||||
}
|
||||
|
||||
@@ -148,6 +148,7 @@ pub const type_json = types.get_json;
|
||||
pub const grid_ref_cell = grid_ref.grid_ref_cell;
|
||||
pub const grid_ref_row = grid_ref.grid_ref_row;
|
||||
pub const grid_ref_graphemes = grid_ref.grid_ref_graphemes;
|
||||
pub const grid_ref_hyperlink_uri = grid_ref.grid_ref_hyperlink_uri;
|
||||
pub const grid_ref_style = grid_ref.grid_ref_style;
|
||||
|
||||
test {
|
||||
|
||||
Reference in New Issue
Block a user