mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-03 06:09:04 +00:00
libghostty: add GhosttySelection type and selection support to formatter (#12115)
Add a new GhosttySelection C API type (selection.h / c/selection.zig) that pairs two GhosttyGridRef endpoints with a rectangle flag. This maps directly to the internal Selection type using untracked pins. The formatter terminal options gain an optional selection pointer. When non-null the formatter restricts output to the specified range instead of emitting the entire screen. When null the existing behavior of formatting the full screen is preserved.
This commit is contained in:
@@ -3,6 +3,8 @@ const testing = std.testing;
|
||||
const lib = @import("../lib.zig");
|
||||
const CAllocator = lib.alloc.Allocator;
|
||||
const terminal_c = @import("terminal.zig");
|
||||
const grid_ref = @import("grid_ref.zig");
|
||||
const selection_c = @import("selection.zig");
|
||||
const ZigTerminal = @import("../Terminal.zig");
|
||||
const formatterpkg = @import("../formatter.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
@@ -23,6 +25,8 @@ pub const Formatter = ?*FormatterWrapper;
|
||||
/// C: GhosttyFormatterFormat
|
||||
pub const Format = formatterpkg.Format;
|
||||
|
||||
const CSelection = selection_c.CSelection;
|
||||
|
||||
/// C: GhosttyFormatterScreenOptions
|
||||
pub const ScreenOptions = extern struct {
|
||||
/// C: GhosttyFormatterScreenExtra
|
||||
@@ -63,6 +67,10 @@ pub const TerminalOptions = extern struct {
|
||||
trim: bool,
|
||||
extra: Extra,
|
||||
|
||||
/// Optional selection to restrict output to a range.
|
||||
/// If null, the entire screen is formatted.
|
||||
selection: ?*const CSelection = null,
|
||||
|
||||
/// C: GhosttyFormatterTerminalExtra
|
||||
pub const Extra = extern struct {
|
||||
size: usize = @sizeOf(Extra),
|
||||
@@ -138,6 +146,12 @@ fn terminal_new_(
|
||||
});
|
||||
formatter.extra = opts.extra.toZig();
|
||||
|
||||
// Setup the content that we're formatting
|
||||
if (opts.selection) |sel| formatter.content = .{
|
||||
.selection = sel.toZig() orelse
|
||||
return error.InvalidValue,
|
||||
};
|
||||
|
||||
ptr.* = .{
|
||||
.kind = .{ .terminal = formatter },
|
||||
.alloc = alloc,
|
||||
@@ -389,6 +403,50 @@ test "format vt" {
|
||||
try testing.expect(std.mem.indexOf(u8, buf[0..written], "Test") != null);
|
||||
}
|
||||
|
||||
test "format plain with selection" {
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
terminal_c.vt_write(t, "Hello World", 11);
|
||||
|
||||
// Get grid refs for "World" (columns 6..10 on row 0)
|
||||
var start_ref: grid_ref.CGridRef = .{};
|
||||
try testing.expectEqual(Result.success, terminal_c.grid_ref(t, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 6, .y = 0 } },
|
||||
}, &start_ref));
|
||||
|
||||
var end_ref: grid_ref.CGridRef = .{};
|
||||
try testing.expectEqual(Result.success, terminal_c.grid_ref(t, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 10, .y = 0 } },
|
||||
}, &end_ref));
|
||||
|
||||
const sel: selection_c.CSelection = .{
|
||||
.start = start_ref,
|
||||
.end = end_ref,
|
||||
};
|
||||
|
||||
var f: Formatter = null;
|
||||
try testing.expectEqual(Result.success, terminal_new(
|
||||
&lib.alloc.test_allocator,
|
||||
&f,
|
||||
t,
|
||||
.{ .emit = .plain, .unwrap = false, .trim = true, .selection = &sel, .extra = .{ .palette = false, .modes = false, .scrolling_region = false, .tabstops = false, .pwd = false, .keyboard = false, .screen = .{ .cursor = false, .style = false, .hyperlink = false, .protection = false, .kitty_keyboard = false, .charsets = false } } },
|
||||
));
|
||||
defer free(f);
|
||||
|
||||
var buf: [1024]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, format_buf(f, &buf, buf.len, &written));
|
||||
try testing.expectEqualStrings("World", buf[0..written]);
|
||||
}
|
||||
|
||||
test "format html" {
|
||||
var t: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
|
||||
@@ -31,7 +31,7 @@ pub const CGridRef = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn toPin(self: CGridRef) ?PageList.Pin {
|
||||
pub fn toPin(self: CGridRef) ?PageList.Pin {
|
||||
return .{
|
||||
.node = self.node orelse return null,
|
||||
.x = self.x,
|
||||
|
||||
@@ -12,6 +12,7 @@ pub const types = @import("types.zig");
|
||||
pub const modes = @import("modes.zig");
|
||||
pub const osc = @import("osc.zig");
|
||||
pub const render = @import("render.zig");
|
||||
pub const selection = @import("selection.zig");
|
||||
pub const key_event = @import("key_event.zig");
|
||||
pub const key_encode = @import("key_encode.zig");
|
||||
pub const mouse_event = @import("mouse_event.zig");
|
||||
@@ -163,6 +164,7 @@ test {
|
||||
_ = modes;
|
||||
_ = osc;
|
||||
_ = render;
|
||||
_ = selection;
|
||||
_ = key_event;
|
||||
_ = key_encode;
|
||||
_ = mouse_event;
|
||||
|
||||
16
src/terminal/c/selection.zig
Normal file
16
src/terminal/c/selection.zig
Normal file
@@ -0,0 +1,16 @@
|
||||
const grid_ref = @import("grid_ref.zig");
|
||||
const Selection = @import("../Selection.zig");
|
||||
|
||||
/// C: GhosttySelection
|
||||
pub const CSelection = extern struct {
|
||||
size: usize = @sizeOf(CSelection),
|
||||
start: grid_ref.CGridRef,
|
||||
end: grid_ref.CGridRef,
|
||||
rectangle: bool = false,
|
||||
|
||||
pub fn toZig(self: CSelection) ?Selection {
|
||||
const start_pin = self.start.toPin() orelse return null;
|
||||
const end_pin = self.end.toPin() orelse return null;
|
||||
return Selection.init(start_pin, end_pin, self.rectangle);
|
||||
}
|
||||
};
|
||||
@@ -13,6 +13,7 @@ const size_report = @import("size_report.zig");
|
||||
|
||||
const terminal = @import("terminal.zig");
|
||||
const formatter = @import("formatter.zig");
|
||||
const selection = @import("selection.zig");
|
||||
const render = @import("render.zig");
|
||||
const style_c = @import("style.zig");
|
||||
const mouse_encode = @import("mouse_encode.zig");
|
||||
@@ -26,6 +27,7 @@ pub const structs: std.StaticStringMap(StructInfo) = .initComptime(.{
|
||||
.{ "GhosttyDeviceAttributesSecondary", StructInfo.init(terminal.DeviceAttributes.Secondary) },
|
||||
.{ "GhosttyDeviceAttributesTertiary", StructInfo.init(terminal.DeviceAttributes.Tertiary) },
|
||||
.{ "GhosttyFormatterTerminalOptions", StructInfo.init(formatter.TerminalOptions) },
|
||||
.{ "GhosttySelection", StructInfo.init(selection.CSelection) },
|
||||
.{ "GhosttyFormatterTerminalExtra", StructInfo.init(formatter.TerminalOptions.Extra) },
|
||||
.{ "GhosttyFormatterScreenExtra", StructInfo.init(formatter.ScreenOptions.Extra) },
|
||||
.{ "GhosttyGridRef", StructInfo.init(grid_ref.CGridRef) },
|
||||
|
||||
Reference in New Issue
Block a user