diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index b5d511b7f..b8e16dbf7 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -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; diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index cc7aa4a16..7be4d7c12 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -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. diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 0d659eb9a..c9a1f1d1d 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -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); diff --git a/src/terminal/apc.zig b/src/terminal/apc.zig index 26c59729a..9663afb3e 100644 --- a/src/terminal/apc.zig +++ b/src/terminal/apc.zig @@ -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, } } }; diff --git a/src/terminal/build_options.zig b/src/terminal/build_options.zig index 98d9bc8fa..57129f5f0 100644 --- a/src/terminal/build_options.zig +++ b/src/terminal/build_options.zig @@ -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); diff --git a/src/terminal/kitty.zig b/src/terminal/kitty.zig index 482919f9f..0868a7710 100644 --- a/src/terminal/kitty.zig +++ b/src/terminal/kitty.zig @@ -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; diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index 028fcdf0a..bd7337b42 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -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(); diff --git a/src/terminal/page.zig b/src/terminal/page.zig index f5ee1de71..086bfe96c 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -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,