mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
vt: ghostty_terminal_scroll_viewport
This commit is contained in:
@@ -53,6 +53,45 @@ typedef struct {
|
||||
// future options.
|
||||
} GhosttyTerminalOptions;
|
||||
|
||||
/**
|
||||
* Scroll viewport behavior tag.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef enum {
|
||||
/** Scroll to the top of the scrollback. */
|
||||
GHOSTTY_SCROLL_VIEWPORT_TOP,
|
||||
|
||||
/** Scroll to the bottom (active area). */
|
||||
GHOSTTY_SCROLL_VIEWPORT_BOTTOM,
|
||||
|
||||
/** Scroll by a delta amount (up is negative). */
|
||||
GHOSTTY_SCROLL_VIEWPORT_DELTA,
|
||||
} GhosttyTerminalScrollViewportTag;
|
||||
|
||||
/**
|
||||
* Scroll viewport value.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef union {
|
||||
/** Scroll delta (only used with GHOSTTY_SCROLL_VIEWPORT_DELTA). Up is negative. */
|
||||
intptr_t delta;
|
||||
|
||||
/** Padding for ABI compatibility. Do not use. */
|
||||
uint64_t _padding[2];
|
||||
} GhosttyTerminalScrollViewportValue;
|
||||
|
||||
/**
|
||||
* Tagged union for scroll viewport behavior.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef struct {
|
||||
GhosttyTerminalScrollViewportTag tag;
|
||||
GhosttyTerminalScrollViewportValue value;
|
||||
} GhosttyTerminalScrollViewport;
|
||||
|
||||
/**
|
||||
* Create a new terminal instance.
|
||||
*
|
||||
@@ -102,8 +141,24 @@ void ghostty_terminal_free(GhosttyTerminal terminal);
|
||||
* @ingroup terminal
|
||||
*/
|
||||
void ghostty_terminal_vt_write(GhosttyTerminal terminal,
|
||||
const uint8_t* data,
|
||||
size_t len);
|
||||
const uint8_t* data,
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* Scroll the terminal viewport.
|
||||
*
|
||||
* Scrolls the terminal's viewport according to the given behavior.
|
||||
* When using GHOSTTY_SCROLL_VIEWPORT_DELTA, set the delta field in
|
||||
* the value union to specify the number of rows to scroll (negative
|
||||
* for up, positive for down). For other behaviors, the value is ignored.
|
||||
*
|
||||
* @param terminal The terminal handle (may be NULL, in which case this is a no-op)
|
||||
* @param behavior The scroll behavior as a tagged union
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
void ghostty_terminal_scroll_viewport(GhosttyTerminal terminal,
|
||||
GhosttyTerminalScrollViewport behavior);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ comptime {
|
||||
@export(&c.terminal_new, .{ .name = "ghostty_terminal_new" });
|
||||
@export(&c.terminal_free, .{ .name = "ghostty_terminal_free" });
|
||||
@export(&c.terminal_vt_write, .{ .name = "ghostty_terminal_vt_write" });
|
||||
@export(&c.terminal_scroll_viewport, .{ .name = "ghostty_terminal_scroll_viewport" });
|
||||
|
||||
// On Wasm we need to export our allocator convenience functions.
|
||||
if (builtin.target.cpu.arch.isWasm()) {
|
||||
|
||||
@@ -5,6 +5,7 @@ const Terminal = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const build_options = @import("terminal_options");
|
||||
const lib = @import("../lib/main.zig");
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
const testing = std.testing;
|
||||
const Allocator = std.mem.Allocator;
|
||||
@@ -35,6 +36,8 @@ const Page = pagepkg.Page;
|
||||
const Cell = pagepkg.Cell;
|
||||
const Row = pagepkg.Row;
|
||||
|
||||
const lib_target: lib.Target = if (build_options.c_abi) .c else .zig;
|
||||
|
||||
const log = std.log.scoped(.terminal);
|
||||
|
||||
/// Default tabstop interval
|
||||
@@ -1704,7 +1707,7 @@ pub fn scrollUp(self: *Terminal, count: usize) !void {
|
||||
}
|
||||
|
||||
/// Options for scrolling the viewport of the terminal grid.
|
||||
pub const ScrollViewport = union(enum) {
|
||||
pub const ScrollViewport = union(Tag) {
|
||||
/// Scroll to the top of the scrollback
|
||||
top,
|
||||
|
||||
@@ -1713,6 +1716,23 @@ pub const ScrollViewport = union(enum) {
|
||||
|
||||
/// Scroll by some delta amount, up is negative.
|
||||
delta: isize,
|
||||
|
||||
pub const Tag = lib.Enum(lib_target, &.{
|
||||
"top",
|
||||
"bottom",
|
||||
"delta",
|
||||
});
|
||||
|
||||
const c_union = lib.TaggedUnion(
|
||||
lib_target,
|
||||
@This(),
|
||||
// Padding: largest variant is isize (8 bytes on 64-bit).
|
||||
// Use [2]u64 (16 bytes) for future expansion.
|
||||
[2]u64,
|
||||
);
|
||||
pub const C = c_union.C;
|
||||
pub const CValue = c_union.CValue;
|
||||
pub const cval = c_union.cval;
|
||||
};
|
||||
|
||||
/// Scroll the viewport of the terminal grid.
|
||||
|
||||
10
src/terminal/c/AGENTS.md
Normal file
10
src/terminal/c/AGENTS.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# libghostty-vt C API
|
||||
|
||||
- C API must be designed with ABI compatibility in mind
|
||||
- Zig tagged unions must be converted to C ABI compatible unions
|
||||
via `lib.TaggedUnion`.
|
||||
- Any functions must be updated all the way through from here to
|
||||
`src/terminal/c/main.zig` to `src/lib_vt.zig` and the headers
|
||||
in `include/ghostty/vt.h`.
|
||||
- In `include/ghostty/vt.h`, always sort the header contents by:
|
||||
(1) macros, (2) forward declarations, (3) types, (4) functions
|
||||
@@ -56,6 +56,7 @@ pub const paste_is_safe = paste.is_safe;
|
||||
pub const terminal_new = terminal.new;
|
||||
pub const terminal_free = terminal.free;
|
||||
pub const terminal_vt_write = terminal.vt_write;
|
||||
pub const terminal_scroll_viewport = terminal.scroll_viewport;
|
||||
|
||||
test {
|
||||
_ = color;
|
||||
|
||||
@@ -67,6 +67,21 @@ pub fn vt_write(
|
||||
stream.nextSlice(ptr[0..len]);
|
||||
}
|
||||
|
||||
/// C: GhosttyTerminalScrollViewport
|
||||
pub const ScrollViewport = ZigTerminal.ScrollViewport.C;
|
||||
|
||||
pub fn scroll_viewport(
|
||||
terminal_: Terminal,
|
||||
behavior: ScrollViewport,
|
||||
) callconv(.c) void {
|
||||
const t = terminal_ orelse return;
|
||||
t.scrollViewport(switch (behavior.tag) {
|
||||
.top => .top,
|
||||
.bottom => .bottom,
|
||||
.delta => .{ .delta = behavior.value.delta },
|
||||
});
|
||||
}
|
||||
|
||||
pub fn free(terminal_: Terminal) callconv(.c) void {
|
||||
const t = terminal_ orelse return;
|
||||
|
||||
@@ -121,6 +136,62 @@ test "free null" {
|
||||
free(null);
|
||||
}
|
||||
|
||||
test "scroll_viewport" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 5,
|
||||
.rows = 2,
|
||||
.max_scrollback = 10_000,
|
||||
},
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
const zt = t.?;
|
||||
|
||||
// Write "hello" on the first line
|
||||
vt_write(t, "hello", 5);
|
||||
|
||||
// Push "hello" into scrollback with 3 newlines (index = ESC D)
|
||||
vt_write(t, "\x1bD\x1bD\x1bD", 6);
|
||||
{
|
||||
// Viewport should be empty now since hello scrolled off
|
||||
const str = try zt.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("", str);
|
||||
}
|
||||
|
||||
// Scroll to top: "hello" should be visible again
|
||||
scroll_viewport(t, .{ .tag = .top, .value = undefined });
|
||||
{
|
||||
const str = try zt.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("hello", str);
|
||||
}
|
||||
|
||||
// Scroll to bottom: viewport should be empty again
|
||||
scroll_viewport(t, .{ .tag = .bottom, .value = undefined });
|
||||
{
|
||||
const str = try zt.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("", str);
|
||||
}
|
||||
|
||||
// Scroll up by delta to bring "hello" back into view
|
||||
scroll_viewport(t, .{ .tag = .delta, .value = .{ .delta = -3 } });
|
||||
{
|
||||
const str = try zt.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("hello", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "scroll_viewport null" {
|
||||
scroll_viewport(null, .{ .tag = .top, .value = undefined });
|
||||
}
|
||||
|
||||
test "vt_write" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
Reference in New Issue
Block a user