@@ -29,10 +29,10 @@ index 4f8fef88e..ca9fb1d4d 100644
2929 #include <ghostty/vt/key.h>
3030diff --git a/include/ghostty/vt/terminal.h b/include/ghostty/vt/terminal.h
3131new file mode 100644
32- index 000000000..298ad36c1
32+ index 000000000..2c9dd99c7
3333--- /dev/null
3434+++ b/include/ghostty/vt/terminal.h
35- @@ -0,0 +1,249 @@
35+ @@ -0,0 +1,269 @@
3636+ /**
3737+ * @file terminal.h
3838+ *
@@ -256,6 +256,26 @@ index 000000000..298ad36c1
256256+ bool ghostty_terminal_is_row_wrapped(GhosttyTerminal term, int y);
257257+
258258+ /* ============================================================================
259+ + * Hyperlink API
260+ + * ========================================================================= */
261+ +
262+ + /**
263+ + * Get the hyperlink URI for a cell in the active viewport.
264+ + * @param row Row index (0-based)
265+ + * @param col Column index (0-based)
266+ + * @param out_buffer Buffer to receive URI bytes (UTF-8)
267+ + * @param buffer_size Size of buffer in bytes
268+ + * @return Number of bytes written, 0 if no hyperlink, -1 on error
269+ + */
270+ + int ghostty_terminal_get_hyperlink_uri(
271+ + GhosttyTerminal term,
272+ + int row,
273+ + int col,
274+ + uint8_t* out_buffer,
275+ + size_t buffer_size
276+ + );
277+ +
278+ + /* ============================================================================
259279+ * Response API - for DSR and other terminal queries
260280+ * ========================================================================= */
261281+
@@ -283,10 +303,10 @@ index 000000000..298ad36c1
283303+
284304+ #endif /* GHOSTTY_VT_TERMINAL_H */
285305diff --git a/src/lib_vt.zig b/src/lib_vt.zig
286- index 03a883e20..f07bbd759 100644
306+ index 03a883e20..32d5f7c38 100644
287307--- a/src/lib_vt.zig
288308+++ b/src/lib_vt.zig
289- @@ -140,6 +140,41 @@ comptime {
309+ @@ -140,6 +140,44 @@ comptime {
290310 @export(&c.sgr_unknown_partial, .{ .name = "ghostty_sgr_unknown_partial" });
291311 @export(&c.sgr_attribute_tag, .{ .name = "ghostty_sgr_attribute_tag" });
292312 @export(&c.sgr_attribute_value, .{ .name = "ghostty_sgr_attribute_value" });
@@ -322,14 +342,17 @@ index 03a883e20..f07bbd759 100644
322342+ @export(&c.terminal_get_scrollback_grapheme, .{ .name = "ghostty_terminal_get_scrollback_grapheme" });
323343+ @export(&c.terminal_is_row_wrapped, .{ .name = "ghostty_terminal_is_row_wrapped" });
324344+
345+ + // Hyperlink API
346+ + @export(&c.terminal_get_hyperlink_uri, .{ .name = "ghostty_terminal_get_hyperlink_uri" });
347+ +
325348+ // Response API (for DSR and other queries)
326349+ @export(&c.terminal_has_response, .{ .name = "ghostty_terminal_has_response" });
327350+ @export(&c.terminal_read_response, .{ .name = "ghostty_terminal_read_response" });
328351
329352 // On Wasm we need to export our allocator convenience functions.
330353 if (builtin.target.cpu.arch.isWasm()) {
331354diff --git a/src/terminal/c/main.zig b/src/terminal/c/main.zig
332- index bc92597f5..18503933f 100644
355+ index bc92597f5..e352c150a 100644
333356--- a/src/terminal/c/main.zig
334357+++ b/src/terminal/c/main.zig
335358@@ -4,6 +4,7 @@ pub const key_event = @import("key_event.zig");
@@ -340,7 +363,7 @@ index bc92597f5..18503933f 100644
340363
341364 // The full C API, unexported.
342365 pub const osc_new = osc.new;
343- @@ -52,6 +53,42 @@ pub const key_encoder_encode = key_encode.encode;
366+ @@ -52,6 +53,45 @@ pub const key_encoder_encode = key_encode.encode;
344367
345368 pub const paste_is_safe = paste.is_safe;
346369
@@ -376,14 +399,17 @@ index bc92597f5..18503933f 100644
376399+ pub const terminal_get_scrollback_grapheme = terminal.getScrollbackGrapheme;
377400+ pub const terminal_is_row_wrapped = terminal.isRowWrapped;
378401+
402+ + // Hyperlink API
403+ + pub const terminal_get_hyperlink_uri = terminal.getHyperlinkUri;
404+ +
379405+ // Response API (for DSR and other queries)
380406+ pub const terminal_has_response = terminal.hasResponse;
381407+ pub const terminal_read_response = terminal.readResponse;
382408+
383409 test {
384410 _ = color;
385411 _ = osc;
386- @@ -59,6 +96 ,7 @@ test {
412+ @@ -59,6 +99 ,7 @@ test {
387413 _ = key_encode;
388414 _ = paste;
389415 _ = sgr;
@@ -393,10 +419,10 @@ index bc92597f5..18503933f 100644
393419 _ = @import("../../lib/allocator.zig");
394420diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig
395421new file mode 100644
396- index 000000000..d57b4e405
422+ index 000000000..2eca7a93a
397423--- /dev/null
398424+++ b/src/terminal/c/terminal.zig
399- @@ -0,0 +1,1025 @@
425+ @@ -0,0 +1,1074 @@
400426+ //! C API wrapper for Terminal
401427+ //!
402428+ //! This provides a minimal, high-performance interface to Ghostty's Terminal
@@ -1357,6 +1383,55 @@ index 000000000..d57b4e405
13571383+ }
13581384+
13591385+ // ============================================================================
1386+ + // Hyperlink API
1387+ + // ============================================================================
1388+ +
1389+ + /// Get the hyperlink URI for a cell in the active viewport.
1390+ + /// Returns number of bytes written, 0 if no hyperlink, -1 on error.
1391+ + pub fn getHyperlinkUri(
1392+ + ptr: ?*anyopaque,
1393+ + row: c_int,
1394+ + col: c_int,
1395+ + out: [*]u8,
1396+ + buf_size: usize,
1397+ + ) callconv(.c) c_int {
1398+ + const wrapper: *const TerminalWrapper = @ptrCast(@alignCast(ptr orelse return -1));
1399+ + const t = &wrapper.terminal;
1400+ +
1401+ + if (row < 0 or col < 0) return -1;
1402+ +
1403+ + // Get the pin for this row from the terminal's active screen
1404+ + const pages = &t.screens.active.pages;
1405+ + const pin = pages.pin(.{ .active = .{ .y = @intCast(row) } }) orelse return -1;
1406+ +
1407+ + const cells = pin.cells(.all);
1408+ + const page = pin.node.data;
1409+ + const x: usize = @intCast(col);
1410+ +
1411+ + if (x >= cells.len) return -1;
1412+ +
1413+ + const cell = &cells[x];
1414+ +
1415+ + // Check if cell has a hyperlink
1416+ + if (!cell.hyperlink) return 0;
1417+ +
1418+ + // Look up the hyperlink ID from the page
1419+ + const hyperlink_id = page.lookupHyperlink(cell) orelse return 0;
1420+ +
1421+ + // Get the hyperlink entry from the set
1422+ + const hyperlink_entry = page.hyperlink_set.get(page.memory, hyperlink_id);
1423+ +
1424+ + // Get the URI bytes from the page memory
1425+ + const uri = hyperlink_entry.uri.slice(page.memory);
1426+ +
1427+ + if (uri.len == 0) return 0;
1428+ + if (buf_size < uri.len) return -1;
1429+ +
1430+ + @memcpy(out[0..uri.len], uri);
1431+ + return @intCast(uri.len);
1432+ + }
1433+ +
1434+ + // ============================================================================
13601435+ // Response API - for DSR and other terminal queries
13611436+ // ============================================================================
13621437+
0 commit comments