mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 03:25:50 +00:00
vt: add ghostty_terminal_cell for point-based cell lookup
Add a new C API function ghostty_terminal_cell that retrieves the opaque cell and row values at a given point in the terminal grid. The point is a tagged union supporting active, viewport, screen, and history coordinate systems.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/modes.h>
|
||||
#include <ghostty/vt/screen.h>
|
||||
#include <ghostty/vt/point.h>
|
||||
#include <ghostty/vt/style.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -372,8 +373,39 @@ GhosttyResult ghostty_terminal_mode_set(GhosttyTerminal terminal,
|
||||
* @ingroup terminal
|
||||
*/
|
||||
GhosttyResult ghostty_terminal_get(GhosttyTerminal terminal,
|
||||
GhosttyTerminalData data,
|
||||
void *out);
|
||||
GhosttyTerminalData data,
|
||||
void *out);
|
||||
|
||||
/**
|
||||
* Get the cell and row at a given point in the terminal.
|
||||
*
|
||||
* Looks up the cell at the specified point in the terminal grid. On success,
|
||||
* the output parameters are set to the opaque cell and row values, which can
|
||||
* be queried further with ghostty_cell_get() and ghostty_row_get().
|
||||
*
|
||||
* Lookups using the `active` and `viewport` tags are fast. The `screen`
|
||||
* and `history` tags may require traversing the full scrollback page list
|
||||
* to resolve the y coordinate, so they can be expensive for large
|
||||
* scrollback buffers.
|
||||
*
|
||||
* This function isn't meant to be used as the core of render loop. It
|
||||
* isn't built to sustain the framerates needed for rendering large screens.
|
||||
* Use the render state API for that. This API is instead meant for less
|
||||
* strictly performance-sensitive use cases.
|
||||
*
|
||||
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param point The point specifying which cell to look up
|
||||
* @param[out] out_cell On success, set to the cell at the given point (may be NULL)
|
||||
* @param[out] out_row On success, set to the row containing the cell (may be NULL)
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
|
||||
* is NULL or the point is out of bounds
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
GhosttyResult ghostty_terminal_cell(GhosttyTerminal terminal,
|
||||
GhosttyPoint point,
|
||||
GhosttyCell *out_cell,
|
||||
GhosttyRow *out_row);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
@@ -196,6 +196,7 @@ comptime {
|
||||
@export(&c.terminal_mode_get, .{ .name = "ghostty_terminal_mode_get" });
|
||||
@export(&c.terminal_mode_set, .{ .name = "ghostty_terminal_mode_set" });
|
||||
@export(&c.terminal_get, .{ .name = "ghostty_terminal_get" });
|
||||
@export(&c.terminal_cell, .{ .name = "ghostty_terminal_cell" });
|
||||
|
||||
// On Wasm we need to export our allocator convenience functions.
|
||||
if (builtin.target.cpu.arch.isWasm()) {
|
||||
|
||||
@@ -109,6 +109,7 @@ pub const terminal_scroll_viewport = terminal.scroll_viewport;
|
||||
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_cell = terminal.cell;
|
||||
|
||||
test {
|
||||
_ = cell;
|
||||
|
||||
@@ -7,7 +7,10 @@ const ScreenSet = @import("../ScreenSet.zig");
|
||||
const PageList = @import("../PageList.zig");
|
||||
const kitty = @import("../kitty/key.zig");
|
||||
const modes = @import("../modes.zig");
|
||||
const point = @import("../point.zig");
|
||||
const size = @import("../size.zig");
|
||||
const cell_c = @import("cell.zig");
|
||||
const row_c = @import("row.zig");
|
||||
const style_c = @import("style.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
@@ -207,6 +210,26 @@ fn getTyped(
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn cell(
|
||||
terminal_: Terminal,
|
||||
pt: point.Point.C,
|
||||
out_cell: ?*cell_c.CCell,
|
||||
out_row: ?*row_c.CRow,
|
||||
) callconv(.c) Result {
|
||||
const t = terminal_ orelse return .invalid_value;
|
||||
const zig_pt: point.Point = switch (pt.tag) {
|
||||
.active => .{ .active = pt.value.active },
|
||||
.viewport => .{ .viewport = pt.value.viewport },
|
||||
.screen => .{ .screen = pt.value.screen },
|
||||
.history => .{ .history = pt.value.history },
|
||||
};
|
||||
const result = t.screens.active.pages.getCell(zig_pt) orelse
|
||||
return .invalid_value;
|
||||
if (out_cell) |p| p.* = @bitCast(result.cell.*);
|
||||
if (out_row) |p| p.* = @bitCast(result.row.*);
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn free(terminal_: Terminal) callconv(.c) void {
|
||||
const t = terminal_ orelse return;
|
||||
|
||||
@@ -612,3 +635,61 @@ test "get invalid" {
|
||||
|
||||
try testing.expectEqual(Result.invalid_value, get(t, .invalid, null));
|
||||
}
|
||||
|
||||
test "cell" {
|
||||
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 out_cell: cell_c.CCell = undefined;
|
||||
var out_row: row_c.CRow = undefined;
|
||||
try testing.expectEqual(Result.success, cell(t, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 0, .y = 0 } },
|
||||
}, &out_cell, &out_row));
|
||||
|
||||
// Verify the cell contains 'H'
|
||||
var cp: u32 = 0;
|
||||
try testing.expectEqual(Result.success, cell_c.get(out_cell, .codepoint, @ptrCast(&cp)));
|
||||
try testing.expectEqual(@as(u32, 'H'), cp);
|
||||
}
|
||||
|
||||
test "cell null terminal" {
|
||||
var out_cell: cell_c.CCell = undefined;
|
||||
var out_row: row_c.CRow = undefined;
|
||||
try testing.expectEqual(Result.invalid_value, cell(null, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 0, .y = 0 } },
|
||||
}, &out_cell, &out_row));
|
||||
}
|
||||
|
||||
test "cell out of bounds" {
|
||||
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);
|
||||
|
||||
var out_cell: cell_c.CCell = undefined;
|
||||
var out_row: row_c.CRow = undefined;
|
||||
try testing.expectEqual(Result.invalid_value, cell(t, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 100, .y = 0 } },
|
||||
}, &out_cell, &out_row));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user