ScreenSet

pull/9594/head
Mitchell Hashimoto 2025-11-14 13:19:16 -08:00
parent 368f4f565a
commit 3aff5f0aff
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
17 changed files with 357 additions and 219 deletions

View File

@ -186,7 +186,7 @@ const Mouse = struct {
/// The point at which the left mouse click happened. This is in screen /// The point at which the left mouse click happened. This is in screen
/// coordinates so that scrolling preserves the location. /// coordinates so that scrolling preserves the location.
left_click_pin: ?*terminal.Pin = null, 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, /// 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 /// 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 // If our screen changed while this is happening, we stop our
// selection scroll. // selection scroll.
if (self.mouse.left_click_screen != t.active_screen) { if (self.mouse.left_click_screen != t.screens.active_key) {
self.io.queueMessage( self.io.queueMessage(
.{ .selection_scroll = false }, .{ .selection_scroll = false },
.locked, .locked,
@ -1703,7 +1703,7 @@ pub fn dumpTextLocked(
// If our bottom right pin is before the viewport, then we can't // If our bottom right pin is before the viewport, then we can't
// possibly have this text be within the viewport. // possibly have this text be within the viewport.
const vp_tl_pin = self.io.terminal.screen.pages.getTopLeft(.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 (br_pin.before(vp_tl_pin)) break :viewport null;
// If our top-left pin is after the viewport, then we can't possibly // 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?", .{}); log.warn("viewport bottom-right pin not found, bug?", .{});
break :viewport null; 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; if (vp_br_pin.before(tl_pin)) break :viewport null;
// We established that our top-left somewhere before the viewport // We established that our top-left somewhere before the viewport
@ -1984,7 +1984,7 @@ fn copySelectionToClipboards(
var contents: std.ArrayList(apprt.ClipboardContent) = .empty; var contents: std.ArrayList(apprt.ClipboardContent) = .empty;
switch (format) { switch (format) {
.plain => { .plain => {
var formatter: ScreenFormatter = .init(&self.io.terminal.screen, opts); var formatter: ScreenFormatter = .init(self.io.terminal.screen, opts);
formatter.content = .{ .selection = sel }; formatter.content = .{ .selection = sel };
try formatter.format(&aw.writer); try formatter.format(&aw.writer);
try contents.append(alloc, .{ try contents.append(alloc, .{
@ -1994,7 +1994,7 @@ fn copySelectionToClipboards(
}, },
.vt => { .vt => {
var formatter: ScreenFormatter = .init(&self.io.terminal.screen, opts: { var formatter: ScreenFormatter = .init(self.io.terminal.screen, opts: {
var copy = opts; var copy = opts;
copy.emit = .vt; copy.emit = .vt;
break :opts copy; break :opts copy;
@ -2011,7 +2011,7 @@ fn copySelectionToClipboards(
}, },
.html => { .html => {
var formatter: ScreenFormatter = .init(&self.io.terminal.screen, opts: { var formatter: ScreenFormatter = .init(self.io.terminal.screen, opts: {
var copy = opts; var copy = opts;
copy.emit = .html; copy.emit = .html;
break :opts copy; break :opts copy;
@ -2029,7 +2029,7 @@ fn copySelectionToClipboards(
.mixed => { .mixed => {
// First, generate plain text with codepoint mappings applied // 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 }; formatter.content = .{ .selection = sel };
try formatter.format(&aw.writer); try formatter.format(&aw.writer);
try contents.append(alloc, .{ try contents.append(alloc, .{
@ -2039,7 +2039,7 @@ fn copySelectionToClipboards(
assert(aw.written().len == 0); assert(aw.written().len == 0);
// Second, generate HTML without codepoint mappings // Second, generate HTML without codepoint mappings
formatter = .init(&self.io.terminal.screen, opts: { formatter = .init(self.io.terminal.screen, opts: {
var copy = opts; var copy = opts;
copy.emit = .html; copy.emit = .html;
@ -3098,7 +3098,7 @@ pub fn scrollCallback(
// we convert to cursor keys. This only happens if we're: // we convert to cursor keys. This only happens if we're:
// (1) alt screen (2) no explicit mouse reporting and (3) alt // (1) alt screen (2) no explicit mouse reporting and (3) alt
// scroll mode enabled. // 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.flags.mouse_event == .none and
self.io.terminal.modes.get(.mouse_alternate_scroll)) self.io.terminal.modes.get(.mouse_alternate_scroll))
{ {
@ -3506,7 +3506,7 @@ pub fn mouseButtonCallback(
{ {
const pos = try self.rt_surface.getCursorPos(); const pos = try self.rt_surface.getCursorPos();
const point = self.posToViewport(pos.x, pos.y); 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 { const p = screen.pages.pin(.{ .viewport = point }) orelse {
log.warn("failed to get pin for clicked point", .{}); log.warn("failed to get pin for clicked point", .{});
return false; return false;
@ -3681,7 +3681,7 @@ pub fn mouseButtonCallback(
self.renderer_state.mutex.lock(); self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock(); defer self.renderer_state.mutex.unlock();
const t: *terminal.Terminal = self.renderer_state.terminal; 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 pos = try self.rt_surface.getCursorPos();
const pin = pin: { const pin = pin: {
@ -3717,14 +3717,15 @@ pub fn mouseButtonCallback(
} }
if (self.mouse.left_click_pin) |prev| { if (self.mouse.left_click_pin) |prev| {
const pin_screen = t.getScreen(self.mouse.left_click_screen); if (t.screens.get(self.mouse.left_click_screen)) |pin_screen| {
pin_screen.pages.untrackPin(prev); pin_screen.pages.untrackPin(prev);
}
self.mouse.left_click_pin = null; self.mouse.left_click_pin = null;
} }
// Store it // Store it
self.mouse.left_click_pin = pin; 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_xpos = pos.x;
self.mouse.left_click_ypos = pos.y; self.mouse.left_click_ypos = pos.y;
@ -3808,7 +3809,7 @@ pub fn mouseButtonCallback(
defer self.renderer_state.mutex.unlock(); defer self.renderer_state.mutex.unlock();
// Get our viewport pin // Get our viewport pin
const screen = &self.renderer_state.terminal.screen; const screen: *terminal.Screen = self.renderer_state.terminal.screen;
const pin = pin: { const pin = pin: {
const pos = try self.rt_surface.getCursorPos(); const pos = try self.rt_surface.getCursorPos();
const pt_viewport = self.posToViewport(pos.x, pos.y); 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 // Click to move cursor only works on the primary screen where prompts
// exist. This means that alt screen multiplexers like tmux will not // exist. This means that alt screen multiplexers like tmux will not
// support this feature. It is just too messy. // 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 // 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 // OSC sequence. If we've never seen that sequence, we can't possibly
@ -3964,7 +3965,7 @@ fn linkAtPos(
terminal.Selection, terminal.Selection,
} { } {
// Convert our cursor position to a screen point. // 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 mouse_pin: terminal.Pin = mouse_pin: {
const point = self.posToViewport(pos.x, pos.y); const point = self.posToViewport(pos.x, pos.y);
const pin = screen.pages.pin(.{ .viewport = point }) orelse { const pin = screen.pages.pin(.{ .viewport = point }) orelse {
@ -4237,7 +4238,7 @@ pub fn cursorPosCallback(
insp.mouse.last_xpos = pos.x; insp.mouse.last_xpos = pos.x;
insp.mouse.last_ypos = pos.y; 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 = .{ insp.mouse.last_point = screen.pages.pin(.{ .viewport = .{
.x = pos_vp.x, .x = pos_vp.x,
.y = pos_vp.y, .y = pos_vp.y,
@ -4303,7 +4304,7 @@ pub fn cursorPosCallback(
// invalidate our pin or mouse state because if the screen switches // invalidate our pin or mouse state because if the screen switches
// back then we can continue our selection. // back then we can continue our selection.
const t: *terminal.Terminal = self.renderer_state.terminal; 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. // All roads lead to requiring a re-render at this point.
try self.queueRender(); try self.queueRender();
@ -4328,7 +4329,7 @@ pub fn cursorPosCallback(
} }
// Convert to points // Convert to points
const screen = &t.screen; const screen: *terminal.Screen = t.screen;
const pin = screen.pages.pin(.{ const pin = screen.pages.pin(.{
.viewport = .{ .viewport = .{
.x = pos_vp.x, .x = pos_vp.x,
@ -4357,7 +4358,7 @@ fn dragLeftClickDouble(
self: *Surface, self: *Surface,
drag_pin: terminal.Pin, drag_pin: terminal.Pin,
) !void { ) !void {
const screen = &self.io.terminal.screen; const screen: *terminal.Screen = self.io.terminal.screen;
const click_pin = self.mouse.left_click_pin.?.*; const click_pin = self.mouse.left_click_pin.?.*;
// Get the word closest to our starting click. // Get the word closest to our starting click.
@ -4397,7 +4398,7 @@ fn dragLeftClickTriple(
self: *Surface, self: *Surface,
drag_pin: terminal.Pin, drag_pin: terminal.Pin,
) !void { ) !void {
const screen = &self.io.terminal.screen; const screen: *terminal.Screen = self.io.terminal.screen;
const click_pin = self.mouse.left_click_pin.?.*; const click_pin = self.mouse.left_click_pin.?.*;
// Get the line selection under our current drag point. If there isn't a // 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(); self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock(); 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(.{ self.io.queueMessage(.{
@ -4966,7 +4967,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
self.renderer_state.mutex.lock(); self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock(); defer self.renderer_state.mutex.unlock();
const sel = self.io.terminal.screen.selection orelse return false; 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 }); 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(); self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock(); 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 { const sel = if (screen.selection) |*sel| sel else {
// If we don't have a selection we do not perform this // If we don't have a selection we do not perform this
// action, allowing the keybind to fall through to the // action, allowing the keybind to fall through to the
@ -5340,7 +5341,7 @@ fn writeScreenFile(
.history => history: { .history => history: {
// We do not support this for alternate screens // We do not support this for alternate screens
// because they don't have scrollback anyways. // 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; break :history null;
} }
@ -5371,7 +5372,7 @@ fn writeScreenFile(
}; };
const ScreenFormatter = terminal.formatter.ScreenFormatter; 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) { .emit = switch (write_screen.emit) {
.plain => .plain, .plain => .plain,
.vt => .vt, .vt => .vt,
@ -5384,7 +5385,7 @@ fn writeScreenFile(
.palette = &self.io.terminal.colors.palette.current, .palette = &self.io.terminal.colors.palette.current,
}); });
formatter.content = .{ .selection = sel.ordered( formatter.content = .{ .selection = sel.ordered(
&self.io.terminal.screen, self.io.terminal.screen,
.forward, .forward,
) }; ) };
try formatter.format(buf_writer); try formatter.format(buf_writer);

View File

@ -1578,7 +1578,7 @@ pub const CAPI = struct {
defer surface.core_surface.renderer_state.mutex.unlock(); defer surface.core_surface.renderer_state.mutex.unlock();
const core_sel = sel.core( const core_sel = sel.core(
&surface.core_surface.renderer_state.terminal.screen, surface.core_surface.renderer_state.terminal.screen,
) orelse return false; ) orelse return false;
return readTextLocked(surface, core_sel, result); return readTextLocked(surface, core_sel, result);
@ -2137,7 +2137,7 @@ pub const CAPI = struct {
// Get our word selection // Get our word selection
const sel = sel: { 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 pos = try ptr.getCursorPos();
const pt_viewport = surface.posToViewport(pos.x, pos.y); const pt_viewport = surface.posToViewport(pos.x, pos.y);
const pin = screen.pages.pin(.{ const pin = screen.pages.pin(.{

View File

@ -304,7 +304,7 @@ fn renderScreenWindow(self: *Inspector) void {
)) return; )) return;
const t = self.surface.renderer_state.terminal; const t = self.surface.renderer_state.terminal;
const screen = &t.screen; const screen: *terminal.Screen = t.screen;
{ {
_ = cimgui.c.igBeginTable( _ = cimgui.c.igBeginTable(
@ -324,7 +324,7 @@ fn renderScreenWindow(self: *Inspector) void {
} }
{ {
_ = cimgui.c.igTableSetColumnIndex(1); _ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%s", @tagName(t.active_screen).ptr); cimgui.c.igText("%s", @tagName(t.screens.active_key).ptr);
} }
} }
} }

View File

@ -1066,7 +1066,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
bg: terminal.color.RGB, bg: terminal.color.RGB,
fg: terminal.color.RGB, fg: terminal.color.RGB,
screen: terminal.Screen, screen: terminal.Screen,
screen_type: terminal.ScreenType, screen_type: terminal.ScreenSet.Key,
mouse: renderer.State.Mouse, mouse: renderer.State.Mouse,
preedit: ?renderer.State.Preedit, preedit: ?renderer.State.Preedit,
cursor_color: ?terminal.color.RGB, cursor_color: ?terminal.color.RGB,
@ -1207,7 +1207,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
.bg = bg, .bg = bg,
.fg = fg, .fg = fg,
.screen = screen_copy, .screen = screen_copy,
.screen_type = state.terminal.active_screen, .screen_type = state.terminal.screens.active_key,
.mouse = state.mouse, .mouse = state.mouse,
.preedit = preedit, .preedit = preedit,
.cursor_color = state.terminal.colors.cursor.get(), .cursor_color = state.terminal.colors.cursor.get(),
@ -2317,7 +2317,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
self: *Self, self: *Self,
wants_rebuild: bool, wants_rebuild: bool,
screen: *terminal.Screen, screen: *terminal.Screen,
screen_type: terminal.ScreenType, screen_type: terminal.ScreenSet.Key,
mouse: renderer.State.Mouse, mouse: renderer.State.Mouse,
preedit: ?renderer.State.Preedit, preedit: ?renderer.State.Preedit,
cursor_style_: ?renderer.CursorStyle, cursor_style_: ?renderer.CursorStyle,

View File

@ -609,7 +609,7 @@ test "matchset osc8" {
// Initialize our terminal // Initialize our terminal
var t = try Terminal.init(alloc, .{ .cols = 10, .rows = 10 }); var t = try Terminal.init(alloc, .{ .cols = 10, .rows = 10 });
defer t.deinit(alloc); defer t.deinit(alloc);
const s = &t.screen; const s: *terminal.Screen = t.screen;
try t.printString("ABC"); try t.printString("ABC");
try t.screen.startHyperlink("http://example.com", null); try t.screen.startHyperlink("http://example.com", null);
@ -624,7 +624,7 @@ test "matchset osc8" {
{ {
var match = try set.matchSet( var match = try set.matchSet(
alloc, alloc,
&t.screen, t.screen,
.{ .x = 2, .y = 0 }, .{ .x = 2, .y = 0 },
inputpkg.ctrlOrSuper(.{}), inputpkg.ctrlOrSuper(.{}),
); );
@ -635,7 +635,7 @@ test "matchset osc8" {
// Match over link // Match over link
var match = try set.matchSet( var match = try set.matchSet(
alloc, alloc,
&t.screen, t.screen,
.{ .x = 3, .y = 0 }, .{ .x = 3, .y = 0 },
inputpkg.ctrlOrSuper(.{}), inputpkg.ctrlOrSuper(.{}),
); );

View File

@ -178,8 +178,15 @@ pub const CharsetState = struct {
pub const Options = struct { pub const Options = struct {
cols: size.CellCountInt, cols: size.CellCountInt,
rows: 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, 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 /// A simple, default terminal. If you rely on specific dimensions or
/// scrollback (or lack of) then do not use this directly. This is just /// scrollback (or lack of) then do not use this directly. This is just
/// for callers that need some defaults. /// for callers that need some defaults.
@ -215,7 +222,7 @@ pub fn init(
errdefer pages.untrackPin(page_pin); errdefer pages.untrackPin(page_pin);
const page_rac = page_pin.rowAndCell(); const page_rac = page_pin.rowAndCell();
return .{ var result: Screen = .{
.alloc = alloc, .alloc = alloc,
.pages = pages, .pages = pages,
.no_scrollback = opts.max_scrollback == 0, .no_scrollback = opts.max_scrollback == 0,
@ -227,6 +234,18 @@ pub fn init(
.page_cell = page_rac.cell, .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 { pub fn deinit(self: *Screen) void {

106
src/terminal/ScreenSet.zig Normal file
View File

@ -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);
}

View File

@ -29,6 +29,7 @@ const size = @import("size.zig");
const pagepkg = @import("page.zig"); const pagepkg = @import("page.zig");
const style = @import("style.zig"); const style = @import("style.zig");
const Screen = @import("Screen.zig"); const Screen = @import("Screen.zig");
const ScreenSet = @import("ScreenSet.zig");
const Page = pagepkg.Page; const Page = pagepkg.Page;
const Cell = pagepkg.Cell; const Cell = pagepkg.Cell;
const Row = pagepkg.Row; const Row = pagepkg.Row;
@ -38,18 +39,17 @@ const log = std.log.scoped(.terminal);
/// Default tabstop interval /// Default tabstop interval
const TABSTOP_INTERVAL = 8; const TABSTOP_INTERVAL = 8;
/// Screen type is an enum that tracks whether a screen is primary or alternate. /// The currently active screen. To get the type of screen this is,
pub const ScreenType = enum(u1) { /// inspect screens.active_key instead.
primary, ///
alternate, /// 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 set of screens behind this terminal (e.g. primary vs alternate).
/// the current screen is. The backup screen is the opposite of the active screens: ScreenSet,
/// screen.
active_screen: ScreenType,
screen: Screen,
secondary_screen: Screen,
/// Whether we're currently writing to the status line (DECSASD and DECSSDT). /// 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 /// We don't support a status line currently so we just black hole this
@ -221,12 +221,19 @@ pub fn init(
) !Terminal { ) !Terminal {
const cols = opts.cols; const cols = opts.cols;
const rows = opts.rows; 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 .{ return .{
.cols = cols, .cols = cols,
.rows = rows, .rows = rows,
.active_screen = .primary, .screen = screen_set.active,
.screen = try .init(alloc, .{ .cols = cols, .rows = rows, .max_scrollback = opts.max_scrollback }), .screens = screen_set,
.secondary_screen = try .init(alloc, .{ .cols = cols, .rows = rows, .max_scrollback = 0 }),
.tabstops = try .init(alloc, cols, TABSTOP_INTERVAL), .tabstops = try .init(alloc, cols, TABSTOP_INTERVAL),
.scrolling_region = .{ .scrolling_region = .{
.top = 0, .top = 0,
@ -245,8 +252,7 @@ pub fn init(
pub fn deinit(self: *Terminal, alloc: Allocator) void { pub fn deinit(self: *Terminal, alloc: Allocator) void {
self.tabstops.deinit(alloc); self.tabstops.deinit(alloc);
self.screen.deinit(); self.screens.deinit(alloc);
self.secondary_screen.deinit();
self.pwd.deinit(alloc); self.pwd.deinit(alloc);
self.* = undefined; self.* = undefined;
} }
@ -266,7 +272,7 @@ pub fn vtHandler(self: *Terminal) ReadonlyHandler {
/// The general allocator we should use for this terminal. /// The general allocator we should use for this terminal.
fn gpa(self: *Terminal) Allocator { fn gpa(self: *Terminal) Allocator {
return self.screen.alloc; return self.screens.active.alloc;
} }
/// Print UTF-8 encoded string to the terminal. /// 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. /// If the shell integration doesn't exist, this will always return false.
pub fn cursorIsAtPrompt(self: *Terminal) bool { pub fn cursorIsAtPrompt(self: *Terminal) bool {
// If we're on the secondary screen, we're never at a prompt. // 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 // Reverse through the active
const start_x, const start_y = .{ self.screen.cursor.x, self.screen.cursor.y }; 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. // at a prompt scrolls the screen contents prior to clearing.
// Most shells send `ESC [ H ESC [ 2 J` so we can't just check // Most shells send `ESC [ H ESC [ 2 J` so we can't just check
// our current cursor position. See #905 // 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 // Go from the bottom of the active up and see if we're
// at a prompt. // at a prompt.
const active_br = self.screen.pages.getBottomRight( const active_br = self.screen.pages.getBottomRight(
@ -2531,25 +2537,22 @@ pub fn resize(
self.tabstops = try .init(alloc, cols, 8); self.tabstops = try .init(alloc, cols, 8);
} }
// If we're making the screen smaller, dealloc the unused items. // Resize primary screen, which supports reflow
if (self.active_screen == .primary) { const primary = self.screens.get(.primary).?;
if (self.flags.shell_redraws_prompt) { if (self.screens.active_key == .primary and
self.screen.clearPrompt(); self.flags.shell_redraws_prompt)
} {
primary.clearPrompt();
if (self.modes.get(.wraparound)) { }
try self.screen.resize(cols, rows); if (self.modes.get(.wraparound)) {
} else { try primary.resize(cols, rows);
try self.screen.resizeWithoutReflow(cols, rows);
}
try self.secondary_screen.resizeWithoutReflow(cols, rows);
} else { } else {
try self.screen.resizeWithoutReflow(cols, rows); try primary.resizeWithoutReflow(cols, rows);
if (self.modes.get(.wraparound)) { }
try self.secondary_screen.resize(cols, rows);
} else { // Alternate screen, if it exists, doesn't reflow
try self.secondary_screen.resizeWithoutReflow(cols, rows); if (self.screens.get(.alternate)) |alt| {
} try alt.resizeWithoutReflow(cols, rows);
} }
// Whenever we resize we just mark it as a screen clear // 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; 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). /// Switch to the given screen type (alternate or primary).
/// ///
/// This does NOT handle behaviors such as clearing the screen, /// 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 /// more than two screens in the future if needed. There isn't
/// currently a spec for this, but it is something I think might /// currently a spec for this, but it is something I think might
/// be useful in the future. /// 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 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 always end hyperlink state when switching screens.
// We need to do this on the original screen. // We need to do this on the original screen.
self.screen.endHyperlink(); old.endHyperlink();
// Switch the screens // Switch the screens/
const old = self.screen; const new = self.screens.get(key) orelse new: {
self.screen = self.secondary_screen; const primary = self.screens.get(.primary).?;
self.secondary_screen = old; break :new try self.screens.getInit(
self.active_screen = t; 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 // 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 // Bring our charset state with us
self.screen.charset = old.charset; new.charset = old.charset;
// Clear our selection // Clear our selection
self.screen.clearSelection(); new.clearSelection();
if (comptime build_options.kitty_graphics) { if (comptime build_options.kitty_graphics) {
// Mark kitty images as dirty so they redraw. Without this set // Mark kitty images as dirty so they redraw. Without this set
// the images will remain where they were (the dirty bit on // the images will remain where they were (the dirty bit on
// the screen only tracks the terminal grid, not the images). // 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. // Mark our terminal as dirty to redraw the grid.
self.flags.dirty.clear = true; 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). /// Switch screen via a mode switch (e.g. mode 47, 1047, 1049).
@ -2653,7 +2668,7 @@ pub fn switchScreenMode(
self: *Terminal, self: *Terminal,
mode: SwitchScreenMode, mode: SwitchScreenMode,
enabled: bool, enabled: bool,
) void { ) !void {
// The behavior in this function is completely based on reading // The behavior in this function is completely based on reading
// the xterm source, specifically "charproc.c" for // the xterm source, specifically "charproc.c" for
// `srm_ALTBUF`, `srm_OPT_ALTBUF`, and `srm_OPT_ALTBUF_CURSOR`. // `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 // If we're disabling 1047 and we're on alt screen then
// we clear the screen. // 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); self.eraseDisplay(.complete, false);
}, },
@ -2675,8 +2690,8 @@ pub fn switchScreenMode(
} }
// Switch screens first to whatever we're going to. // Switch screens first to whatever we're going to.
const to: ScreenType = if (enabled) .alternate else .primary; const to: ScreenSet.Key = if (enabled) .alternate else .primary;
const old_ = self.switchScreen(to); const old_ = try self.switchScreen(to);
switch (mode) { switch (mode) {
// For these modes, we need to copy the cursor. We only copy // 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 // Mode 1049 restores cursor on the primary screen when
// we disable it. // we disable it.
.@"1049" => if (enabled) { .@"1049" => if (enabled) {
assert(self.active_screen == .alternate); assert(self.screens.active_key == .alternate);
self.eraseDisplay(.complete, false); self.eraseDisplay(.complete, false);
// When we enter alt screen with 1049, we always copy the // When we enter alt screen with 1049, we always copy the
@ -2714,7 +2729,7 @@ pub fn switchScreenMode(
}; };
} }
} else { } else {
assert(self.active_screen == .primary); assert(self.screens.active_key == .primary);
self.restoreCursor() catch |err| { self.restoreCursor() catch |err| {
log.warn( log.warn(
"restore cursor on switch screen failed to={} err={}", "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 /// this will reuse the existing memory. In the latter case, memory may
/// be wasted (since its unused) but it isn't leaked. /// be wasted (since its unused) but it isn't leaked.
pub fn fullReset(self: *Terminal) void { pub fn fullReset(self: *Terminal) void {
// Reset our screens
self.screen.reset();
self.secondary_screen.reset();
// Ensure we're back on primary screen // Ensure we're back on primary screen
if (self.active_screen != .primary) { self.screens.switchTo(.primary);
const old = self.screen; self.screens.remove(
self.screen = self.secondary_screen; self.screens.active.alloc,
self.secondary_screen = old; .alternate,
self.active_screen = .primary; );
} self.screen = self.screens.active;
// Reset our screens
self.screens.active.reset();
// Rest our basic state // Rest our basic state
self.modes.reset(); self.modes.reset();
@ -10757,7 +10771,7 @@ test "Terminal: cursorIsAtPrompt alternate screen" {
try testing.expect(t.cursorIsAtPrompt()); try testing.expect(t.cursorIsAtPrompt());
// Secondary screen is never a prompt // Secondary screen is never a prompt
t.switchScreenMode(.@"1049", true); try t.switchScreenMode(.@"1049", true);
try testing.expect(!t.cursorIsAtPrompt()); try testing.expect(!t.cursorIsAtPrompt());
t.markSemanticPrompt(.prompt); t.markSemanticPrompt(.prompt);
try testing.expect(!t.cursorIsAtPrompt()); 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 }); var t = try init(testing.allocator, .{ .cols = 10, .rows = 10 });
defer t.deinit(testing.allocator); defer t.deinit(testing.allocator);
t.switchScreenMode(.@"1049", true); try t.switchScreenMode(.@"1049", true);
t.screen.kitty_keyboard.push(.{ t.screen.kitty_keyboard.push(.{
.disambiguate = true, .disambiguate = true,
.report_events = false, .report_events = false,
@ -10849,10 +10863,10 @@ test "Terminal: fullReset clears alt screen kitty keyboard state" {
.report_all = true, .report_all = true,
.report_associated = true, .report_associated = true,
}); });
t.switchScreenMode(.@"1049", false); try t.switchScreenMode(.@"1049", false);
t.fullReset(); 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" { test "Terminal: fullReset default modes" {
@ -11164,8 +11178,8 @@ test "Terminal: mode 47 alt screen plain" {
try t.printString("1A"); try t.printString("1A");
// Go to alt screen with mode 47 // Go to alt screen with mode 47
t.switchScreenMode(.@"47", true); try t.switchScreenMode(.@"47", true);
try testing.expectEqual(ScreenType.alternate, t.active_screen); try testing.expectEqual(.alternate, t.screens.active_key);
// Screen should be empty // Screen should be empty
{ {
@ -11184,8 +11198,8 @@ test "Terminal: mode 47 alt screen plain" {
} }
// Go back to primary // Go back to primary
t.switchScreenMode(.@"47", false); try t.switchScreenMode(.@"47", false);
try testing.expectEqual(ScreenType.primary, t.active_screen); try testing.expectEqual(.primary, t.screens.active_key);
// Primary screen should still have the original content // 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 // Go back to alt screen with mode 47
t.switchScreenMode(.@"47", true); try t.switchScreenMode(.@"47", true);
try testing.expectEqual(ScreenType.alternate, t.active_screen); try testing.expectEqual(.alternate, t.screens.active_key);
// Screen should retain content // 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 } }); try t.setAttribute(.{ .direct_color_fg = .{ .r = 0xFF, .g = 0, .b = 0x7F } });
// Go to alt screen with mode 47 // Go to alt screen with mode 47
t.switchScreenMode(.@"47", true); try t.switchScreenMode(.@"47", true);
try testing.expectEqual(ScreenType.alternate, t.active_screen); try testing.expectEqual(.alternate, t.screens.active_key);
// Verify that our style is set // 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 } }); try t.setAttribute(.{ .direct_color_fg = .{ .r = 0, .g = 0xFF, .b = 0 } });
// Go back to primary // Go back to primary
t.switchScreenMode(.@"47", false); try t.switchScreenMode(.@"47", false);
try testing.expectEqual(ScreenType.primary, t.active_screen); try testing.expectEqual(.primary, t.screens.active_key);
// Verify that our style is still set // Verify that our style is still set
{ {
@ -11251,8 +11265,8 @@ test "Terminal: mode 1047 alt screen plain" {
try t.printString("1A"); try t.printString("1A");
// Go to alt screen with mode 47 // Go to alt screen with mode 47
t.switchScreenMode(.@"1047", true); try t.switchScreenMode(.@"1047", true);
try testing.expectEqual(ScreenType.alternate, t.active_screen); try testing.expectEqual(.alternate, t.screens.active_key);
// Screen should be empty // Screen should be empty
{ {
@ -11271,8 +11285,8 @@ test "Terminal: mode 1047 alt screen plain" {
} }
// Go back to primary // Go back to primary
t.switchScreenMode(.@"1047", false); try t.switchScreenMode(.@"1047", false);
try testing.expectEqual(ScreenType.primary, t.active_screen); try testing.expectEqual(.primary, t.screens.active_key);
// Primary screen should still have the original content // 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 // Go back to alt screen with mode 1047
t.switchScreenMode(.@"1047", true); try t.switchScreenMode(.@"1047", true);
try testing.expectEqual(ScreenType.alternate, t.active_screen); try testing.expectEqual(.alternate, t.screens.active_key);
// Screen should be empty // 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 } }); try t.setAttribute(.{ .direct_color_fg = .{ .r = 0xFF, .g = 0, .b = 0x7F } });
// Go to alt screen with mode 47 // Go to alt screen with mode 47
t.switchScreenMode(.@"1047", true); try t.switchScreenMode(.@"1047", true);
try testing.expectEqual(ScreenType.alternate, t.active_screen); try testing.expectEqual(.alternate, t.screens.active_key);
// Verify that our style is set // 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 } }); try t.setAttribute(.{ .direct_color_fg = .{ .r = 0, .g = 0xFF, .b = 0 } });
// Go back to primary // Go back to primary
t.switchScreenMode(.@"1047", false); try t.switchScreenMode(.@"1047", false);
try testing.expectEqual(ScreenType.primary, t.active_screen); try testing.expectEqual(.primary, t.screens.active_key);
// Verify that our style is still set // Verify that our style is still set
{ {
@ -11338,8 +11352,8 @@ test "Terminal: mode 1049 alt screen plain" {
try t.printString("1A"); try t.printString("1A");
// Go to alt screen with mode 47 // Go to alt screen with mode 47
t.switchScreenMode(.@"1049", true); try t.switchScreenMode(.@"1049", true);
try testing.expectEqual(ScreenType.alternate, t.active_screen); try testing.expectEqual(.alternate, t.screens.active_key);
// Screen should be empty // Screen should be empty
{ {
@ -11358,8 +11372,8 @@ test "Terminal: mode 1049 alt screen plain" {
} }
// Go back to primary // Go back to primary
t.switchScreenMode(.@"1049", false); try t.switchScreenMode(.@"1049", false);
try testing.expectEqual(ScreenType.primary, t.active_screen); try testing.expectEqual(.primary, t.screens.active_key);
// Primary screen should still have the original content // 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 // Go back to alt screen with mode 1049
t.switchScreenMode(.@"1049", true); try t.switchScreenMode(.@"1049", true);
try testing.expectEqual(ScreenType.alternate, t.active_screen); try testing.expectEqual(.alternate, t.screens.active_key);
// Screen should be empty // Screen should be empty
{ {

View File

@ -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.content = self.content;
screen_formatter.extra = self.extra.screen; screen_formatter.extra = self.extra.screen;
screen_formatter.pin_map = self.pin_map; screen_formatter.pin_map = self.pin_map;
@ -4231,7 +4231,7 @@ test "Screen plain single line" {
var pin_map: std.ArrayList(Pin) = .empty; var pin_map: std.ArrayList(Pin) = .empty;
defer pin_map.deinit(alloc); 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 }; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
try formatter.format(&builder.writer); try formatter.format(&builder.writer);
@ -4268,7 +4268,7 @@ test "Screen plain multiline" {
var pin_map: std.ArrayList(Pin) = .empty; var pin_map: std.ArrayList(Pin) = .empty;
defer pin_map.deinit(alloc); 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 }; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
try formatter.format(&builder.writer); try formatter.format(&builder.writer);
@ -4316,7 +4316,7 @@ test "Screen plain with selection" {
var pin_map: std.ArrayList(Pin) = .empty; var pin_map: std.ArrayList(Pin) = .empty;
defer pin_map.deinit(alloc); defer pin_map.deinit(alloc);
var formatter: ScreenFormatter = .init(&t.screen, .plain); var formatter: ScreenFormatter = .init(t.screen, .plain);
formatter.content = .{ .selection = .init( formatter.content = .{ .selection = .init(
t.screen.pages.pin(.{ .active = .{ .x = 0, .y = 1 } }).?, t.screen.pages.pin(.{ .active = .{ .x = 0, .y = 1 } }).?,
t.screen.pages.pin(.{ .active = .{ .x = 4, .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; var pin_map: std.ArrayList(Pin) = .empty;
defer pin_map.deinit(alloc); defer pin_map.deinit(alloc);
var formatter: ScreenFormatter = .init(&t.screen, .vt); var formatter: ScreenFormatter = .init(t.screen, .vt);
formatter.extra.cursor = true; formatter.extra.cursor = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4420,7 +4420,7 @@ test "Screen vt with style" {
var pin_map: std.ArrayList(Pin) = .empty; var pin_map: std.ArrayList(Pin) = .empty;
defer pin_map.deinit(alloc); defer pin_map.deinit(alloc);
var formatter: ScreenFormatter = .init(&t.screen, .vt); var formatter: ScreenFormatter = .init(t.screen, .vt);
formatter.extra.style = true; formatter.extra.style = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4472,7 +4472,7 @@ test "Screen vt with hyperlink" {
var pin_map: std.ArrayList(Pin) = .empty; var pin_map: std.ArrayList(Pin) = .empty;
defer pin_map.deinit(alloc); defer pin_map.deinit(alloc);
var formatter: ScreenFormatter = .init(&t.screen, .vt); var formatter: ScreenFormatter = .init(t.screen, .vt);
formatter.extra.hyperlink = true; formatter.extra.hyperlink = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4532,7 +4532,7 @@ test "Screen vt with protection" {
var pin_map: std.ArrayList(Pin) = .empty; var pin_map: std.ArrayList(Pin) = .empty;
defer pin_map.deinit(alloc); defer pin_map.deinit(alloc);
var formatter: ScreenFormatter = .init(&t.screen, .vt); var formatter: ScreenFormatter = .init(t.screen, .vt);
formatter.extra.protection = true; formatter.extra.protection = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; 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; var pin_map: std.ArrayList(Pin) = .empty;
defer pin_map.deinit(alloc); 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.extra.kitty_keyboard = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4638,7 +4638,7 @@ test "Screen vt with charsets" {
var pin_map: std.ArrayList(Pin) = .empty; var pin_map: std.ArrayList(Pin) = .empty;
defer pin_map.deinit(alloc); defer pin_map.deinit(alloc);
var formatter: ScreenFormatter = .init(&t.screen, .vt); var formatter: ScreenFormatter = .init(t.screen, .vt);
formatter.extra.charsets = true; formatter.extra.charsets = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };

View File

@ -252,7 +252,7 @@ fn display(
result.placement_id, result.placement_id,
p, p,
) catch |err| { ) catch |err| {
p.deinit(&terminal.screen); p.deinit(terminal.screen);
encodeError(&result, err); encodeError(&result, err);
return result; return result;
}; };

View File

@ -232,7 +232,7 @@ pub const ImageStorage = struct {
// Deinit the placement and remove it // Deinit the placement and remove it
const image_id = entry.key_ptr.image_id; 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); self.placements.removeByPtr(entry.key_ptr);
if (delete_images) self.deleteIfUnused(alloc, image_id); if (delete_images) self.deleteIfUnused(alloc, image_id);
} }
@ -247,7 +247,7 @@ pub const ImageStorage = struct {
.id => |v| self.deleteById( .id => |v| self.deleteById(
alloc, alloc,
&t.screen, t.screen,
v.image_id, v.image_id,
v.placement_id, v.placement_id,
v.delete, v.delete,
@ -257,7 +257,7 @@ pub const ImageStorage = struct {
const img = self.imageByNumber(v.image_number) orelse break :newest; const img = self.imageByNumber(v.image_number) orelse break :newest;
self.deleteById( self.deleteById(
alloc, alloc,
&t.screen, t.screen,
img.id, img.id,
v.placement_id, v.placement_id,
v.delete, v.delete,
@ -332,7 +332,7 @@ pub const ImageStorage = struct {
const img = self.imageById(entry.key_ptr.image_id) orelse continue; const img = self.imageById(entry.key_ptr.image_id) orelse continue;
const rect = entry.value_ptr.rect(img, t) orelse continue; const rect = entry.value_ptr.rect(img, t) orelse continue;
if (rect.top_left.x <= x and rect.bottom_right.x >= x) { 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); self.placements.removeByPtr(entry.key_ptr);
if (v.delete) self.deleteIfUnused(alloc, img.id); if (v.delete) self.deleteIfUnused(alloc, img.id);
} }
@ -364,7 +364,7 @@ pub const ImageStorage = struct {
var target_pin_copy = target_pin; var target_pin_copy = target_pin;
target_pin_copy.x = rect.top_left.x; target_pin_copy.x = rect.top_left.x;
if (target_pin_copy.isBetween(rect.top_left, rect.bottom_right)) { 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); self.placements.removeByPtr(entry.key_ptr);
if (v.delete) self.deleteIfUnused(alloc, img.id); if (v.delete) self.deleteIfUnused(alloc, img.id);
} }
@ -387,7 +387,7 @@ pub const ImageStorage = struct {
if (entry.value_ptr.z == v.z) { if (entry.value_ptr.z == v.z) {
const image_id = entry.key_ptr.image_id; 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); self.placements.removeByPtr(entry.key_ptr);
if (v.delete) self.deleteIfUnused(alloc, image_id); if (v.delete) self.deleteIfUnused(alloc, image_id);
} }
@ -411,7 +411,7 @@ pub const ImageStorage = struct {
while (it.next()) |entry| { while (it.next()) |entry| {
if (entry.key_ptr.image_id >= v.first or entry.key_ptr.image_id <= v.last) { if (entry.key_ptr.image_id >= v.first or entry.key_ptr.image_id <= v.last) {
const image_id = entry.key_ptr.image_id; 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); self.placements.removeByPtr(entry.key_ptr);
if (v.delete) self.deleteIfUnused(alloc, image_id); 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; const rect = entry.value_ptr.rect(img, t) orelse continue;
if (target_pin.isBetween(rect.top_left, rect.bottom_right)) { if (target_pin.isBetween(rect.top_left, rect.bottom_right)) {
if (filter) |f| if (!f(filter_ctx, entry.value_ptr.*)) continue; 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); self.placements.removeByPtr(entry.key_ptr);
if (delete_unused) self.deleteIfUnused(alloc, img.id); if (delete_unused) self.deleteIfUnused(alloc, img.id);
} }
@ -825,7 +825,7 @@ test "storage: add placement with zero placement id" {
t.height_px = 100; t.height_px = 100;
var s: ImageStorage = .{}; 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 = 1, .width = 50, .height = 50 });
try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); 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 }) } }); 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(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1 });
try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 }); 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(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen); defer s.deinit(alloc, t.screen);
s.total_limit = 5000; s.total_limit = 5000;
try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 1 });
try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 2 });
@ -901,7 +901,7 @@ test "storage: delete all placements" {
const tracked = t.screen.pages.countTrackedPins(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1 });
try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 }); try s.addImage(alloc, .{ .id = 3 });
@ -924,7 +924,7 @@ test "storage: delete all placements by image id" {
const tracked = t.screen.pages.countTrackedPins(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1 });
try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 }); 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(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1 });
try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 }); try s.addImage(alloc, .{ .id = 3 });
@ -970,7 +970,7 @@ test "storage: delete placement by specific id" {
const tracked = t.screen.pages.countTrackedPins(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1 });
try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 }); try s.addImage(alloc, .{ .id = 3 });
@ -1000,7 +1000,7 @@ test "storage: delete intersecting cursor" {
const tracked = t.screen.pages.countTrackedPins(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1, .width = 50, .height = 50 });
try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); 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 }) } }); 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(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1, .width = 50, .height = 50 });
try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); 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 }) } }); 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(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1, .width = 50, .height = 50 });
try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); 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 }) } }); 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(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1, .width = 50, .height = 50 });
try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); 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 }) } }); 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; t.height_px = 100;
var s: ImageStorage = .{}; 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.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, 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 }) } }); 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(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1, .width = 50, .height = 50 });
try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); 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 }) } }); 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; t.height_px = 100;
var s: ImageStorage = .{}; 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.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, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 0 }) } });
try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 1 }) } }); 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(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1 });
try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 }); try s.addImage(alloc, .{ .id = 3 });
@ -1245,7 +1245,7 @@ test "storage: delete images by range 2" {
const tracked = t.screen.pages.countTrackedPins(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1 });
try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 }); try s.addImage(alloc, .{ .id = 3 });
@ -1270,7 +1270,7 @@ test "storage: delete images by range 3" {
const tracked = t.screen.pages.countTrackedPins(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1 });
try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 }); try s.addImage(alloc, .{ .id = 3 });
@ -1295,7 +1295,7 @@ test "storage: delete images by range 4" {
const tracked = t.screen.pages.countTrackedPins(); const tracked = t.screen.pages.countTrackedPins();
var s: ImageStorage = .{}; 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 = 1 });
try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 }); try s.addImage(alloc, .{ .id = 3 });

View File

@ -1180,7 +1180,7 @@ test "unicode render placement: dog 4x2" {
var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 }); var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 });
defer t.deinit(alloc); defer t.deinit(alloc);
var s: ImageStorage = .{}; var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen); defer s.deinit(alloc, t.screen);
const image: Image = .{ .id = 1, .width = 500, .height = 306 }; const image: Image = .{ .id = 1, .width = 500, .height = 306 };
try s.addImage(alloc, image); 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 }); var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 });
defer t.deinit(alloc); defer t.deinit(alloc);
var s: ImageStorage = .{}; var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen); defer s.deinit(alloc, t.screen);
const image: Image = .{ .id = 1, .width = 500, .height = 306 }; const image: Image = .{ .id = 1, .width = 500, .height = 306 };
try s.addImage(alloc, image); 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 }); var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 });
defer t.deinit(alloc); defer t.deinit(alloc);
var s: ImageStorage = .{}; var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen); defer s.deinit(alloc, t.screen);
const image: Image = .{ .id = 1, .width = 500, .height = 306 }; const image: Image = .{ .id = 1, .width = 500, .height = 306 };
try s.addImage(alloc, image); try s.addImage(alloc, image);

View File

@ -41,7 +41,7 @@ pub const Point = point.Point;
pub const ReadonlyHandler = stream_readonly.Handler; pub const ReadonlyHandler = stream_readonly.Handler;
pub const ReadonlyStream = stream_readonly.Stream; pub const ReadonlyStream = stream_readonly.Stream;
pub const Screen = @import("Screen.zig"); pub const Screen = @import("Screen.zig");
pub const ScreenType = Terminal.ScreenType; pub const ScreenSet = @import("ScreenSet.zig");
pub const Scrollbar = PageList.Scrollbar; pub const Scrollbar = PageList.Scrollbar;
pub const Selection = @import("Selection.zig"); pub const Selection = @import("Selection.zig");
pub const SizeReportStyle = csi.SizeReportStyle; pub const SizeReportStyle = csi.SizeReportStyle;

View File

@ -391,7 +391,7 @@ test "simple search" {
defer s.deinit(); defer s.deinit();
try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang"); 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(); defer search.deinit();
try search.searchAll(); try search.searchAll();
try testing.expectEqual(2, search.active_results.items.len); 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"); for (0..list.rows) |_| try s.nextSlice("\r\n");
try s.nextSlice("hello."); 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(); defer search.deinit();
try search.searchAll(); try search.searchAll();
try testing.expectEqual(0, search.active_results.items.len); 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"); try s.nextSlice("Fizz\r\n");
// Start up our search which will populate our initial active area. // 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(); defer search.deinit();
try search.searchAll(); try search.searchAll();
{ {
@ -562,7 +562,7 @@ test "active change contents" {
defer s.deinit(); defer s.deinit();
try s.nextSlice("Fuzz\r\nBuzz\r\nFizz\r\nBang"); 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(); defer search.deinit();
try search.searchAll(); try search.searchAll();
try testing.expectEqual(1, search.active_results.items.len); try testing.expectEqual(1, search.active_results.items.len);

View File

@ -233,9 +233,9 @@ pub const Handler = struct {
self.terminal.scrolling_region.right = self.terminal.cols - 1; self.terminal.scrolling_region.right = self.terminal.cols - 1;
}, },
.alt_screen_legacy => self.terminal.switchScreenMode(.@"47", enabled), .alt_screen_legacy => try self.terminal.switchScreenMode(.@"47", enabled),
.alt_screen => self.terminal.switchScreenMode(.@"1047", enabled), .alt_screen => try self.terminal.switchScreenMode(.@"1047", enabled),
.alt_screen_save_cursor_clear_enter => self.terminal.switchScreenMode(.@"1049", enabled), .alt_screen_save_cursor_clear_enter => try self.terminal.switchScreenMode(.@"1049", enabled),
.save_cursor => if (enabled) { .save_cursor => if (enabled) {
self.terminal.saveCursor(); self.terminal.saveCursor();
@ -527,18 +527,18 @@ test "alt screen" {
// Write to primary screen // Write to primary screen
try s.nextSlice("Primary"); 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 // Switch to alt screen
try s.nextSlice("\x1B[?1049h"); 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 // Write to alt screen
try s.nextSlice("Alt"); try s.nextSlice("Alt");
// Switch back to primary // Switch back to primary
try s.nextSlice("\x1B[?1049l"); 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); const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str); defer testing.allocator.free(str);

View File

@ -246,16 +246,15 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
errdefer term.deinit(alloc); errdefer term.deinit(alloc);
// Set the image size limits // Set the image size limits
try term.screen.kitty_images.setLimit( var it = term.screens.all.iterator();
alloc, while (it.next()) |entry| {
&term.screen, const screen: *terminalpkg.Screen = entry.value.*;
opts.config.image_storage_limit, try screen.kitty_images.setLimit(
); alloc,
try term.secondary_screen.kitty_images.setLimit( screen,
alloc, opts.config.image_storage_limit,
&term.secondary_screen, );
opts.config.image_storage_limit, }
);
// Set our default cursor style // Set our default cursor style
term.screen.cursor.cursor_style = opts.config.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 // Set the image size limits
try self.terminal.screen.kitty_images.setLimit( var it = self.terminal.screens.all.iterator();
self.alloc, while (it.next()) |entry| {
&self.terminal.screen, const screen: *terminalpkg.Screen = entry.value.*;
config.image_storage_limit, try screen.kitty_images.setLimit(
); self.alloc,
try self.terminal.secondary_screen.kitty_images.setLimit( screen,
self.alloc, config.image_storage_limit,
&self.terminal.secondary_screen, );
config.image_storage_limit, }
);
} }
/// Resize the terminal. /// 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 // emulator-level screen clear, this messes up the running programs
// knowledge of where the cursor is and causes rendering issues. So, // knowledge of where the cursor is and causes rendering issues. So,
// for alt screen, we do nothing. // for alt screen, we do nothing.
if (self.terminal.active_screen == .alternate) return; if (self.terminal.screens.active_key == .alternate) return;
// Clear our selection // Clear our selection
self.terminal.screen.clearSelection(); self.terminal.screen.clearSelection();

View File

@ -583,15 +583,15 @@ pub const StreamHandler = struct {
}, },
.alt_screen_legacy => { .alt_screen_legacy => {
self.terminal.switchScreenMode(.@"47", enabled); try self.terminal.switchScreenMode(.@"47", enabled);
}, },
.alt_screen => { .alt_screen => {
self.terminal.switchScreenMode(.@"1047", enabled); try self.terminal.switchScreenMode(.@"1047", enabled);
}, },
.alt_screen_save_cursor_clear_enter => { .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 // Mode 1048 is xterm's conditional save cursor depending