diff --git a/src/Surface.zig b/src/Surface.zig index f41e2f409..db8b4474e 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -186,7 +186,7 @@ const Mouse = struct { /// The point at which the left mouse click happened. This is in screen /// coordinates so that scrolling preserves the location. left_click_pin: ?*terminal.Pin = null, - left_click_screen: terminal.ScreenType = .primary, + left_click_screen: terminal.ScreenSet.Key = .primary, /// The starting xpos/ypos of the left click. Note that if scrolling occurs, /// these will point to different "cells", but the xpos/ypos will stay @@ -1065,7 +1065,7 @@ fn selectionScrollTick(self: *Surface) !void { // If our screen changed while this is happening, we stop our // selection scroll. - if (self.mouse.left_click_screen != t.active_screen) { + if (self.mouse.left_click_screen != t.screens.active_key) { self.io.queueMessage( .{ .selection_scroll = false }, .locked, @@ -1703,7 +1703,7 @@ pub fn dumpTextLocked( // If our bottom right pin is before the viewport, then we can't // possibly have this text be within the viewport. const vp_tl_pin = self.io.terminal.screen.pages.getTopLeft(.viewport); - const br_pin = sel.bottomRight(&self.io.terminal.screen); + const br_pin = sel.bottomRight(self.io.terminal.screen); if (br_pin.before(vp_tl_pin)) break :viewport null; // If our top-left pin is after the viewport, then we can't possibly @@ -1714,7 +1714,7 @@ pub fn dumpTextLocked( log.warn("viewport bottom-right pin not found, bug?", .{}); break :viewport null; }; - const tl_pin = sel.topLeft(&self.io.terminal.screen); + const tl_pin = sel.topLeft(self.io.terminal.screen); if (vp_br_pin.before(tl_pin)) break :viewport null; // We established that our top-left somewhere before the viewport @@ -1984,7 +1984,7 @@ fn copySelectionToClipboards( var contents: std.ArrayList(apprt.ClipboardContent) = .empty; switch (format) { .plain => { - var formatter: ScreenFormatter = .init(&self.io.terminal.screen, opts); + var formatter: ScreenFormatter = .init(self.io.terminal.screen, opts); formatter.content = .{ .selection = sel }; try formatter.format(&aw.writer); try contents.append(alloc, .{ @@ -1994,7 +1994,7 @@ fn copySelectionToClipboards( }, .vt => { - var formatter: ScreenFormatter = .init(&self.io.terminal.screen, opts: { + var formatter: ScreenFormatter = .init(self.io.terminal.screen, opts: { var copy = opts; copy.emit = .vt; break :opts copy; @@ -2011,7 +2011,7 @@ fn copySelectionToClipboards( }, .html => { - var formatter: ScreenFormatter = .init(&self.io.terminal.screen, opts: { + var formatter: ScreenFormatter = .init(self.io.terminal.screen, opts: { var copy = opts; copy.emit = .html; break :opts copy; @@ -2029,7 +2029,7 @@ fn copySelectionToClipboards( .mixed => { // First, generate plain text with codepoint mappings applied - var formatter: ScreenFormatter = .init(&self.io.terminal.screen, opts); + var formatter: ScreenFormatter = .init(self.io.terminal.screen, opts); formatter.content = .{ .selection = sel }; try formatter.format(&aw.writer); try contents.append(alloc, .{ @@ -2039,7 +2039,7 @@ fn copySelectionToClipboards( assert(aw.written().len == 0); // Second, generate HTML without codepoint mappings - formatter = .init(&self.io.terminal.screen, opts: { + formatter = .init(self.io.terminal.screen, opts: { var copy = opts; copy.emit = .html; @@ -3098,7 +3098,7 @@ pub fn scrollCallback( // we convert to cursor keys. This only happens if we're: // (1) alt screen (2) no explicit mouse reporting and (3) alt // scroll mode enabled. - if (self.io.terminal.active_screen == .alternate and + if (self.io.terminal.screens.active_key == .alternate and self.io.terminal.flags.mouse_event == .none and self.io.terminal.modes.get(.mouse_alternate_scroll)) { @@ -3506,7 +3506,7 @@ pub fn mouseButtonCallback( { const pos = try self.rt_surface.getCursorPos(); const point = self.posToViewport(pos.x, pos.y); - const screen = &self.renderer_state.terminal.screen; + const screen: *terminal.Screen = self.renderer_state.terminal.screen; const p = screen.pages.pin(.{ .viewport = point }) orelse { log.warn("failed to get pin for clicked point", .{}); return false; @@ -3681,7 +3681,7 @@ pub fn mouseButtonCallback( self.renderer_state.mutex.lock(); defer self.renderer_state.mutex.unlock(); const t: *terminal.Terminal = self.renderer_state.terminal; - const screen = &self.renderer_state.terminal.screen; + const screen: *terminal.Screen = self.renderer_state.terminal.screen; const pos = try self.rt_surface.getCursorPos(); const pin = pin: { @@ -3717,14 +3717,15 @@ pub fn mouseButtonCallback( } if (self.mouse.left_click_pin) |prev| { - const pin_screen = t.getScreen(self.mouse.left_click_screen); - pin_screen.pages.untrackPin(prev); + if (t.screens.get(self.mouse.left_click_screen)) |pin_screen| { + pin_screen.pages.untrackPin(prev); + } self.mouse.left_click_pin = null; } // Store it self.mouse.left_click_pin = pin; - self.mouse.left_click_screen = t.active_screen; + self.mouse.left_click_screen = t.screens.active_key; self.mouse.left_click_xpos = pos.x; self.mouse.left_click_ypos = pos.y; @@ -3808,7 +3809,7 @@ pub fn mouseButtonCallback( defer self.renderer_state.mutex.unlock(); // Get our viewport pin - const screen = &self.renderer_state.terminal.screen; + const screen: *terminal.Screen = self.renderer_state.terminal.screen; const pin = pin: { const pos = try self.rt_surface.getCursorPos(); const pt_viewport = self.posToViewport(pos.x, pos.y); @@ -3911,7 +3912,7 @@ fn clickMoveCursor(self: *Surface, to: terminal.Pin) !void { // Click to move cursor only works on the primary screen where prompts // exist. This means that alt screen multiplexers like tmux will not // support this feature. It is just too messy. - if (t.active_screen != .primary) return; + if (t.screens.active_key != .primary) return; // This flag is only set if we've seen at least one semantic prompt // OSC sequence. If we've never seen that sequence, we can't possibly @@ -3964,7 +3965,7 @@ fn linkAtPos( terminal.Selection, } { // Convert our cursor position to a screen point. - const screen = &self.renderer_state.terminal.screen; + const screen: *terminal.Screen = self.renderer_state.terminal.screen; const mouse_pin: terminal.Pin = mouse_pin: { const point = self.posToViewport(pos.x, pos.y); const pin = screen.pages.pin(.{ .viewport = point }) orelse { @@ -4237,7 +4238,7 @@ pub fn cursorPosCallback( insp.mouse.last_xpos = pos.x; insp.mouse.last_ypos = pos.y; - const screen = &self.renderer_state.terminal.screen; + const screen: *terminal.Screen = self.renderer_state.terminal.screen; insp.mouse.last_point = screen.pages.pin(.{ .viewport = .{ .x = pos_vp.x, .y = pos_vp.y, @@ -4303,7 +4304,7 @@ pub fn cursorPosCallback( // invalidate our pin or mouse state because if the screen switches // back then we can continue our selection. const t: *terminal.Terminal = self.renderer_state.terminal; - if (self.mouse.left_click_screen != t.active_screen) break :select; + if (self.mouse.left_click_screen != t.screens.active_key) break :select; // All roads lead to requiring a re-render at this point. try self.queueRender(); @@ -4328,7 +4329,7 @@ pub fn cursorPosCallback( } // Convert to points - const screen = &t.screen; + const screen: *terminal.Screen = t.screen; const pin = screen.pages.pin(.{ .viewport = .{ .x = pos_vp.x, @@ -4357,7 +4358,7 @@ fn dragLeftClickDouble( self: *Surface, drag_pin: terminal.Pin, ) !void { - const screen = &self.io.terminal.screen; + const screen: *terminal.Screen = self.io.terminal.screen; const click_pin = self.mouse.left_click_pin.?.*; // Get the word closest to our starting click. @@ -4397,7 +4398,7 @@ fn dragLeftClickTriple( self: *Surface, drag_pin: terminal.Pin, ) !void { - const screen = &self.io.terminal.screen; + const screen: *terminal.Screen = self.io.terminal.screen; const click_pin = self.mouse.left_click_pin.?.*; // Get the line selection under our current drag point. If there isn't a @@ -4930,7 +4931,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool { self.renderer_state.mutex.lock(); defer self.renderer_state.mutex.unlock(); - if (self.io.terminal.active_screen == .alternate) return false; + if (self.io.terminal.screens.active_key == .alternate) return false; } self.io.queueMessage(.{ @@ -4966,7 +4967,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool self.renderer_state.mutex.lock(); defer self.renderer_state.mutex.unlock(); const sel = self.io.terminal.screen.selection orelse return false; - const tl = sel.topLeft(&self.io.terminal.screen); + const tl = sel.topLeft(self.io.terminal.screen); self.io.terminal.screen.scroll(.{ .pin = tl }); } @@ -5220,7 +5221,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool self.renderer_state.mutex.lock(); defer self.renderer_state.mutex.unlock(); - const screen = &self.io.terminal.screen; + const screen: *terminal.Screen = self.io.terminal.screen; const sel = if (screen.selection) |*sel| sel else { // If we don't have a selection we do not perform this // action, allowing the keybind to fall through to the @@ -5340,7 +5341,7 @@ fn writeScreenFile( .history => history: { // We do not support this for alternate screens // because they don't have scrollback anyways. - if (self.io.terminal.active_screen == .alternate) { + if (self.io.terminal.screens.active_key == .alternate) { break :history null; } @@ -5371,7 +5372,7 @@ fn writeScreenFile( }; const ScreenFormatter = terminal.formatter.ScreenFormatter; - var formatter: ScreenFormatter = .init(&self.io.terminal.screen, .{ + var formatter: ScreenFormatter = .init(self.io.terminal.screen, .{ .emit = switch (write_screen.emit) { .plain => .plain, .vt => .vt, @@ -5384,7 +5385,7 @@ fn writeScreenFile( .palette = &self.io.terminal.colors.palette.current, }); formatter.content = .{ .selection = sel.ordered( - &self.io.terminal.screen, + self.io.terminal.screen, .forward, ) }; try formatter.format(buf_writer); diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 1faa0b9c6..1a713310f 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1578,7 +1578,7 @@ pub const CAPI = struct { defer surface.core_surface.renderer_state.mutex.unlock(); const core_sel = sel.core( - &surface.core_surface.renderer_state.terminal.screen, + surface.core_surface.renderer_state.terminal.screen, ) orelse return false; return readTextLocked(surface, core_sel, result); @@ -2137,7 +2137,7 @@ pub const CAPI = struct { // Get our word selection const sel = sel: { - const screen = &surface.renderer_state.terminal.screen; + const screen: *terminal.Screen = surface.renderer_state.terminal.screen; const pos = try ptr.getCursorPos(); const pt_viewport = surface.posToViewport(pos.x, pos.y); const pin = screen.pages.pin(.{ diff --git a/src/inspector/Inspector.zig b/src/inspector/Inspector.zig index 92da5a362..0f2371400 100644 --- a/src/inspector/Inspector.zig +++ b/src/inspector/Inspector.zig @@ -304,7 +304,7 @@ fn renderScreenWindow(self: *Inspector) void { )) return; const t = self.surface.renderer_state.terminal; - const screen = &t.screen; + const screen: *terminal.Screen = t.screen; { _ = cimgui.c.igBeginTable( @@ -324,7 +324,7 @@ fn renderScreenWindow(self: *Inspector) void { } { _ = cimgui.c.igTableSetColumnIndex(1); - cimgui.c.igText("%s", @tagName(t.active_screen).ptr); + cimgui.c.igText("%s", @tagName(t.screens.active_key).ptr); } } } diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 0b4c55896..905261b9f 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -1066,7 +1066,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { bg: terminal.color.RGB, fg: terminal.color.RGB, screen: terminal.Screen, - screen_type: terminal.ScreenType, + screen_type: terminal.ScreenSet.Key, mouse: renderer.State.Mouse, preedit: ?renderer.State.Preedit, cursor_color: ?terminal.color.RGB, @@ -1207,7 +1207,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .bg = bg, .fg = fg, .screen = screen_copy, - .screen_type = state.terminal.active_screen, + .screen_type = state.terminal.screens.active_key, .mouse = state.mouse, .preedit = preedit, .cursor_color = state.terminal.colors.cursor.get(), @@ -2317,7 +2317,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { self: *Self, wants_rebuild: bool, screen: *terminal.Screen, - screen_type: terminal.ScreenType, + screen_type: terminal.ScreenSet.Key, mouse: renderer.State.Mouse, preedit: ?renderer.State.Preedit, cursor_style_: ?renderer.CursorStyle, diff --git a/src/renderer/link.zig b/src/renderer/link.zig index 40a25ea19..ec4000f65 100644 --- a/src/renderer/link.zig +++ b/src/renderer/link.zig @@ -609,7 +609,7 @@ test "matchset osc8" { // Initialize our terminal var t = try Terminal.init(alloc, .{ .cols = 10, .rows = 10 }); defer t.deinit(alloc); - const s = &t.screen; + const s: *terminal.Screen = t.screen; try t.printString("ABC"); try t.screen.startHyperlink("http://example.com", null); @@ -624,7 +624,7 @@ test "matchset osc8" { { var match = try set.matchSet( alloc, - &t.screen, + t.screen, .{ .x = 2, .y = 0 }, inputpkg.ctrlOrSuper(.{}), ); @@ -635,7 +635,7 @@ test "matchset osc8" { // Match over link var match = try set.matchSet( alloc, - &t.screen, + t.screen, .{ .x = 3, .y = 0 }, inputpkg.ctrlOrSuper(.{}), ); diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index c4c3bed57..8ed256869 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -178,8 +178,15 @@ pub const CharsetState = struct { pub const Options = struct { cols: size.CellCountInt, rows: size.CellCountInt, + + /// The maximum size of scrollback in bytes. Zero means unlimited. Any + /// other value will be clamped to support a minimum of the active area. max_scrollback: usize = 0, + /// The total storage limit for Kitty images in bytes for this + /// screen. Kitty image storage is per-screen. + kitty_image_storage_limit: usize = 320 * 1000 * 1000, // 320MB + /// A simple, default terminal. If you rely on specific dimensions or /// scrollback (or lack of) then do not use this directly. This is just /// for callers that need some defaults. @@ -215,7 +222,7 @@ pub fn init( errdefer pages.untrackPin(page_pin); const page_rac = page_pin.rowAndCell(); - return .{ + var result: Screen = .{ .alloc = alloc, .pages = pages, .no_scrollback = opts.max_scrollback == 0, @@ -227,6 +234,18 @@ pub fn init( .page_cell = page_rac.cell, }, }; + + if (comptime build_options.kitty_graphics) { + // This can't fail because the storage is always empty at this point + // and the only fail-able case is that we have to evict images. + result.kitty_images.setLimit( + alloc, + &result, + opts.kitty_image_storage_limit, + ) catch unreachable; + } + + return result; } pub fn deinit(self: *Screen) void { diff --git a/src/terminal/ScreenSet.zig b/src/terminal/ScreenSet.zig new file mode 100644 index 000000000..1b6b053fe --- /dev/null +++ b/src/terminal/ScreenSet.zig @@ -0,0 +1,106 @@ +/// A ScreenSet holds multiple terminal screens. This is initially created +/// to handle simple primary vs alternate screens, but could be extended +/// in the future to handle N screens. +/// +/// One of the goals of this is to allow lazy initialization of screens +/// as needed. The primary screen is always initialized, but the alternate +/// screen may not be until first used. +const ScreenSet = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const testing = std.testing; +const Allocator = std.mem.Allocator; +const Screen = @import("Screen.zig"); + +/// The possible keys for screens in the screen set. +pub const Key = enum(u1) { + primary, + alternate, +}; + +/// The key value of the currently active screen. Useful for simple +/// comparisons, e.g. "is this screen the primary screen". +active_key: Key, + +/// The active screen pointer. +active: *Screen, + +/// All screens that are initialized. +all: std.EnumMap(Key, *Screen), + +pub fn init( + alloc: Allocator, + opts: Screen.Options, +) !ScreenSet { + // We need to initialize our initial primary screen + const screen = try alloc.create(Screen); + errdefer alloc.destroy(screen); + screen.* = try .init(alloc, opts); + return .{ + .active_key = .primary, + .active = screen, + .all = .init(.{ .primary = screen }), + }; +} + +pub fn deinit(self: *ScreenSet, alloc: Allocator) void { + // Destroy all initialized screens + var it = self.all.iterator(); + while (it.next()) |entry| { + entry.value.*.deinit(); + alloc.destroy(entry.value.*); + } +} + +/// Get the screen for the given key, if it is initialized. +pub fn get(self: *const ScreenSet, key: Key) ?*Screen { + return self.all.get(key); +} + +/// Get the screen for the given key, initializing it if necessary. +pub fn getInit( + self: *ScreenSet, + alloc: Allocator, + key: Key, + opts: Screen.Options, +) !*Screen { + if (self.get(key)) |screen| return screen; + const screen = try alloc.create(Screen); + errdefer alloc.destroy(screen); + screen.* = try .init(alloc, opts); + self.all.put(key, screen); + return screen; +} + +/// Remove a key from the set. The primary screen cannot be removed (asserted). +pub fn remove( + self: *ScreenSet, + alloc: Allocator, + key: Key, +) void { + assert(key != .primary); + if (self.all.fetchRemove(key)) |screen| { + screen.deinit(); + alloc.destroy(screen); + } +} + +/// Switch the active screen to the given key. Requires that the +/// screen is initialized. +pub fn switchTo(self: *ScreenSet, key: Key) void { + self.active_key = key; + self.active = self.all.get(key).?; +} + +test ScreenSet { + const alloc = testing.allocator; + var set: ScreenSet = try .init(alloc, .default); + defer set.deinit(alloc); + try testing.expectEqual(.primary, set.active_key); + + // Initialize a secondary screen + _ = try set.getInit(alloc, .alternate, .default); + set.switchTo(.alternate); + try testing.expectEqual(.alternate, set.active_key); +} diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index d9ad62ae1..185537115 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -29,6 +29,7 @@ const size = @import("size.zig"); const pagepkg = @import("page.zig"); const style = @import("style.zig"); const Screen = @import("Screen.zig"); +const ScreenSet = @import("ScreenSet.zig"); const Page = pagepkg.Page; const Cell = pagepkg.Cell; const Row = pagepkg.Row; @@ -38,18 +39,17 @@ const log = std.log.scoped(.terminal); /// Default tabstop interval const TABSTOP_INTERVAL = 8; -/// Screen type is an enum that tracks whether a screen is primary or alternate. -pub const ScreenType = enum(u1) { - primary, - alternate, -}; +/// The currently active screen. To get the type of screen this is, +/// inspect screens.active_key instead. +/// +/// Note: long term I'd like to get rid of this and force everyone +/// to go through screens instead but there's SO MUCH code that relies +/// on this property existing and it was really nasty to change all of +/// that today. +screen: *Screen, -/// Screen is the current screen state. The "active_screen" field says what -/// the current screen is. The backup screen is the opposite of the active -/// screen. -active_screen: ScreenType, -screen: Screen, -secondary_screen: Screen, +/// The set of screens behind this terminal (e.g. primary vs alternate). +screens: ScreenSet, /// Whether we're currently writing to the status line (DECSASD and DECSSDT). /// We don't support a status line currently so we just black hole this @@ -221,12 +221,19 @@ pub fn init( ) !Terminal { const cols = opts.cols; const rows = opts.rows; + + var screen_set: ScreenSet = try .init(alloc, .{ + .cols = cols, + .rows = rows, + .max_scrollback = opts.max_scrollback, + }); + errdefer screen_set.deinit(alloc); + return .{ .cols = cols, .rows = rows, - .active_screen = .primary, - .screen = try .init(alloc, .{ .cols = cols, .rows = rows, .max_scrollback = opts.max_scrollback }), - .secondary_screen = try .init(alloc, .{ .cols = cols, .rows = rows, .max_scrollback = 0 }), + .screen = screen_set.active, + .screens = screen_set, .tabstops = try .init(alloc, cols, TABSTOP_INTERVAL), .scrolling_region = .{ .top = 0, @@ -245,8 +252,7 @@ pub fn init( pub fn deinit(self: *Terminal, alloc: Allocator) void { self.tabstops.deinit(alloc); - self.screen.deinit(); - self.secondary_screen.deinit(); + self.screens.deinit(alloc); self.pwd.deinit(alloc); self.* = undefined; } @@ -266,7 +272,7 @@ pub fn vtHandler(self: *Terminal) ReadonlyHandler { /// The general allocator we should use for this terminal. fn gpa(self: *Terminal) Allocator { - return self.screen.alloc; + return self.screens.active.alloc; } /// Print UTF-8 encoded string to the terminal. @@ -1074,7 +1080,7 @@ pub fn markSemanticPrompt(self: *Terminal, p: SemanticPrompt) void { /// If the shell integration doesn't exist, this will always return false. pub fn cursorIsAtPrompt(self: *Terminal) bool { // If we're on the secondary screen, we're never at a prompt. - if (self.active_screen == .alternate) return false; + if (self.screens.active_key == .alternate) return false; // Reverse through the active const start_x, const start_y = .{ self.screen.cursor.x, self.screen.cursor.y }; @@ -2202,7 +2208,7 @@ pub fn eraseDisplay( // at a prompt scrolls the screen contents prior to clearing. // Most shells send `ESC [ H ESC [ 2 J` so we can't just check // our current cursor position. See #905 - if (self.active_screen == .primary) at_prompt: { + if (self.screens.active_key == .primary) at_prompt: { // Go from the bottom of the active up and see if we're // at a prompt. const active_br = self.screen.pages.getBottomRight( @@ -2531,25 +2537,22 @@ pub fn resize( self.tabstops = try .init(alloc, cols, 8); } - // If we're making the screen smaller, dealloc the unused items. - if (self.active_screen == .primary) { - if (self.flags.shell_redraws_prompt) { - self.screen.clearPrompt(); - } - - if (self.modes.get(.wraparound)) { - try self.screen.resize(cols, rows); - } else { - try self.screen.resizeWithoutReflow(cols, rows); - } - try self.secondary_screen.resizeWithoutReflow(cols, rows); + // Resize primary screen, which supports reflow + const primary = self.screens.get(.primary).?; + if (self.screens.active_key == .primary and + self.flags.shell_redraws_prompt) + { + primary.clearPrompt(); + } + if (self.modes.get(.wraparound)) { + try primary.resize(cols, rows); } else { - try self.screen.resizeWithoutReflow(cols, rows); - if (self.modes.get(.wraparound)) { - try self.secondary_screen.resize(cols, rows); - } else { - try self.secondary_screen.resizeWithoutReflow(cols, rows); - } + try primary.resizeWithoutReflow(cols, rows); + } + + // Alternate screen, if it exists, doesn't reflow + if (self.screens.get(.alternate)) |alt| { + try alt.resizeWithoutReflow(cols, rows); } // Whenever we resize we just mark it as a screen clear @@ -2581,14 +2584,6 @@ pub fn getPwd(self: *const Terminal) ?[]const u8 { return self.pwd.items; } -/// Get the screen pointer for the given type. -pub fn getScreen(self: *Terminal, t: ScreenType) *Screen { - return if (self.active_screen == t) - &self.screen - else - &self.secondary_screen; -} - /// Switch to the given screen type (alternate or primary). /// /// This does NOT handle behaviors such as clearing the screen, @@ -2604,40 +2599,60 @@ pub fn getScreen(self: *Terminal, t: ScreenType) *Screen { /// more than two screens in the future if needed. There isn't /// currently a spec for this, but it is something I think might /// be useful in the future. -pub fn switchScreen(self: *Terminal, t: ScreenType) ?*Screen { +pub fn switchScreen(self: *Terminal, key: ScreenSet.Key) !?*Screen { // If we're already on the requested screen we do nothing. - if (self.active_screen == t) return null; + if (self.screens.active_key == key) return null; + const old = self.screens.active; // We always end hyperlink state when switching screens. // We need to do this on the original screen. - self.screen.endHyperlink(); + old.endHyperlink(); - // Switch the screens - const old = self.screen; - self.screen = self.secondary_screen; - self.secondary_screen = old; - self.active_screen = t; + // Switch the screens/ + const new = self.screens.get(key) orelse new: { + const primary = self.screens.get(.primary).?; + break :new try self.screens.getInit( + old.alloc, + key, + .{ + .cols = self.cols, + .rows = self.rows, + .max_scrollback = switch (key) { + .primary => primary.pages.explicit_max_size, + .alternate => 0, + }, + + // Inherit our Kitty image storage limit from the primary + // screen if we have to initialize. + .kitty_image_storage_limit = primary.kitty_images.total_limit, + }, + ); + }; // The new screen should not have any hyperlinks set - assert(self.screen.cursor.hyperlink_id == 0); + assert(new.cursor.hyperlink_id == 0); // Bring our charset state with us - self.screen.charset = old.charset; + new.charset = old.charset; // Clear our selection - self.screen.clearSelection(); + new.clearSelection(); 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; + new.kitty_images.dirty = true; } // Mark our terminal as dirty to redraw the grid. self.flags.dirty.clear = true; - return &self.secondary_screen; + // Finalize the switch + self.screens.switchTo(key); + self.screen = new; + + return old; } /// Switch screen via a mode switch (e.g. mode 47, 1047, 1049). @@ -2653,7 +2668,7 @@ pub fn switchScreenMode( self: *Terminal, mode: SwitchScreenMode, enabled: bool, -) void { +) !void { // The behavior in this function is completely based on reading // the xterm source, specifically "charproc.c" for // `srm_ALTBUF`, `srm_OPT_ALTBUF`, and `srm_OPT_ALTBUF_CURSOR`. @@ -2665,7 +2680,7 @@ pub fn switchScreenMode( // If we're disabling 1047 and we're on alt screen then // we clear the screen. - .@"1047" => if (!enabled and self.active_screen == .alternate) { + .@"1047" => if (!enabled and self.screens.active_key == .alternate) { self.eraseDisplay(.complete, false); }, @@ -2675,8 +2690,8 @@ pub fn switchScreenMode( } // Switch screens first to whatever we're going to. - const to: ScreenType = if (enabled) .alternate else .primary; - const old_ = self.switchScreen(to); + const to: ScreenSet.Key = if (enabled) .alternate else .primary; + const old_ = try self.switchScreen(to); switch (mode) { // For these modes, we need to copy the cursor. We only copy @@ -2697,7 +2712,7 @@ pub fn switchScreenMode( // Mode 1049 restores cursor on the primary screen when // we disable it. .@"1049" => if (enabled) { - assert(self.active_screen == .alternate); + assert(self.screens.active_key == .alternate); self.eraseDisplay(.complete, false); // When we enter alt screen with 1049, we always copy the @@ -2714,7 +2729,7 @@ pub fn switchScreenMode( }; } } else { - assert(self.active_screen == .primary); + assert(self.screens.active_key == .primary); self.restoreCursor() catch |err| { log.warn( "restore cursor on switch screen failed to={} err={}", @@ -2765,17 +2780,16 @@ pub fn plainStringUnwrapped(self: *Terminal, alloc: Allocator) ![]const u8 { /// this will reuse the existing memory. In the latter case, memory may /// be wasted (since its unused) but it isn't leaked. pub fn fullReset(self: *Terminal) void { - // Reset our screens - self.screen.reset(); - self.secondary_screen.reset(); - // Ensure we're back on primary screen - if (self.active_screen != .primary) { - const old = self.screen; - self.screen = self.secondary_screen; - self.secondary_screen = old; - self.active_screen = .primary; - } + self.screens.switchTo(.primary); + self.screens.remove( + self.screens.active.alloc, + .alternate, + ); + self.screen = self.screens.active; + + // Reset our screens + self.screens.active.reset(); // Rest our basic state self.modes.reset(); @@ -10757,7 +10771,7 @@ test "Terminal: cursorIsAtPrompt alternate screen" { try testing.expect(t.cursorIsAtPrompt()); // Secondary screen is never a prompt - t.switchScreenMode(.@"1049", true); + try t.switchScreenMode(.@"1049", true); try testing.expect(!t.cursorIsAtPrompt()); t.markSemanticPrompt(.prompt); try testing.expect(!t.cursorIsAtPrompt()); @@ -10841,7 +10855,7 @@ test "Terminal: fullReset clears alt screen kitty keyboard state" { var t = try init(testing.allocator, .{ .cols = 10, .rows = 10 }); defer t.deinit(testing.allocator); - t.switchScreenMode(.@"1049", true); + try t.switchScreenMode(.@"1049", true); t.screen.kitty_keyboard.push(.{ .disambiguate = true, .report_events = false, @@ -10849,10 +10863,10 @@ test "Terminal: fullReset clears alt screen kitty keyboard state" { .report_all = true, .report_associated = true, }); - t.switchScreenMode(.@"1049", false); + try t.switchScreenMode(.@"1049", false); t.fullReset(); - try testing.expectEqual(0, t.secondary_screen.kitty_keyboard.current().int()); + try testing.expect(t.screens.get(.alternate) == null); } test "Terminal: fullReset default modes" { @@ -11164,8 +11178,8 @@ test "Terminal: mode 47 alt screen plain" { try t.printString("1A"); // Go to alt screen with mode 47 - t.switchScreenMode(.@"47", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"47", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should be empty { @@ -11184,8 +11198,8 @@ test "Terminal: mode 47 alt screen plain" { } // Go back to primary - t.switchScreenMode(.@"47", false); - try testing.expectEqual(ScreenType.primary, t.active_screen); + try t.switchScreenMode(.@"47", false); + try testing.expectEqual(.primary, t.screens.active_key); // Primary screen should still have the original content { @@ -11195,8 +11209,8 @@ test "Terminal: mode 47 alt screen plain" { } // Go back to alt screen with mode 47 - t.switchScreenMode(.@"47", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"47", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should retain content { @@ -11215,8 +11229,8 @@ test "Terminal: mode 47 copies cursor both directions" { try t.setAttribute(.{ .direct_color_fg = .{ .r = 0xFF, .g = 0, .b = 0x7F } }); // Go to alt screen with mode 47 - t.switchScreenMode(.@"47", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"47", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Verify that our style is set { @@ -11230,8 +11244,8 @@ test "Terminal: mode 47 copies cursor both directions" { try t.setAttribute(.{ .direct_color_fg = .{ .r = 0, .g = 0xFF, .b = 0 } }); // Go back to primary - t.switchScreenMode(.@"47", false); - try testing.expectEqual(ScreenType.primary, t.active_screen); + try t.switchScreenMode(.@"47", false); + try testing.expectEqual(.primary, t.screens.active_key); // Verify that our style is still set { @@ -11251,8 +11265,8 @@ test "Terminal: mode 1047 alt screen plain" { try t.printString("1A"); // Go to alt screen with mode 47 - t.switchScreenMode(.@"1047", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"1047", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should be empty { @@ -11271,8 +11285,8 @@ test "Terminal: mode 1047 alt screen plain" { } // Go back to primary - t.switchScreenMode(.@"1047", false); - try testing.expectEqual(ScreenType.primary, t.active_screen); + try t.switchScreenMode(.@"1047", false); + try testing.expectEqual(.primary, t.screens.active_key); // Primary screen should still have the original content { @@ -11282,8 +11296,8 @@ test "Terminal: mode 1047 alt screen plain" { } // Go back to alt screen with mode 1047 - t.switchScreenMode(.@"1047", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"1047", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should be empty { @@ -11302,8 +11316,8 @@ test "Terminal: mode 1047 copies cursor both directions" { try t.setAttribute(.{ .direct_color_fg = .{ .r = 0xFF, .g = 0, .b = 0x7F } }); // Go to alt screen with mode 47 - t.switchScreenMode(.@"1047", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"1047", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Verify that our style is set { @@ -11317,8 +11331,8 @@ test "Terminal: mode 1047 copies cursor both directions" { try t.setAttribute(.{ .direct_color_fg = .{ .r = 0, .g = 0xFF, .b = 0 } }); // Go back to primary - t.switchScreenMode(.@"1047", false); - try testing.expectEqual(ScreenType.primary, t.active_screen); + try t.switchScreenMode(.@"1047", false); + try testing.expectEqual(.primary, t.screens.active_key); // Verify that our style is still set { @@ -11338,8 +11352,8 @@ test "Terminal: mode 1049 alt screen plain" { try t.printString("1A"); // Go to alt screen with mode 47 - t.switchScreenMode(.@"1049", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"1049", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should be empty { @@ -11358,8 +11372,8 @@ test "Terminal: mode 1049 alt screen plain" { } // Go back to primary - t.switchScreenMode(.@"1049", false); - try testing.expectEqual(ScreenType.primary, t.active_screen); + try t.switchScreenMode(.@"1049", false); + try testing.expectEqual(.primary, t.screens.active_key); // Primary screen should still have the original content { @@ -11377,8 +11391,8 @@ test "Terminal: mode 1049 alt screen plain" { } // Go back to alt screen with mode 1049 - t.switchScreenMode(.@"1049", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"1049", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should be empty { diff --git a/src/terminal/formatter.zig b/src/terminal/formatter.zig index 6683b3453..0a742ccb1 100644 --- a/src/terminal/formatter.zig +++ b/src/terminal/formatter.zig @@ -331,7 +331,7 @@ pub const TerminalFormatter = struct { } } - var screen_formatter: ScreenFormatter = .init(&self.terminal.screen, self.opts); + var screen_formatter: ScreenFormatter = .init(self.terminal.screen, self.opts); screen_formatter.content = self.content; screen_formatter.extra = self.extra.screen; screen_formatter.pin_map = self.pin_map; @@ -4231,7 +4231,7 @@ test "Screen plain single line" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .plain); + var formatter: ScreenFormatter = .init(t.screen, .plain); formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; try formatter.format(&builder.writer); @@ -4268,7 +4268,7 @@ test "Screen plain multiline" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .plain); + var formatter: ScreenFormatter = .init(t.screen, .plain); formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; try formatter.format(&builder.writer); @@ -4316,7 +4316,7 @@ test "Screen plain with selection" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .plain); + var formatter: ScreenFormatter = .init(t.screen, .plain); formatter.content = .{ .selection = .init( t.screen.pages.pin(.{ .active = .{ .x = 0, .y = 1 } }).?, t.screen.pages.pin(.{ .active = .{ .x = 4, .y = 1 } }).?, @@ -4361,7 +4361,7 @@ test "Screen vt with cursor position" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.cursor = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; @@ -4420,7 +4420,7 @@ test "Screen vt with style" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.style = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; @@ -4472,7 +4472,7 @@ test "Screen vt with hyperlink" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.hyperlink = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; @@ -4532,7 +4532,7 @@ test "Screen vt with protection" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.protection = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; @@ -4584,7 +4584,7 @@ test "Screen vt with kitty keyboard" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.kitty_keyboard = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; @@ -4638,7 +4638,7 @@ test "Screen vt with charsets" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.charsets = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; diff --git a/src/terminal/kitty/graphics_exec.zig b/src/terminal/kitty/graphics_exec.zig index f917c104a..9cf0bda0c 100644 --- a/src/terminal/kitty/graphics_exec.zig +++ b/src/terminal/kitty/graphics_exec.zig @@ -252,7 +252,7 @@ fn display( result.placement_id, p, ) catch |err| { - p.deinit(&terminal.screen); + p.deinit(terminal.screen); encodeError(&result, err); return result; }; diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig index 8aef0ece5..34b9a6e85 100644 --- a/src/terminal/kitty/graphics_storage.zig +++ b/src/terminal/kitty/graphics_storage.zig @@ -232,7 +232,7 @@ pub const ImageStorage = struct { // Deinit the placement and remove it const image_id = entry.key_ptr.image_id; - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (delete_images) self.deleteIfUnused(alloc, image_id); } @@ -247,7 +247,7 @@ pub const ImageStorage = struct { .id => |v| self.deleteById( alloc, - &t.screen, + t.screen, v.image_id, v.placement_id, v.delete, @@ -257,7 +257,7 @@ pub const ImageStorage = struct { const img = self.imageByNumber(v.image_number) orelse break :newest; self.deleteById( alloc, - &t.screen, + t.screen, img.id, v.placement_id, v.delete, @@ -332,7 +332,7 @@ pub const ImageStorage = struct { const img = self.imageById(entry.key_ptr.image_id) orelse continue; const rect = entry.value_ptr.rect(img, t) orelse continue; if (rect.top_left.x <= x and rect.bottom_right.x >= x) { - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (v.delete) self.deleteIfUnused(alloc, img.id); } @@ -364,7 +364,7 @@ pub const ImageStorage = struct { var target_pin_copy = target_pin; target_pin_copy.x = rect.top_left.x; if (target_pin_copy.isBetween(rect.top_left, rect.bottom_right)) { - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (v.delete) self.deleteIfUnused(alloc, img.id); } @@ -387,7 +387,7 @@ pub const ImageStorage = struct { if (entry.value_ptr.z == v.z) { const image_id = entry.key_ptr.image_id; - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (v.delete) self.deleteIfUnused(alloc, image_id); } @@ -411,7 +411,7 @@ pub const ImageStorage = struct { while (it.next()) |entry| { if (entry.key_ptr.image_id >= v.first or entry.key_ptr.image_id <= v.last) { const image_id = entry.key_ptr.image_id; - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (v.delete) self.deleteIfUnused(alloc, image_id); } @@ -498,7 +498,7 @@ pub const ImageStorage = struct { const rect = entry.value_ptr.rect(img, t) orelse continue; if (target_pin.isBetween(rect.top_left, rect.bottom_right)) { if (filter) |f| if (!f(filter_ctx, entry.value_ptr.*)) continue; - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (delete_unused) self.deleteIfUnused(alloc, img.id); } @@ -825,7 +825,7 @@ test "storage: add placement with zero placement id" { t.height_px = 100; var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 0, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); @@ -853,7 +853,7 @@ test "storage: delete all placements and images" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -876,7 +876,7 @@ test "storage: delete all placements and images preserves limit" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); s.total_limit = 5000; try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); @@ -901,7 +901,7 @@ test "storage: delete all placements" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -924,7 +924,7 @@ test "storage: delete all placements by image id" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -947,7 +947,7 @@ test "storage: delete all placements by image id and unused images" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -970,7 +970,7 @@ test "storage: delete placement by specific id" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -1000,7 +1000,7 @@ test "storage: delete intersecting cursor" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); @@ -1032,7 +1032,7 @@ test "storage: delete intersecting cursor plus unused" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); @@ -1064,7 +1064,7 @@ test "storage: delete intersecting cursor hits multiple" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); @@ -1090,7 +1090,7 @@ test "storage: delete by column" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); @@ -1122,7 +1122,7 @@ test "storage: delete by column 1x1" { t.height_px = 100; var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 1, .height = 1 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 0 }) } }); @@ -1156,7 +1156,7 @@ test "storage: delete by row" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); @@ -1188,7 +1188,7 @@ test "storage: delete by row 1x1" { t.height_px = 100; var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 1, .height = 1 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 0 }) } }); try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 1 }) } }); @@ -1220,7 +1220,7 @@ test "storage: delete images by range 1" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -1245,7 +1245,7 @@ test "storage: delete images by range 2" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -1270,7 +1270,7 @@ test "storage: delete images by range 3" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -1295,7 +1295,7 @@ test "storage: delete images by range 4" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index a4a25e751..491c3e110 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -1180,7 +1180,7 @@ test "unicode render placement: dog 4x2" { var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 }); defer t.deinit(alloc); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); const image: Image = .{ .id = 1, .width = 500, .height = 306 }; try s.addImage(alloc, image); @@ -1247,7 +1247,7 @@ test "unicode render placement: dog 2x2 with blank cells" { var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 }); defer t.deinit(alloc); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); const image: Image = .{ .id = 1, .width = 500, .height = 306 }; try s.addImage(alloc, image); @@ -1313,7 +1313,7 @@ test "unicode render placement: dog 1x1" { var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 }); defer t.deinit(alloc); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); const image: Image = .{ .id = 1, .width = 500, .height = 306 }; try s.addImage(alloc, image); diff --git a/src/terminal/main.zig b/src/terminal/main.zig index bdcbfe77f..3f67b78b3 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -41,7 +41,7 @@ pub const Point = point.Point; pub const ReadonlyHandler = stream_readonly.Handler; pub const ReadonlyStream = stream_readonly.Stream; pub const Screen = @import("Screen.zig"); -pub const ScreenType = Terminal.ScreenType; +pub const ScreenSet = @import("ScreenSet.zig"); pub const Scrollbar = PageList.Scrollbar; pub const Selection = @import("Selection.zig"); pub const SizeReportStyle = csi.SizeReportStyle; diff --git a/src/terminal/search/screen.zig b/src/terminal/search/screen.zig index 0ffeb76c4..674f08b8c 100644 --- a/src/terminal/search/screen.zig +++ b/src/terminal/search/screen.zig @@ -391,7 +391,7 @@ test "simple search" { defer s.deinit(); try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang"); - var search: ScreenSearch = try .init(alloc, &t.screen, "Fizz"); + var search: ScreenSearch = try .init(alloc, t.screen, "Fizz"); defer search.deinit(); try search.searchAll(); try testing.expectEqual(2, search.active_results.items.len); @@ -444,7 +444,7 @@ test "simple search with history" { for (0..list.rows) |_| try s.nextSlice("\r\n"); try s.nextSlice("hello."); - var search: ScreenSearch = try .init(alloc, &t.screen, "Fizz"); + var search: ScreenSearch = try .init(alloc, t.screen, "Fizz"); defer search.deinit(); try search.searchAll(); try testing.expectEqual(0, search.active_results.items.len); @@ -482,7 +482,7 @@ test "reload active with history change" { try s.nextSlice("Fizz\r\n"); // Start up our search which will populate our initial active area. - var search: ScreenSearch = try .init(alloc, &t.screen, "Fizz"); + var search: ScreenSearch = try .init(alloc, t.screen, "Fizz"); defer search.deinit(); try search.searchAll(); { @@ -562,7 +562,7 @@ test "active change contents" { defer s.deinit(); try s.nextSlice("Fuzz\r\nBuzz\r\nFizz\r\nBang"); - var search: ScreenSearch = try .init(alloc, &t.screen, "Fizz"); + var search: ScreenSearch = try .init(alloc, t.screen, "Fizz"); defer search.deinit(); try search.searchAll(); try testing.expectEqual(1, search.active_results.items.len); diff --git a/src/terminal/stream_readonly.zig b/src/terminal/stream_readonly.zig index 907c48762..d34e4c84c 100644 --- a/src/terminal/stream_readonly.zig +++ b/src/terminal/stream_readonly.zig @@ -233,9 +233,9 @@ pub const Handler = struct { self.terminal.scrolling_region.right = self.terminal.cols - 1; }, - .alt_screen_legacy => self.terminal.switchScreenMode(.@"47", enabled), - .alt_screen => self.terminal.switchScreenMode(.@"1047", enabled), - .alt_screen_save_cursor_clear_enter => self.terminal.switchScreenMode(.@"1049", enabled), + .alt_screen_legacy => try self.terminal.switchScreenMode(.@"47", enabled), + .alt_screen => try self.terminal.switchScreenMode(.@"1047", enabled), + .alt_screen_save_cursor_clear_enter => try self.terminal.switchScreenMode(.@"1049", enabled), .save_cursor => if (enabled) { self.terminal.saveCursor(); @@ -527,18 +527,18 @@ test "alt screen" { // Write to primary screen try s.nextSlice("Primary"); - try testing.expectEqual(Terminal.ScreenType.primary, t.active_screen); + try testing.expectEqual(.primary, t.screens.active_key); // Switch to alt screen try s.nextSlice("\x1B[?1049h"); - try testing.expectEqual(Terminal.ScreenType.alternate, t.active_screen); + try testing.expectEqual(.alternate, t.screens.active_key); // Write to alt screen try s.nextSlice("Alt"); // Switch back to primary try s.nextSlice("\x1B[?1049l"); - try testing.expectEqual(Terminal.ScreenType.primary, t.active_screen); + try testing.expectEqual(.primary, t.screens.active_key); const str = try t.plainString(testing.allocator); defer testing.allocator.free(str); diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index 1e181a137..6371722b5 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -246,16 +246,15 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { errdefer term.deinit(alloc); // Set the image size limits - try term.screen.kitty_images.setLimit( - alloc, - &term.screen, - opts.config.image_storage_limit, - ); - try term.secondary_screen.kitty_images.setLimit( - alloc, - &term.secondary_screen, - opts.config.image_storage_limit, - ); + var it = term.screens.all.iterator(); + while (it.next()) |entry| { + const screen: *terminalpkg.Screen = entry.value.*; + try screen.kitty_images.setLimit( + alloc, + screen, + opts.config.image_storage_limit, + ); + } // Set our default cursor style term.screen.cursor.cursor_style = opts.config.cursor_style; @@ -451,16 +450,15 @@ pub fn changeConfig(self: *Termio, td: *ThreadData, config: *DerivedConfig) !voi }; // Set the image size limits - try self.terminal.screen.kitty_images.setLimit( - self.alloc, - &self.terminal.screen, - config.image_storage_limit, - ); - try self.terminal.secondary_screen.kitty_images.setLimit( - self.alloc, - &self.terminal.secondary_screen, - config.image_storage_limit, - ); + var it = self.terminal.screens.all.iterator(); + while (it.next()) |entry| { + const screen: *terminalpkg.Screen = entry.value.*; + try screen.kitty_images.setLimit( + self.alloc, + screen, + config.image_storage_limit, + ); + } } /// Resize the terminal. @@ -578,7 +576,7 @@ pub fn clearScreen(self: *Termio, td: *ThreadData, history: bool) !void { // emulator-level screen clear, this messes up the running programs // knowledge of where the cursor is and causes rendering issues. So, // for alt screen, we do nothing. - if (self.terminal.active_screen == .alternate) return; + if (self.terminal.screens.active_key == .alternate) return; // Clear our selection self.terminal.screen.clearSelection(); diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 0131ff2e1..4b40ff3cf 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -583,15 +583,15 @@ pub const StreamHandler = struct { }, .alt_screen_legacy => { - self.terminal.switchScreenMode(.@"47", enabled); + try self.terminal.switchScreenMode(.@"47", enabled); }, .alt_screen => { - self.terminal.switchScreenMode(.@"1047", enabled); + try self.terminal.switchScreenMode(.@"1047", enabled); }, .alt_screen_save_cursor_clear_enter => { - self.terminal.switchScreenMode(.@"1049", enabled); + try self.terminal.switchScreenMode(.@"1049", enabled); }, // Mode 1048 is xterm's conditional save cursor depending