terminal: support disabling kitty graphics protocol
parent
1b46884e72
commit
811f9f05d0
|
|
@ -1153,9 +1153,11 @@ const ReflowCursor = struct {
|
|||
self.page_cell.style_id = id;
|
||||
}
|
||||
|
||||
// Copy Kitty virtual placeholder status
|
||||
if (cell.codepoint() == kitty.graphics.unicode.placeholder) {
|
||||
self.page_row.kitty_virtual_placeholder = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// Copy Kitty virtual placeholder status
|
||||
if (cell.codepoint() == kitty.graphics.unicode.placeholder) {
|
||||
self.page_row.kitty_virtual_placeholder = true;
|
||||
}
|
||||
}
|
||||
|
||||
self.cursorForward();
|
||||
|
|
@ -8917,6 +8919,8 @@ test "PageList resize reflow less cols to wrap a multi-codepoint grapheme with a
|
|||
}
|
||||
|
||||
test "PageList resize reflow less cols copy kitty placeholder" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
|
|
@ -8956,6 +8960,8 @@ test "PageList resize reflow less cols copy kitty placeholder" {
|
|||
}
|
||||
|
||||
test "PageList resize reflow more cols clears kitty placeholder" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
|
|
@ -8997,6 +9003,8 @@ test "PageList resize reflow more cols clears kitty placeholder" {
|
|||
}
|
||||
|
||||
test "PageList resize reflow wrap moves kitty placeholder" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,10 @@ protected_mode: ansi.ProtectedMode = .off,
|
|||
kitty_keyboard: kitty.KeyFlagStack = .{},
|
||||
|
||||
/// Kitty graphics protocol state.
|
||||
kitty_images: kitty.graphics.ImageStorage = .{},
|
||||
kitty_images: if (build_options.kitty_graphics)
|
||||
kitty.graphics.ImageStorage
|
||||
else
|
||||
struct {} = .{},
|
||||
|
||||
/// Dirty flags for the renderer.
|
||||
dirty: Dirty = .{},
|
||||
|
|
@ -208,7 +211,9 @@ pub fn init(
|
|||
}
|
||||
|
||||
pub fn deinit(self: *Screen) void {
|
||||
self.kitty_images.deinit(self.alloc, self);
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
self.kitty_images.deinit(self.alloc, self);
|
||||
}
|
||||
self.cursor.deinit(self.alloc);
|
||||
self.pages.deinit();
|
||||
}
|
||||
|
|
@ -269,9 +274,11 @@ pub fn reset(self: *Screen) void {
|
|||
.page_cell = cursor_rac.cell,
|
||||
};
|
||||
|
||||
// Reset kitty graphics storage
|
||||
self.kitty_images.deinit(self.alloc, self);
|
||||
self.kitty_images = .{ .dirty = true };
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// Reset kitty graphics storage
|
||||
self.kitty_images.deinit(self.alloc, self);
|
||||
self.kitty_images = .{ .dirty = true };
|
||||
}
|
||||
|
||||
// Reset our basic state
|
||||
self.saved_cursor = null;
|
||||
|
|
@ -690,8 +697,10 @@ pub fn cursorDownScroll(self: *Screen) !void {
|
|||
assert(self.cursor.y == self.pages.rows - 1);
|
||||
defer self.assertIntegrity();
|
||||
|
||||
// Scrolling dirties the images because it updates their placements pins.
|
||||
self.kitty_images.dirty = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// Scrolling dirties the images because it updates their placements pins.
|
||||
self.kitty_images.dirty = true;
|
||||
}
|
||||
|
||||
// If we have no scrollback, then we shift all our rows instead.
|
||||
if (self.no_scrollback) {
|
||||
|
|
@ -1154,10 +1163,12 @@ pub const Scroll = union(enum) {
|
|||
pub fn scroll(self: *Screen, behavior: Scroll) void {
|
||||
defer self.assertIntegrity();
|
||||
|
||||
// No matter what, scrolling marks our image state as dirty since
|
||||
// it could move placements. If there are no placements or no images
|
||||
// this is still a very cheap operation.
|
||||
self.kitty_images.dirty = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// No matter what, scrolling marks our image state as dirty since
|
||||
// it could move placements. If there are no placements or no images
|
||||
// this is still a very cheap operation.
|
||||
self.kitty_images.dirty = true;
|
||||
}
|
||||
|
||||
switch (behavior) {
|
||||
.active => self.pages.scroll(.{ .active = {} }),
|
||||
|
|
@ -1176,10 +1187,12 @@ pub fn scrollClear(self: *Screen) !void {
|
|||
try self.pages.scrollClear();
|
||||
self.cursorReload();
|
||||
|
||||
// No matter what, scrolling marks our image state as dirty since
|
||||
// it could move placements. If there are no placements or no images
|
||||
// this is still a very cheap operation.
|
||||
self.kitty_images.dirty = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// No matter what, scrolling marks our image state as dirty since
|
||||
// it could move placements. If there are no placements or no images
|
||||
// this is still a very cheap operation.
|
||||
self.kitty_images.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the viewport is scrolled to the bottom of the screen.
|
||||
|
|
@ -1299,14 +1312,16 @@ pub fn clearCells(
|
|||
if (cells.len == self.pages.cols) row.styled = false;
|
||||
}
|
||||
|
||||
if (row.kitty_virtual_placeholder and
|
||||
cells.len == self.pages.cols)
|
||||
{
|
||||
for (cells) |c| {
|
||||
if (c.codepoint() == kitty.graphics.unicode.placeholder) {
|
||||
break;
|
||||
}
|
||||
} else row.kitty_virtual_placeholder = false;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
if (row.kitty_virtual_placeholder and
|
||||
cells.len == self.pages.cols)
|
||||
{
|
||||
for (cells) |c| {
|
||||
if (c.codepoint() == kitty.graphics.unicode.placeholder) {
|
||||
break;
|
||||
}
|
||||
} else row.kitty_virtual_placeholder = false;
|
||||
}
|
||||
}
|
||||
|
||||
@memset(cells, self.blankCell());
|
||||
|
|
@ -1570,8 +1585,10 @@ fn resizeInternal(
|
|||
) !void {
|
||||
defer self.assertIntegrity();
|
||||
|
||||
// No matter what we mark our image state as dirty
|
||||
self.kitty_images.dirty = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// No matter what we mark our image state as dirty
|
||||
self.kitty_images.dirty = true;
|
||||
}
|
||||
|
||||
// Release the cursor style while resizing just
|
||||
// in case the cursor ends up on a different page.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
const Terminal = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const build_options = @import("terminal_options");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
|
|
@ -679,8 +680,10 @@ fn printCell(
|
|||
|
||||
// If this is a Kitty unicode placeholder then we need to mark the
|
||||
// row so that the renderer can lookup rows with these much faster.
|
||||
if (c == kitty.graphics.unicode.placeholder) {
|
||||
self.screen.cursor.page_row.kitty_virtual_placeholder = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
if (c == kitty.graphics.unicode.placeholder) {
|
||||
self.screen.cursor.page_row.kitty_virtual_placeholder = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We check for an active hyperlink first because setHyperlink
|
||||
|
|
@ -1143,8 +1146,10 @@ pub fn index(self: *Terminal) !void {
|
|||
self.screen.cursor.x >= self.scrolling_region.left and
|
||||
self.screen.cursor.x <= self.scrolling_region.right)
|
||||
{
|
||||
// Scrolling dirties the images because it updates their placements pins.
|
||||
self.screen.kitty_images.dirty = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// Scrolling dirties the images because it updates their placements pins.
|
||||
self.screen.kitty_images.dirty = true;
|
||||
}
|
||||
|
||||
// If our scrolling region is at the top, we create scrollback.
|
||||
if (self.scrolling_region.top == 0 and
|
||||
|
|
@ -1472,8 +1477,10 @@ pub fn insertLines(self: *Terminal, count: usize) void {
|
|||
self.screen.cursor.x < self.scrolling_region.left or
|
||||
self.screen.cursor.x > self.scrolling_region.right) return;
|
||||
|
||||
// Scrolling dirties the images because it updates their placements pins.
|
||||
self.screen.kitty_images.dirty = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// Scrolling dirties the images because it updates their placements pins.
|
||||
self.screen.kitty_images.dirty = true;
|
||||
}
|
||||
|
||||
// At the end we need to return the cursor to the row it started on.
|
||||
const start_y = self.screen.cursor.y;
|
||||
|
|
@ -1676,8 +1683,10 @@ pub fn deleteLines(self: *Terminal, count: usize) void {
|
|||
self.screen.cursor.x < self.scrolling_region.left or
|
||||
self.screen.cursor.x > self.scrolling_region.right) return;
|
||||
|
||||
// Scrolling dirties the images because it updates their placements pins.
|
||||
self.screen.kitty_images.dirty = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// Scrolling dirties the images because it updates their placements pins.
|
||||
self.screen.kitty_images.dirty = true;
|
||||
}
|
||||
|
||||
// At the end we need to return the cursor to the row it started on.
|
||||
const start_y = self.screen.cursor.y;
|
||||
|
|
@ -2136,12 +2145,14 @@ pub fn eraseDisplay(
|
|||
// Unsets pending wrap state
|
||||
self.screen.cursor.pending_wrap = false;
|
||||
|
||||
// Clear all Kitty graphics state for this screen
|
||||
self.screen.kitty_images.delete(
|
||||
self.screen.alloc,
|
||||
self,
|
||||
.{ .all = true },
|
||||
);
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// Clear all Kitty graphics state for this screen
|
||||
self.screen.kitty_images.delete(
|
||||
self.screen.alloc,
|
||||
self,
|
||||
.{ .all = true },
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
.complete => {
|
||||
|
|
@ -2195,12 +2206,14 @@ pub fn eraseDisplay(
|
|||
// Unsets pending wrap state
|
||||
self.screen.cursor.pending_wrap = false;
|
||||
|
||||
// Clear all Kitty graphics state for this screen
|
||||
self.screen.kitty_images.delete(
|
||||
self.screen.alloc,
|
||||
self,
|
||||
.{ .all = true },
|
||||
);
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// Clear all Kitty graphics state for this screen
|
||||
self.screen.kitty_images.delete(
|
||||
self.screen.alloc,
|
||||
self,
|
||||
.{ .all = true },
|
||||
);
|
||||
}
|
||||
|
||||
// Cleared screen dirty bit
|
||||
self.flags.dirty.clear = true;
|
||||
|
|
@ -2574,10 +2587,12 @@ pub fn switchScreen(self: *Terminal, t: ScreenType) ?*Screen {
|
|||
// Clear our selection
|
||||
self.screen.clearSelection();
|
||||
|
||||
// Mark kitty images as dirty so they redraw. Without this set
|
||||
// the images will remain where they were (the dirty bit on
|
||||
// the screen only tracks the terminal grid, not the images).
|
||||
self.screen.kitty_images.dirty = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
// Mark kitty images as dirty so they redraw. Without this set
|
||||
// the images will remain where they were (the dirty bit on
|
||||
// the screen only tracks the terminal grid, not the images).
|
||||
self.screen.kitty_images.dirty = true;
|
||||
}
|
||||
|
||||
// Mark our terminal as dirty to redraw the grid.
|
||||
self.flags.dirty.clear = true;
|
||||
|
|
@ -3862,6 +3877,8 @@ test "Terminal: print invoke charset single" {
|
|||
}
|
||||
|
||||
test "Terminal: print kitty unicode placeholder" {
|
||||
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
|
||||
|
||||
var t = try init(testing.allocator, .{ .cols = 10, .rows = 10 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const std = @import("std");
|
||||
const build_options = @import("terminal_options");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
|
|
@ -33,17 +34,22 @@ pub const Handler = struct {
|
|||
.identify => {
|
||||
switch (byte) {
|
||||
// Kitty graphics protocol
|
||||
'G' => self.state = .{ .kitty = kitty_gfx.CommandParser.init(alloc) },
|
||||
'G' => self.state = if (comptime build_options.kitty_graphics)
|
||||
.{ .kitty = kitty_gfx.CommandParser.init(alloc) }
|
||||
else
|
||||
.{ .ignore = {} },
|
||||
|
||||
// Unknown
|
||||
else => self.state = .{ .ignore = {} },
|
||||
}
|
||||
},
|
||||
|
||||
.kitty => |*p| p.feed(byte) catch |err| {
|
||||
log.warn("kitty graphics protocol error: {}", .{err});
|
||||
self.state = .{ .ignore = {} };
|
||||
},
|
||||
.kitty => |*p| if (comptime build_options.kitty_graphics) {
|
||||
p.feed(byte) catch |err| {
|
||||
log.warn("kitty graphics protocol error: {}", .{err});
|
||||
self.state = .{ .ignore = {} };
|
||||
};
|
||||
} else unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,6 +63,8 @@ pub const Handler = struct {
|
|||
.inactive => unreachable,
|
||||
.ignore, .identify => null,
|
||||
.kitty => |*p| kitty: {
|
||||
if (comptime !build_options.kitty_graphics) unreachable;
|
||||
|
||||
const command = p.complete() catch |err| {
|
||||
log.warn("kitty graphics protocol error: {}", .{err});
|
||||
break :kitty null;
|
||||
|
|
@ -81,23 +89,35 @@ pub const State = union(enum) {
|
|||
identify: void,
|
||||
|
||||
/// Kitty graphics protocol
|
||||
kitty: kitty_gfx.CommandParser,
|
||||
kitty: if (build_options.kitty_graphics)
|
||||
kitty_gfx.CommandParser
|
||||
else
|
||||
void,
|
||||
|
||||
pub fn deinit(self: *State) void {
|
||||
switch (self.*) {
|
||||
.inactive, .ignore, .identify => {},
|
||||
.kitty => |*v| v.deinit(),
|
||||
.kitty => |*v| if (comptime build_options.kitty_graphics)
|
||||
v.deinit()
|
||||
else
|
||||
unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Possible APC commands.
|
||||
pub const Command = union(enum) {
|
||||
kitty: kitty_gfx.Command,
|
||||
kitty: if (build_options.kitty_graphics)
|
||||
kitty_gfx.Command
|
||||
else
|
||||
void,
|
||||
|
||||
pub fn deinit(self: *Command, alloc: Allocator) void {
|
||||
switch (self.*) {
|
||||
.kitty => |*v| v.deinit(alloc),
|
||||
.kitty => |*v| if (comptime build_options.kitty_graphics)
|
||||
v.deinit(alloc)
|
||||
else
|
||||
unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ pub fn addOptions(
|
|||
opts.addOption(bool, "slow_runtime_safety", v.slow_runtime_safety);
|
||||
|
||||
// These are synthesized based on other options.
|
||||
opts.addOption(bool, "kitty_graphics", v.oniguruma);
|
||||
opts.addOption(bool, "tmux_control_mode", v.oniguruma);
|
||||
|
||||
m.addOptions("terminal_options", opts);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
//! Types and functions related to Kitty protocols.
|
||||
|
||||
const build_options = @import("terminal_options");
|
||||
|
||||
const key = @import("kitty/key.zig");
|
||||
pub const color = @import("kitty/color.zig");
|
||||
pub const graphics = @import("kitty/graphics.zig");
|
||||
pub const graphics = if (build_options.kitty_graphics) @import("kitty/graphics.zig") else struct {};
|
||||
|
||||
pub const KeyFlags = key.Flags;
|
||||
pub const KeyFlagStack = key.FlagStack;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const mem = std.mem;
|
|||
const assert = std.debug.assert;
|
||||
const Allocator = mem.Allocator;
|
||||
const RGB = @import("color.zig").RGB;
|
||||
const kitty = @import("kitty.zig");
|
||||
const kitty_color = @import("kitty/color.zig");
|
||||
const osc_color = @import("osc/color.zig");
|
||||
pub const color = osc_color;
|
||||
|
||||
|
|
@ -132,7 +132,7 @@ pub const Command = union(enum) {
|
|||
|
||||
/// Kitty color protocol, OSC 21
|
||||
/// https://sw.kovidgoyal.net/kitty/color-stack/#id1
|
||||
kitty_color_protocol: kitty.color.OSC,
|
||||
kitty_color_protocol: kitty_color.OSC,
|
||||
|
||||
/// Show a desktop notification (OSC 9 or OSC 777)
|
||||
show_desktop_notification: struct {
|
||||
|
|
@ -796,7 +796,7 @@ pub const Parser = struct {
|
|||
|
||||
self.command = .{
|
||||
.kitty_color_protocol = .{
|
||||
.list = std.ArrayList(kitty.color.OSC.Request).init(alloc),
|
||||
.list = std.ArrayList(kitty_color.OSC.Request).init(alloc),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -1490,7 +1490,7 @@ pub const Parser = struct {
|
|||
return;
|
||||
}
|
||||
|
||||
const key = kitty.color.Kind.parse(self.temp_state.key) orelse {
|
||||
const key = kitty_color.Kind.parse(self.temp_state.key) orelse {
|
||||
log.warn("unknown key in kitty color protocol: {s}", .{self.temp_state.key});
|
||||
return;
|
||||
};
|
||||
|
|
@ -1504,7 +1504,7 @@ pub const Parser = struct {
|
|||
switch (self.command) {
|
||||
.kitty_color_protocol => |*v| {
|
||||
// Cap our allocation amount for our list.
|
||||
if (v.list.items.len >= @as(usize, kitty.color.Kind.max) * 2) {
|
||||
if (v.list.items.len >= @as(usize, kitty_color.Kind.max) * 2) {
|
||||
self.state = .invalid;
|
||||
log.warn("exceeded limit for number of keys in kitty color protocol, ignoring", .{});
|
||||
return;
|
||||
|
|
@ -2600,7 +2600,7 @@ test "OSC: hyperlink end" {
|
|||
|
||||
test "OSC: kitty color protocol" {
|
||||
const testing = std.testing;
|
||||
const Kind = kitty.color.Kind;
|
||||
const Kind = kitty_color.Kind;
|
||||
|
||||
var p: Parser = .initAlloc(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
|
|
|||
|
|
@ -890,8 +890,10 @@ pub const Page = struct {
|
|||
error.NeedsRehash => return error.StyleSetNeedsRehash,
|
||||
} orelse src_cell.style_id;
|
||||
}
|
||||
if (src_cell.codepoint() == kitty.graphics.unicode.placeholder) {
|
||||
dst_row.kitty_virtual_placeholder = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
if (src_cell.codepoint() == kitty.graphics.unicode.placeholder) {
|
||||
dst_row.kitty_virtual_placeholder = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -980,8 +982,10 @@ pub const Page = struct {
|
|||
dst.hyperlink = true;
|
||||
dst_row.hyperlink = true;
|
||||
}
|
||||
if (src.codepoint() == kitty.graphics.unicode.placeholder) {
|
||||
dst_row.kitty_virtual_placeholder = true;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
if (src.codepoint() == kitty.graphics.unicode.placeholder) {
|
||||
dst_row.kitty_virtual_placeholder = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1002,7 +1006,9 @@ pub const Page = struct {
|
|||
src_row.grapheme = false;
|
||||
src_row.hyperlink = false;
|
||||
src_row.styled = false;
|
||||
src_row.kitty_virtual_placeholder = false;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
src_row.kitty_virtual_placeholder = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1100,14 +1106,16 @@ pub const Page = struct {
|
|||
if (cells.len == self.size.cols) row.styled = false;
|
||||
}
|
||||
|
||||
if (row.kitty_virtual_placeholder and
|
||||
cells.len == self.size.cols)
|
||||
{
|
||||
for (cells) |c| {
|
||||
if (c.codepoint() == kitty.graphics.unicode.placeholder) {
|
||||
break;
|
||||
}
|
||||
} else row.kitty_virtual_placeholder = false;
|
||||
if (comptime build_options.kitty_graphics) {
|
||||
if (row.kitty_virtual_placeholder and
|
||||
cells.len == self.size.cols)
|
||||
{
|
||||
for (cells) |c| {
|
||||
if (c.codepoint() == kitty.graphics.unicode.placeholder) {
|
||||
break;
|
||||
}
|
||||
} else row.kitty_virtual_placeholder = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Zero the cells as u64s since empirically this seems
|
||||
|
|
@ -1929,6 +1937,9 @@ pub const Row = packed struct(u64) {
|
|||
|
||||
/// True if this row contains a virtual placeholder for the Kitty
|
||||
/// graphics protocol. (U+10EEEE)
|
||||
// Note: We keep this as memory-using even if the kitty graphics
|
||||
// feature is disabled because we want to keep our padding and
|
||||
// everything throughout the same.
|
||||
kitty_virtual_placeholder: bool = false,
|
||||
|
||||
_padding: u23 = 0,
|
||||
|
|
|
|||
Loading…
Reference in New Issue