terminal: change primary/alt screens to use a ScreenSet, stable pointers (#9594)

This PR changes our primary/alt screen tracking from being by-value
fields that are copied during switch to heap-allocated pointers that are
stable. This is motivated now by #189 since our search thread needs a
stable screen pointer, but this will also help us in the future with our
future N-screens proposal I have.

Also, as a nice bonus, alt screen memory is now initialized on demand
when you first enter alt-screen, so this saves a few MB of memory for a
new terminal if you never use alt screen!

This is something I've wanted to do for a veryyyyyy long time, but the
annoyance of the task really held me back. I finally pushed through and
did this with the help of some AI agents for the rote tasks (renaming
stuff).
pull/9595/head
Mitchell Hashimoto 2025-11-14 16:00:47 -08:00 committed by GitHub
commit 7cc8ea7efb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 1796 additions and 1625 deletions

View File

@ -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,
@ -1077,7 +1077,7 @@ fn selectionScrollTick(self: *Surface) !void {
try t.scrollViewport(.{ .delta = delta });
// Next, trigger our drag behavior
const pin = t.screen.pages.pin(.{
const pin = t.screens.active.pages.pin(.{
.viewport = .{
.x = pos_vp.x,
.y = pos_vp.y,
@ -1161,7 +1161,7 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void {
// so that we can close the terminal. We close the terminal on
// any key press that encodes a character.
t.modes.set(.disable_keyboard, false);
t.screen.kitty_keyboard.set(.set, .disabled);
t.screens.active.kitty_keyboard.set(.set, .disabled);
}
// Waiting after command we stop here. The terminal is updated, our
@ -1372,7 +1372,7 @@ fn mouseRefreshLinks(
const left_idx = @intFromEnum(input.MouseButton.left);
if (self.mouse.click_state[left_idx] == .press) click: {
const pin = self.mouse.left_click_pin orelse break :click;
const click_pt = self.io.terminal.screen.pages.pointFromPin(
const click_pt = self.io.terminal.screens.active.pages.pointFromPin(
.viewport,
pin.*,
) orelse break :click;
@ -1386,7 +1386,7 @@ fn mouseRefreshLinks(
const link = (try self.linkAtPos(pos)) orelse break :link .{ null, false };
switch (link[0]) {
.open => {
const str = try self.io.terminal.screen.selectionString(alloc, .{
const str = try self.io.terminal.screens.active.selectionString(alloc, .{
.sel = link[1],
.trim = false,
});
@ -1416,7 +1416,7 @@ fn mouseRefreshLinks(
if (link_) |link| {
self.renderer_state.mouse.point = pos_vp;
self.mouse.over_link = true;
self.renderer_state.terminal.screen.dirty.hyperlink_hover = true;
self.renderer_state.terminal.screens.active.dirty.hyperlink_hover = true;
_ = try self.rt_app.performAction(
.{ .surface = self },
.mouse_shape,
@ -1692,7 +1692,7 @@ pub fn dumpTextLocked(
sel: terminal.Selection,
) !Text {
// Read out the text
const text = try self.io.terminal.screen.selectionString(alloc, .{
const text = try self.io.terminal.screens.active.selectionString(alloc, .{
.sel = sel,
.trim = false,
});
@ -1702,19 +1702,19 @@ pub fn dumpTextLocked(
const vp: ?Text.Viewport = viewport: {
// 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 vp_tl_pin = self.io.terminal.screens.active.pages.getTopLeft(.viewport);
const br_pin = sel.bottomRight(self.io.terminal.screens.active);
if (br_pin.before(vp_tl_pin)) break :viewport null;
// If our top-left pin is after the viewport, then we can't possibly
// have this text be within the viewport.
const vp_br_pin = self.io.terminal.screen.pages.getBottomRight(.viewport) orelse {
const vp_br_pin = self.io.terminal.screens.active.pages.getBottomRight(.viewport) orelse {
// I don't think this is possible but I don't want to crash on
// that assertion so let's just break out...
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.screens.active);
if (vp_br_pin.before(tl_pin)) break :viewport null;
// We established that our top-left somewhere before the viewport
@ -1724,7 +1724,7 @@ pub fn dumpTextLocked(
// Our top-left point. If it doesn't exist in the viewport it must
// be before and we can return (0,0).
const tl_pt: terminal.Point = self.io.terminal.screen.pages.pointFromPin(
const tl_pt: terminal.Point = self.io.terminal.screens.active.pages.pointFromPin(
.viewport,
tl_pin,
) orelse tl: {
@ -1737,7 +1737,7 @@ pub fn dumpTextLocked(
// Our bottom-right point. If it doesn't exist in the viewport
// it must be the bottom-right of the viewport.
const br_pt = self.io.terminal.screen.pages.pointFromPin(
const br_pt = self.io.terminal.screens.active.pages.pointFromPin(
.viewport,
br_pin,
) orelse br: {
@ -1745,7 +1745,7 @@ pub fn dumpTextLocked(
assert(vp_br_pin.before(br_pin));
}
break :br self.io.terminal.screen.pages.pointFromPin(
break :br self.io.terminal.screens.active.pages.pointFromPin(
.viewport,
vp_br_pin,
).?;
@ -1786,8 +1786,8 @@ pub fn dumpTextLocked(
};
// Utilize viewport sizing to convert to offsets
const start = tl_coord.y * self.io.terminal.screen.pages.cols + tl_coord.x;
const end = br_coord.y * self.io.terminal.screen.pages.cols + br_coord.x;
const start = tl_coord.y * self.io.terminal.screens.active.pages.cols + tl_coord.x;
const end = br_coord.y * self.io.terminal.screens.active.pages.cols + br_coord.x;
break :viewport .{
.tl_px_x = x,
@ -1807,15 +1807,15 @@ pub fn dumpTextLocked(
pub fn hasSelection(self: *const Surface) bool {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
return self.io.terminal.screen.selection != null;
return self.io.terminal.screens.active.selection != null;
}
/// Returns the selected text. This is allocated.
pub fn selectionString(self: *Surface, alloc: Allocator) !?[:0]const u8 {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
const sel = self.io.terminal.screen.selection orelse return null;
return try self.io.terminal.screen.selectionString(alloc, .{
const sel = self.io.terminal.screens.active.selection orelse return null;
return try self.io.terminal.screens.active.selectionString(alloc, .{
.sel = sel,
.trim = false,
});
@ -1838,7 +1838,7 @@ pub fn pwd(
/// keyboard should be rendered.
pub fn imePoint(self: *const Surface) apprt.IMEPos {
self.renderer_state.mutex.lock();
const cursor = self.renderer_state.terminal.screen.cursor;
const cursor = self.renderer_state.terminal.screens.active.cursor;
const preedit_width: usize = if (self.renderer_state.preedit) |preedit| preedit.width() else 0;
self.renderer_state.mutex.unlock();
@ -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.screens.active, 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.screens.active, 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.screens.active, 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.screens.active, 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.screens.active, opts: {
var copy = opts;
copy.emit = .html;
@ -2079,8 +2079,8 @@ fn copySelectionToClipboards(
///
/// This must be called with the renderer mutex held.
fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void {
const prev_ = self.io.terminal.screen.selection;
try self.io.terminal.screen.select(sel_);
const prev_ = self.io.terminal.screens.active.selection;
try self.io.terminal.screens.active.select(sel_);
// If copy on select is false then exit early.
if (self.config.copy_on_select == .false) return;
@ -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.screens.active;
const p = screen.pages.pin(.{ .viewport = point }) orelse {
log.warn("failed to get pin for clicked point", .{});
return false;
@ -3594,7 +3594,7 @@ pub fn mouseButtonCallback(
if (self.config.copy_on_select != .false) {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
const prev_ = self.io.terminal.screen.selection;
const prev_ = self.io.terminal.screens.active.selection;
if (prev_) |prev| {
try self.setSelection(terminal.Selection.init(
prev.start(),
@ -3666,7 +3666,7 @@ pub fn mouseButtonCallback(
// If we have a selection then we do not do click to move because
// it means that we moved our cursor while pressing the mouse button.
if (self.io.terminal.screen.selection != null) break :click_move;
if (self.io.terminal.screens.active.selection != null) break :click_move;
// Moving always resets the click count so that we don't highlight.
self.mouse.left_click_count = 0;
@ -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.screens.active;
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);
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;
@ -3757,17 +3758,17 @@ pub fn mouseButtonCallback(
// Single click
1 => {
// If we have a selection, clear it. This always happens.
if (self.io.terminal.screen.selection != null) {
try self.io.terminal.screen.select(null);
if (self.io.terminal.screens.active.selection != null) {
try self.io.terminal.screens.active.select(null);
try self.queueRender();
}
},
// Double click, select the word under our mouse
2 => {
const sel_ = self.io.terminal.screen.selectWord(pin.*);
const sel_ = self.io.terminal.screens.active.selectWord(pin.*);
if (sel_) |sel| {
try self.io.terminal.screen.select(sel);
try self.io.terminal.screens.active.select(sel);
try self.queueRender();
}
},
@ -3775,11 +3776,11 @@ pub fn mouseButtonCallback(
// Triple click, select the line under our mouse
3 => {
const sel_ = if (mods.ctrlOrSuper())
self.io.terminal.screen.selectOutput(pin.*)
self.io.terminal.screens.active.selectOutput(pin.*)
else
self.io.terminal.screen.selectLine(.{ .pin = pin.* });
self.io.terminal.screens.active.selectLine(.{ .pin = pin.* });
if (sel_) |sel| {
try self.io.terminal.screen.select(sel);
try self.io.terminal.screens.active.select(sel);
try self.queueRender();
}
},
@ -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.screens.active;
const pin = pin: {
const pos = try self.rt_surface.getCursorPos();
const pt_viewport = self.posToViewport(pos.x, pos.y);
@ -3834,7 +3835,7 @@ pub fn mouseButtonCallback(
.@"context-menu" => {
// If we already have a selection and the selection contains
// where we clicked then we don't want to modify the selection.
if (self.io.terminal.screen.selection) |prev_sel| {
if (self.io.terminal.screens.active.selection) |prev_sel| {
if (prev_sel.contains(screen, pin)) break :sel;
// The selection doesn't contain our pin, so we create a new
@ -3849,7 +3850,7 @@ pub fn mouseButtonCallback(
return false;
},
.copy => {
if (self.io.terminal.screen.selection) |sel| {
if (self.io.terminal.screens.active.selection) |sel| {
try self.copySelectionToClipboards(
sel,
&.{.standard},
@ -3860,7 +3861,7 @@ pub fn mouseButtonCallback(
try self.setSelection(null);
try self.queueRender();
},
.@"copy-or-paste" => if (self.io.terminal.screen.selection) |sel| {
.@"copy-or-paste" => if (self.io.terminal.screens.active.selection) |sel| {
try self.copySelectionToClipboards(
sel,
&.{.standard},
@ -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
@ -3919,8 +3920,8 @@ fn clickMoveCursor(self: *Surface, to: terminal.Pin) !void {
if (!t.flags.shell_redraws_prompt) return;
// Get our path
const from = t.screen.cursor.page_pin.*;
const path = t.screen.promptPath(from, to);
const from = t.screens.active.cursor.page_pin.*;
const path = t.screens.active.promptPath(from, to);
log.debug("click-to-move-cursor from={} to={} path={}", .{ from, to, path });
// If we aren't moving at all, fast path out of here.
@ -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.screens.active;
const mouse_pin: terminal.Pin = mouse_pin: {
const point = self.posToViewport(pos.x, pos.y);
const pin = screen.pages.pin(.{ .viewport = point }) orelse {
@ -4052,7 +4053,7 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool {
const action, const sel = try self.linkAtPos(pos) orelse return false;
switch (action) {
.open => {
const str = try self.io.terminal.screen.selectionString(self.alloc, .{
const str = try self.io.terminal.screens.active.selectionString(self.alloc, .{
.sel = sel,
.trim = false,
});
@ -4138,8 +4139,8 @@ pub fn mousePressureCallback(
// This should always be set in this state but we don't want
// to handle state inconsistency here.
const pin = self.mouse.left_click_pin orelse break :select;
const sel = self.io.terminal.screen.selectWord(pin.*) orelse break :select;
try self.io.terminal.screen.select(sel);
const sel = self.io.terminal.screens.active.selectWord(pin.*) orelse break :select;
try self.io.terminal.screens.active.select(sel);
try self.queueRender();
}
}
@ -4196,7 +4197,7 @@ pub fn cursorPosCallback(
// Mark the link's row as dirty, but continue with updating the
// mouse state below so we can scroll when our position is negative.
self.renderer_state.terminal.screen.dirty.hyperlink_hover = true;
self.renderer_state.terminal.screens.active.dirty.hyperlink_hover = true;
}
// Always show the mouse again if it is hidden
@ -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.screens.active;
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.screens.active;
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.screens.active;
const click_pin = self.mouse.left_click_pin.?.*;
// Get the word closest to our starting click.
@ -4378,13 +4379,13 @@ fn dragLeftClickDouble(
// If our current mouse position is before the starting position,
// then the selection start is the word nearest our current position.
if (drag_pin.before(click_pin)) {
try self.io.terminal.screen.select(.init(
try self.io.terminal.screens.active.select(.init(
word_current.start(),
word_start.end(),
false,
));
} else {
try self.io.terminal.screen.select(.init(
try self.io.terminal.screens.active.select(.init(
word_start.start(),
word_current.end(),
false,
@ -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.screens.active;
const click_pin = self.mouse.left_click_pin.?.*;
// Get the line selection under our current drag point. If there isn't a
@ -4416,7 +4417,7 @@ fn dragLeftClickTriple(
} else {
sel.endPtr().* = line.end();
}
try self.io.terminal.screen.select(sel);
try self.io.terminal.screens.active.select(sel);
}
fn dragLeftClickSingle(
@ -4425,7 +4426,7 @@ fn dragLeftClickSingle(
drag_x: f64,
) !void {
// This logic is in a separate function so that it can be unit tested.
try self.io.terminal.screen.select(mouseSelection(
try self.io.terminal.screens.active.select(mouseSelection(
self.mouse.left_click_pin.?.*,
drag_pin,
@intFromFloat(@max(0.0, self.mouse.left_click_xpos)),
@ -4772,7 +4773,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
.copy_to_clipboard => |format| {
// We can read from the renderer state without holding
// the lock because only we will write to this field.
if (self.io.terminal.screen.selection) |sel| {
if (self.io.terminal.screens.active.selection) |sel| {
try self.copySelectionToClipboards(
sel,
&.{.standard},
@ -4805,7 +4806,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
const url_text = switch (link_info[0]) {
.open => url_text: {
// For regex links, get the text from selection
break :url_text (self.io.terminal.screen.selectionString(self.alloc, .{
break :url_text (self.io.terminal.screens.active.selectionString(self.alloc, .{
.sel = link_info[1],
.trim = self.config.clipboard_trim_trailing_spaces,
})) catch |err| {
@ -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(.{
@ -4955,7 +4956,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
const t: *terminal.Terminal = self.renderer_state.terminal;
t.screen.scroll(.{ .row = n });
t.screens.active.scroll(.{ .row = n });
}
try self.queueRender();
@ -4965,9 +4966,9 @@ 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);
self.io.terminal.screen.scroll(.{ .pin = tl });
const sel = self.io.terminal.screens.active.selection orelse return false;
const tl = sel.topLeft(self.io.terminal.screens.active);
self.io.terminal.screens.active.scroll(.{ .pin = tl });
}
try self.queueRender();
@ -5176,7 +5177,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
),
.select_all => {
const sel = self.io.terminal.screen.selectAll();
const sel = self.io.terminal.screens.active.selectAll();
if (sel) |s| {
try self.setSelection(s);
try self.queueRender();
@ -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.screens.active;
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
@ -5335,12 +5336,12 @@ fn writeScreenFile(
// We only dump history if we have history. We still keep
// the file and write the empty file to the pty so that this
// command always works on the primary screen.
const pages = &self.io.terminal.screen.pages;
const pages = &self.io.terminal.screens.active.pages;
const sel_: ?terminal.Selection = switch (loc) {
.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;
}
@ -5361,7 +5362,7 @@ fn writeScreenFile(
);
},
.selection => self.io.terminal.screen.selection,
.selection => self.io.terminal.screens.active.selection,
};
const sel = sel_ orelse {
@ -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.screens.active, .{
.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.screens.active,
.forward,
) };
try formatter.format(buf_writer);
@ -5696,7 +5697,7 @@ fn testMouseSelection(
.padding = .{ .left = 5, .top = 5, .right = 5, .bottom = 5 },
.screen = .{ .width = 110, .height = 110 },
};
var screen = try terminal.Screen.init(std.testing.allocator, 10, 5, 0);
var screen = try terminal.Screen.init(std.testing.allocator, .{ .cols = 10, .rows = 5, .max_scrollback = 0 });
defer screen.deinit();
// We hold both ctrl and alt for rectangular
@ -5765,7 +5766,7 @@ fn testMouseSelectionIsNull(
.padding = .{ .left = 5, .top = 5, .right = 5, .bottom = 5 },
.screen = .{ .width = 110, .height = 110 },
};
var screen = try terminal.Screen.init(std.testing.allocator, 10, 5, 0);
var screen = try terminal.Screen.init(std.testing.allocator, .{ .cols = 10, .rows = 5, .max_scrollback = 0 });
defer screen.deinit();
// We hold both ctrl and alt for rectangular

View File

@ -1558,7 +1558,7 @@ pub const CAPI = struct {
defer core_surface.renderer_state.mutex.unlock();
// If we don't have a selection, do nothing.
const core_sel = core_surface.io.terminal.screen.selection orelse return false;
const core_sel = core_surface.io.terminal.screens.active.selection orelse return false;
// Read the text from the selection.
return readTextLocked(surface, core_sel, result);
@ -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.screens.active,
) 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.screens.active;
const pos = try ptr.getCursorPos();
const pt_viewport = surface.posToViewport(pos.x, pos.y);
const pin = screen.pages.pin(.{
@ -2149,7 +2149,7 @@ pub const CAPI = struct {
if (comptime std.debug.runtime_safety) unreachable;
return false;
};
break :sel surface.io.terminal.screen.selectWord(pin) orelse return false;
break :sel surface.io.terminal.screens.active.selectWord(pin) orelse return false;
};
// Read the selection

View File

@ -598,7 +598,7 @@ test "run iterator" {
{
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("ABCD");
@ -616,7 +616,7 @@ test "run iterator" {
// Spaces should be part of a run
{
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("ABCD EFG");
@ -633,7 +633,7 @@ test "run iterator" {
{
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("A😃D");
@ -652,7 +652,7 @@ test "run iterator" {
// Bad ligatures
for (&[_][]const u8{ "fl", "fi", "st" }) |bad| {
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(bad);
@ -678,7 +678,7 @@ test "run iterator: empty cells with background set" {
{
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.setAttribute(.{ .direct_color_bg = .{ .r = 0xFF, .g = 0, .b = 0 } });
try screen.testWriteString("A");
@ -731,7 +731,7 @@ test "shape" {
buf_idx += try std.unicode.utf8Encode(0x1F3FD, buf[buf_idx..]); // Medium skin tone
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(buf[0..buf_idx]);
@ -764,7 +764,7 @@ test "shape nerd fonts" {
buf_idx += try std.unicode.utf8Encode(' ', buf[buf_idx..]); // space
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(buf[0..buf_idx]);
@ -791,7 +791,7 @@ test "shape inconsolata ligs" {
defer testdata.deinit();
{
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(">=");
@ -814,7 +814,7 @@ test "shape inconsolata ligs" {
}
{
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("===");
@ -845,7 +845,7 @@ test "shape monaspace ligs" {
defer testdata.deinit();
{
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("===");
@ -877,7 +877,7 @@ test "shape left-replaced lig in last run" {
defer testdata.deinit();
{
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("!==");
@ -909,7 +909,7 @@ test "shape left-replaced lig in early run" {
defer testdata.deinit();
{
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("!==X");
@ -938,7 +938,7 @@ test "shape U+3C9 with JB Mono" {
defer testdata.deinit();
{
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("\u{03C9} foo");
@ -969,7 +969,7 @@ test "shape emoji width" {
defer testdata.deinit();
{
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("👍");
@ -998,7 +998,7 @@ test "shape emoji width long" {
defer testdata.deinit();
// Make a screen and add a long emoji sequence to it.
var screen = try terminal.Screen.init(alloc, 30, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 30, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
var page = screen.pages.pages.first.?.data;
@ -1050,7 +1050,7 @@ test "shape variation selector VS15" {
buf_idx += try std.unicode.utf8Encode(0xFE0E, buf[buf_idx..]); // ZWJ to force text
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(buf[0..buf_idx]);
@ -1083,7 +1083,7 @@ test "shape variation selector VS16" {
buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // ZWJ to force color
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(buf[0..buf_idx]);
@ -1111,7 +1111,7 @@ test "shape with empty cells in between" {
defer testdata.deinit();
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 30, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 30, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("A");
screen.cursorRight(5);
@ -1149,7 +1149,7 @@ test "shape Chinese characters" {
buf_idx += try std.unicode.utf8Encode('a', buf[buf_idx..]);
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 30, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 30, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(buf[0..buf_idx]);
@ -1187,7 +1187,7 @@ test "shape box glyphs" {
buf_idx += try std.unicode.utf8Encode(0x2501, buf[buf_idx..]); //
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(buf[0..buf_idx]);
@ -1219,7 +1219,7 @@ test "shape selection boundary" {
defer testdata.deinit();
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("a1b2c3d4e5");
@ -1342,7 +1342,7 @@ test "shape cursor boundary" {
defer testdata.deinit();
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("a1b2c3d4e5");
@ -1479,7 +1479,7 @@ test "shape cursor boundary and colored emoji" {
defer testdata.deinit();
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 3, .rows = 10, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("👍🏼");
@ -1574,7 +1574,7 @@ test "shape cell attribute change" {
// Plain >= should shape into 1 run
{
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(">=");
@ -1594,7 +1594,7 @@ test "shape cell attribute change" {
// Bold vs regular should split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 3, .rows = 10, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(">");
try screen.setAttribute(.{ .bold = {} });
@ -1616,7 +1616,7 @@ test "shape cell attribute change" {
// Changing fg color should split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 3, .rows = 10, .max_scrollback = 0 });
defer screen.deinit();
try screen.setAttribute(.{ .direct_color_fg = .{ .r = 1, .g = 2, .b = 3 } });
try screen.testWriteString(">");
@ -1639,7 +1639,7 @@ test "shape cell attribute change" {
// Changing bg color should NOT split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 3, .rows = 10, .max_scrollback = 0 });
defer screen.deinit();
try screen.setAttribute(.{ .direct_color_bg = .{ .r = 1, .g = 2, .b = 3 } });
try screen.testWriteString(">");
@ -1662,7 +1662,7 @@ test "shape cell attribute change" {
// Same bg color should not split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 3, .rows = 10, .max_scrollback = 0 });
defer screen.deinit();
try screen.setAttribute(.{ .direct_color_bg = .{ .r = 1, .g = 2, .b = 3 } });
try screen.testWriteString(">");
@ -1700,7 +1700,7 @@ test "shape high plane sprite font codepoint" {
var testdata = try testShaper(alloc);
defer testdata.deinit();
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
// U+1FB70: Vertical One Eighth Block-2

View File

@ -207,7 +207,7 @@ test "run iterator" {
{
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("ABCD");
@ -225,7 +225,7 @@ test "run iterator" {
// Spaces should be part of a run
{
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("ABCD EFG");
@ -242,7 +242,7 @@ test "run iterator" {
{
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("A😃D");
@ -273,7 +273,7 @@ test "run iterator: empty cells with background set" {
{
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.setAttribute(.{ .direct_color_bg = .{ .r = 0xFF, .g = 0, .b = 0 } });
try screen.testWriteString("A");
@ -327,7 +327,7 @@ test "shape" {
buf_idx += try std.unicode.utf8Encode(0x1F3FD, buf[buf_idx..]); // Medium skin tone
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(buf[0..buf_idx]);
@ -355,7 +355,7 @@ test "shape inconsolata ligs" {
defer testdata.deinit();
{
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(">=");
@ -378,7 +378,7 @@ test "shape inconsolata ligs" {
}
{
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("===");
@ -409,7 +409,7 @@ test "shape monaspace ligs" {
defer testdata.deinit();
{
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("===");
@ -443,7 +443,7 @@ test "shape arabic forced LTR" {
var testdata = try testShaperWithFont(alloc, .arabic);
defer testdata.deinit();
var screen = try terminal.Screen.init(alloc, 120, 30, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 120, .rows = 30, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(@embedFile("testdata/arabic.txt"));
@ -478,7 +478,7 @@ test "shape emoji width" {
defer testdata.deinit();
{
var screen = try terminal.Screen.init(alloc, 5, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("👍");
@ -509,7 +509,10 @@ test "shape emoji width long" {
defer testdata.deinit();
// Make a screen and add a long emoji sequence to it.
var screen = try terminal.Screen.init(alloc, 30, 3, 0);
var screen = try terminal.Screen.init(
alloc,
.{ .cols = 30, .rows = 3 },
);
defer screen.deinit();
var page = screen.pages.pages.first.?.data;
@ -563,7 +566,7 @@ test "shape variation selector VS15" {
buf_idx += try std.unicode.utf8Encode(0xFE0E, buf[buf_idx..]); // ZWJ to force text
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(buf[0..buf_idx]);
@ -598,7 +601,7 @@ test "shape variation selector VS16" {
buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // ZWJ to force color
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(buf[0..buf_idx]);
@ -628,7 +631,10 @@ test "shape with empty cells in between" {
defer testdata.deinit();
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 30, 3, 0);
var screen = try terminal.Screen.init(
alloc,
.{ .cols = 30, .rows = 3 },
);
defer screen.deinit();
try screen.testWriteString("A");
screen.cursorRight(5);
@ -666,7 +672,10 @@ test "shape Chinese characters" {
buf_idx += try std.unicode.utf8Encode('a', buf[buf_idx..]);
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 30, 3, 0);
var screen = try terminal.Screen.init(
alloc,
.{ .cols = 30, .rows = 3 },
);
defer screen.deinit();
try screen.testWriteString(buf[0..buf_idx]);
@ -704,7 +713,7 @@ test "shape box glyphs" {
buf_idx += try std.unicode.utf8Encode(0x2501, buf[buf_idx..]); //
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(buf[0..buf_idx]);
@ -737,7 +746,7 @@ test "shape selection boundary" {
defer testdata.deinit();
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("a1b2c3d4e5");
@ -860,7 +869,7 @@ test "shape cursor boundary" {
defer testdata.deinit();
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString("a1b2c3d4e5");
@ -997,7 +1006,10 @@ test "shape cursor boundary and colored emoji" {
defer testdata.deinit();
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
var screen = try terminal.Screen.init(
alloc,
.{ .cols = 3, .rows = 10 },
);
defer screen.deinit();
try screen.testWriteString("👍🏼");
@ -1092,7 +1104,7 @@ test "shape cell attribute change" {
// Plain >= should shape into 1 run
{
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
var screen = try terminal.Screen.init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 });
defer screen.deinit();
try screen.testWriteString(">=");
@ -1112,7 +1124,10 @@ test "shape cell attribute change" {
// Bold vs regular should split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
var screen = try terminal.Screen.init(
alloc,
.{ .cols = 3, .rows = 10 },
);
defer screen.deinit();
try screen.testWriteString(">");
try screen.setAttribute(.{ .bold = {} });
@ -1134,7 +1149,10 @@ test "shape cell attribute change" {
// Changing fg color should split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
var screen = try terminal.Screen.init(
alloc,
.{ .cols = 3, .rows = 10 },
);
defer screen.deinit();
try screen.setAttribute(.{ .direct_color_fg = .{ .r = 1, .g = 2, .b = 3 } });
try screen.testWriteString(">");
@ -1157,7 +1175,10 @@ test "shape cell attribute change" {
// Changing bg color should not split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
var screen = try terminal.Screen.init(
alloc,
.{ .cols = 3, .rows = 10 },
);
defer screen.deinit();
try screen.setAttribute(.{ .direct_color_bg = .{ .r = 1, .g = 2, .b = 3 } });
try screen.testWriteString(">");
@ -1180,7 +1201,10 @@ test "shape cell attribute change" {
// Same bg color should not split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
var screen = try terminal.Screen.init(
alloc,
.{ .cols = 3, .rows = 10 },
);
defer screen.deinit();
try screen.setAttribute(.{ .direct_color_bg = .{ .r = 1, .g = 2, .b = 3 } });
try screen.testWriteString(">");

View File

@ -57,7 +57,7 @@ pub const Options = struct {
.keypad_key_application = t.modes.get(.keypad_keys),
.ignore_keypad_with_numlock = t.modes.get(.ignore_keypad_with_numlock),
.modify_other_keys_state_2 = t.flags.modify_other_keys_2,
.kitty_flags = t.screen.kitty_keyboard.current(),
.kitty_flags = t.screens.active.kitty_keyboard.current(),
// These can't be known from the terminal state.
.macos_option_as_alt = .false,

View File

@ -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.screens.active;
{
_ = 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);
}
}
}
@ -774,7 +774,7 @@ fn renderSizeWindow(self: *Inspector) void {
{
const hover_point: terminal.point.Coordinate = pt: {
const p = self.mouse.last_point orelse break :pt .{};
const pt = t.screen.pages.pointFromPin(
const pt = t.screens.active.pages.pointFromPin(
.active,
p,
) orelse break :pt .{};
@ -861,7 +861,7 @@ fn renderSizeWindow(self: *Inspector) void {
{
const left_click_point: terminal.point.Coordinate = pt: {
const p = mouse.left_click_pin orelse break :pt .{};
const pt = t.screen.pages.pointFromPin(
const pt = t.screens.active.pages.pointFromPin(
.active,
p.*,
) orelse break :pt .{};

View File

@ -64,7 +64,7 @@ pub const VTEvent = struct {
return .{
.kind = kind,
.str = str,
.cursor = t.screen.cursor,
.cursor = t.screens.active.cursor,
.scrolling_region = t.scrolling_region,
.metadata = md.unmanaged,
};

View File

@ -524,7 +524,7 @@ test "Cell constraint widths" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try terminal.Screen.init(alloc, 4, 1, 0);
var s = try terminal.Screen.init(alloc, .{ .cols = 4, .rows = 1, .max_scrollback = 0 });
defer s.deinit();
// for each case, the numbers in the comment denote expected

View File

@ -41,7 +41,7 @@ pub fn style(
// at the bottom, we never render the cursor. The cursor x/y is by
// viewport so if we are above the viewport, we'll end up rendering
// the cursor in some random part of the screen.
if (!state.terminal.screen.viewportIsBottom()) return null;
if (!state.terminal.screens.active.viewportIsBottom()) return null;
// If we are in preedit, then we always show the block cursor. We do
// this even if the cursor is explicitly not visible because it shows
@ -62,7 +62,7 @@ pub fn style(
}
// Otherwise, we use whatever style the terminal wants.
return .fromTerminal(state.terminal.screen.cursor.cursor_style);
return .fromTerminal(state.terminal.screens.active.cursor.cursor_style);
}
test "cursor: default uses configured style" {
@ -71,7 +71,7 @@ test "cursor: default uses configured style" {
var term = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 10 });
defer term.deinit(alloc);
term.screen.cursor.cursor_style = .bar;
term.screens.active.cursor.cursor_style = .bar;
term.modes.set(.cursor_blinking, true);
var state: State = .{
@ -92,7 +92,7 @@ test "cursor: blinking disabled" {
var term = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 10 });
defer term.deinit(alloc);
term.screen.cursor.cursor_style = .bar;
term.screens.active.cursor.cursor_style = .bar;
term.modes.set(.cursor_blinking, false);
var state: State = .{
@ -113,7 +113,7 @@ test "cursor: explicitly not visible" {
var term = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 10 });
defer term.deinit(alloc);
term.screen.cursor.cursor_style = .bar;
term.screens.active.cursor.cursor_style = .bar;
term.modes.set(.cursor_visible, false);
term.modes.set(.cursor_blinking, false);

View File

@ -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,
@ -1102,7 +1102,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// naturally limits the number of calls to this method (it
// can be expensive) and also makes it so we don't need another
// cross-thread mailbox message within the IO path.
const scrollbar = state.terminal.screen.pages.scrollbar();
const scrollbar = state.terminal.screens.active.pages.scrollbar();
// Get our bg/fg, swap them if reversed.
const RGB = terminal.color.RGB;
@ -1116,12 +1116,12 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
};
// Get the viewport pin so that we can compare it to the current.
const viewport_pin = state.terminal.screen.pages.pin(.{ .viewport = .{} }).?;
const viewport_pin = state.terminal.screens.active.pages.pin(.{ .viewport = .{} }).?;
// We used to share terminal state, but we've since learned through
// analysis that it is faster to copy the terminal state than to
// hold the lock while rebuilding GPU cells.
var screen_copy = try state.terminal.screen.clone(
var screen_copy = try state.terminal.screens.active.clone(
self.alloc,
.{ .viewport = .{} },
null,
@ -1153,7 +1153,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// If we have any virtual references, we must also rebuild our
// kitty state on every frame because any cell change can move
// an image.
if (state.terminal.screen.kitty_images.dirty or
if (state.terminal.screens.active.kitty_images.dirty or
self.image_virtual)
{
try self.prepKittyGraphics(state.terminal);
@ -1169,7 +1169,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
}
{
const Int = @typeInfo(terminal.Screen.Dirty).@"struct".backing_integer.?;
const v: Int = @bitCast(state.terminal.screen.dirty);
const v: Int = @bitCast(state.terminal.screens.active.dirty);
if (v > 0) break :rebuild true;
}
@ -1187,9 +1187,9 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// success and reset while we hold the lock. This is much easier
// than coordinating row by row or as changes are persisted.
state.terminal.flags.dirty = .{};
state.terminal.screen.dirty = .{};
state.terminal.screens.active.dirty = .{};
{
var it = state.terminal.screen.pages.pageIterator(
var it = state.terminal.screens.active.pages.pageIterator(
.right_down,
.{ .screen = .{} },
null,
@ -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(),
@ -1633,7 +1633,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
self.draw_mutex.lock();
defer self.draw_mutex.unlock();
const storage = &t.screen.kitty_images;
const storage = &t.screens.active.kitty_images;
defer storage.dirty = false;
// We always clear our previous placements no matter what because
@ -1657,10 +1657,10 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// The top-left and bottom-right corners of our viewport in screen
// points. This lets us determine offsets and containment of placements.
const top = t.screen.pages.getTopLeft(.viewport);
const bot = t.screen.pages.getBottomRight(.viewport).?;
const top_y = t.screen.pages.pointFromPin(.screen, top).?.screen.y;
const bot_y = t.screen.pages.pointFromPin(.screen, bot).?.screen.y;
const top = t.screens.active.pages.getTopLeft(.viewport);
const bot = t.screens.active.pages.getBottomRight(.viewport).?;
const top_y = t.screens.active.pages.pointFromPin(.screen, top).?.screen.y;
const bot_y = t.screens.active.pages.pointFromPin(.screen, bot).?.screen.y;
// Go through the placements and ensure the image is
// on the GPU or else is ready to be sent to the GPU.
@ -1751,7 +1751,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
t: *terminal.Terminal,
p: *const terminal.kitty.graphics.unicode.Placement,
) !void {
const storage = &t.screen.kitty_images;
const storage = &t.screens.active.kitty_images;
const image = storage.imageById(p.image_id) orelse {
log.warn(
"missing image for virtual placement, ignoring image_id={}",
@ -1773,7 +1773,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// If our placement is zero sized then we don't do anything.
if (rp.dest_width == 0 or rp.dest_height == 0) return;
const viewport: terminal.point.Point = t.screen.pages.pointFromPin(
const viewport: terminal.point.Point = t.screens.active.pages.pointFromPin(
.viewport,
rp.top_left,
) orelse {
@ -1817,8 +1817,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
const rect = p.rect(image.*, t) orelse return;
// This is expensive but necessary.
const img_top_y = t.screen.pages.pointFromPin(.screen, rect.top_left).?.screen.y;
const img_bot_y = t.screen.pages.pointFromPin(.screen, rect.bottom_right).?.screen.y;
const img_top_y = t.screens.active.pages.pointFromPin(.screen, rect.top_left).?.screen.y;
const img_bot_y = t.screens.active.pages.pointFromPin(.screen, rect.bottom_right).?.screen.y;
// If the selection isn't within our viewport then skip it.
if (img_top_y > bot_y) return;
@ -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,

View File

@ -398,7 +398,7 @@ test "matchset" {
const alloc = testing.allocator;
// Initialize our screen
var s = try Screen.init(alloc, 5, 5, 0);
var s = try Screen.init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 });
defer s.deinit();
const str = "1ABCD2EFGH\n3IJKL";
try s.testWriteString(str);
@ -456,7 +456,7 @@ test "matchset hover links" {
const alloc = testing.allocator;
// Initialize our screen
var s = try Screen.init(alloc, 5, 5, 0);
var s = try Screen.init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 });
defer s.deinit();
const str = "1ABCD2EFGH\n3IJKL";
try s.testWriteString(str);
@ -549,7 +549,7 @@ test "matchset mods no match" {
const alloc = testing.allocator;
// Initialize our screen
var s = try Screen.init(alloc, 5, 5, 0);
var s = try Screen.init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 });
defer s.deinit();
const str = "1ABCD2EFGH\n3IJKL";
try s.testWriteString(str);
@ -609,12 +609,12 @@ 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.screens.active;
try t.printString("ABC");
try t.screen.startHyperlink("http://example.com", null);
try t.screens.active.startHyperlink("http://example.com", null);
try t.printString("123");
t.screen.endHyperlink();
t.screens.active.endHyperlink();
// Get a set
var set = try Set.fromConfig(alloc, &.{});
@ -624,7 +624,7 @@ test "matchset osc8" {
{
var match = try set.matchSet(
alloc,
&t.screen,
t.screens.active,
.{ .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.screens.active,
.{ .x = 3, .y = 0 },
inputpkg.ctrlOrSuper(.{}),
);

File diff suppressed because it is too large Load Diff

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

@ -451,7 +451,7 @@ pub fn adjust(
test "Selection: adjust right" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("A1234\nB5678\nC1234\nD5678");
@ -518,7 +518,7 @@ test "Selection: adjust right" {
test "Selection: adjust left" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("A1234\nB5678\nC1234\nD5678");
@ -567,7 +567,7 @@ test "Selection: adjust left" {
test "Selection: adjust left skips blanks" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("A1234\nB5678\nC12\nD56");
@ -616,7 +616,7 @@ test "Selection: adjust left skips blanks" {
test "Selection: adjust up" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("A\nB\nC\nD\nE");
@ -663,7 +663,7 @@ test "Selection: adjust up" {
test "Selection: adjust down" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("A\nB\nC\nD\nE");
@ -710,7 +710,7 @@ test "Selection: adjust down" {
test "Selection: adjust down with not full screen" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("A\nB\nC");
@ -738,7 +738,7 @@ test "Selection: adjust down with not full screen" {
test "Selection: adjust home" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("A\nB\nC");
@ -766,7 +766,7 @@ test "Selection: adjust home" {
test "Selection: adjust end with not full screen" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("A\nB\nC");
@ -794,7 +794,7 @@ test "Selection: adjust end with not full screen" {
test "Selection: adjust beginning of line" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 8, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 8, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("A12 B34\nC12 D34");
@ -864,7 +864,7 @@ test "Selection: adjust beginning of line" {
test "Selection: adjust end of line" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 8, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 8, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("A12 B34\nC12 D34");
@ -934,7 +934,7 @@ test "Selection: order, standard" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try Screen.init(alloc, 100, 100, 1);
var s = try Screen.init(alloc, .{ .cols = 100, .rows = 100, .max_scrollback = 1 });
defer s.deinit();
{
@ -998,7 +998,7 @@ test "Selection: order, rectangle" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try Screen.init(alloc, 100, 100, 1);
var s = try Screen.init(alloc, .{ .cols = 100, .rows = 100, .max_scrollback = 1 });
defer s.deinit();
// Conventions:
@ -1110,7 +1110,7 @@ test "Selection: order, rectangle" {
test "topLeft" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
{
// forward
@ -1173,7 +1173,7 @@ test "topLeft" {
test "bottomRight" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
{
// forward
@ -1236,7 +1236,7 @@ test "bottomRight" {
test "ordered" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
{
// forward
@ -1317,7 +1317,7 @@ test "ordered" {
test "Selection: contains" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 10, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 10, .max_scrollback = 0 });
defer s.deinit();
{
const sel = Selection.init(
@ -1363,7 +1363,7 @@ test "Selection: contains" {
test "Selection: contains, rectangle" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 15, 15, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 15, .rows = 15, .max_scrollback = 0 });
defer s.deinit();
{
const sel = Selection.init(
@ -1425,7 +1425,7 @@ test "Selection: contains, rectangle" {
test "Selection: containedRow" {
const testing = std.testing;
var s = try Screen.init(testing.allocator, 10, 5, 0);
var s = try Screen.init(testing.allocator, .{ .cols = 10, .rows = 5, .max_scrollback = 0 });
defer s.deinit();
{

View File

@ -109,7 +109,7 @@ test "StringMap searchIterator" {
defer re.deinit();
// Initialize our screen
var s = try Screen.init(alloc, 5, 5, 0);
var s = try Screen.init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 });
defer s.deinit();
const str = "1ABCD2EFGH\n3IJKL";
try s.testWriteString(str);

File diff suppressed because it is too large Load Diff

View File

@ -287,7 +287,7 @@ pub const TerminalFormatter = struct {
// the node pointer is always properly initialized.
m.map.appendNTimes(
m.alloc,
self.terminal.screen.pages.getTopLeft(.screen),
self.terminal.screens.active.pages.getTopLeft(.screen),
discarding.count,
) catch return error.WriteFailed;
}
@ -325,13 +325,13 @@ pub const TerminalFormatter = struct {
// the node pointer is always properly initialized.
m.map.appendNTimes(
m.alloc,
self.terminal.screen.pages.getTopLeft(.screen),
self.terminal.screens.active.pages.getTopLeft(.screen),
discarding.count,
) catch return error.WriteFailed;
}
}
var screen_formatter: ScreenFormatter = .init(&self.terminal.screen, self.opts);
var screen_formatter: ScreenFormatter = .init(self.terminal.screens.active, self.opts);
screen_formatter.content = self.content;
screen_formatter.extra = self.extra.screen;
screen_formatter.pin_map = self.pin_map;
@ -409,7 +409,7 @@ pub const TerminalFormatter = struct {
.x = last.x,
.y = last.y,
};
} else self.terminal.screen.pages.getTopLeft(.screen),
} else self.terminal.screens.active.pages.getTopLeft(.screen),
discarding.count,
) catch return error.WriteFailed;
}
@ -1422,7 +1422,7 @@ test "Page plain single line" {
try s.nextSlice("hello, world");
// Verify we have only a single page
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -1469,7 +1469,7 @@ test "Page plain single wide char" {
try s.nextSlice("1A⚡");
// Verify we have only a single page
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -1560,7 +1560,7 @@ test "Page plain single wide char soft-wrapped unwrapped" {
try s.nextSlice("1A⚡");
// Verify we have only a single page
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -1677,7 +1677,7 @@ test "Page plain multiline" {
try s.nextSlice("hello\r\nworld");
// Verify we have only a single page
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -1728,7 +1728,7 @@ test "Page plain multiline rectangle" {
try s.nextSlice("hello\r\nworld");
// Verify we have only a single page
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -1782,7 +1782,7 @@ test "Page plain multi blank lines" {
try s.nextSlice("hello\r\n\r\n\r\nworld");
// Verify we have only a single page
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -1835,7 +1835,7 @@ test "Page plain trailing blank lines" {
try s.nextSlice("hello\r\nworld\r\n\r\n");
// Verify we have only a single page
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -1888,7 +1888,7 @@ test "Page plain trailing whitespace" {
try s.nextSlice("hello \r\nworld ");
// Verify we have only a single page
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -1941,7 +1941,7 @@ test "Page plain trailing whitespace no trim" {
try s.nextSlice("hello \r\nworld ");
// Verify we have only a single page
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -1996,7 +1996,7 @@ test "Page plain with prior trailing state rows" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -2042,7 +2042,7 @@ test "Page plain with prior trailing state cells no wrapped line" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -2087,7 +2087,7 @@ test "Page plain with prior trailing state cells with wrap continuation" {
try s.nextSlice("world");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -2141,7 +2141,7 @@ test "Page plain soft-wrapped without unwrap" {
try s.nextSlice("hello world test");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -2190,7 +2190,7 @@ test "Page plain soft-wrapped with unwrap" {
try s.nextSlice("hello world test");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -2238,7 +2238,7 @@ test "Page plain soft-wrapped 3 lines without unwrap" {
try s.nextSlice("hello world this is a test");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -2292,7 +2292,7 @@ test "Page plain soft-wrapped 3 lines with unwrap" {
try s.nextSlice("hello world this is a test");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -2344,7 +2344,7 @@ test "Page plain start_y subset" {
try s.nextSlice("hello\r\nworld\r\ntest");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2391,7 +2391,7 @@ test "Page plain end_y subset" {
try s.nextSlice("hello\r\nworld\r\ntest");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2438,7 +2438,7 @@ test "Page plain start_y and end_y range" {
try s.nextSlice("hello\r\nworld\r\ntest\r\nfoo");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2486,7 +2486,7 @@ test "Page plain start_y out of bounds" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2524,7 +2524,7 @@ test "Page plain end_y greater than rows" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2567,7 +2567,7 @@ test "Page plain end_y less than start_y" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2606,7 +2606,7 @@ test "Page plain start_x on first row only" {
try s.nextSlice("hello world");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2648,7 +2648,7 @@ test "Page plain end_x on last row only" {
try s.nextSlice("first line\r\nsecond line\r\nthird line");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2701,7 +2701,7 @@ test "Page plain start_x and end_x multiline" {
try s.nextSlice("hello world\r\ntest case\r\nfoo bar");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2758,7 +2758,7 @@ test "Page plain start_x out of bounds" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2796,7 +2796,7 @@ test "Page plain end_x greater than cols" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2838,7 +2838,7 @@ test "Page plain end_x less than start_x single row" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2878,7 +2878,7 @@ test "Page plain start_y non-zero ignores trailing state" {
try s.nextSlice("hello\r\nworld");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2922,7 +2922,7 @@ test "Page plain start_x non-zero ignores trailing state" {
try s.nextSlice("hello world");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -2966,7 +2966,7 @@ test "Page plain start_y and start_x zero uses trailing state" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .plain);
@ -3014,7 +3014,7 @@ test "Page plain single line with styling" {
try s.nextSlice("hello, \x1b[1mworld\x1b[0m");
// Verify we have only a single page
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
try testing.expect(pages.pages.first != null);
try testing.expect(pages.pages.first == pages.pages.last);
@ -3059,7 +3059,7 @@ test "Page VT single line plain text" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .vt);
@ -3098,7 +3098,7 @@ test "Page VT single line with bold" {
try s.nextSlice("\x1b[1mhello\x1b[0m");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .vt);
@ -3144,7 +3144,7 @@ test "Page VT multiple styles" {
try s.nextSlice("\x1b[1mhello \x1b[3mworld\x1b[0m");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .vt);
@ -3179,7 +3179,7 @@ test "Page VT with foreground color" {
try s.nextSlice("\x1b[31mred\x1b[0m");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .vt);
@ -3225,7 +3225,7 @@ test "Page VT with background and foreground colors" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .{
@ -3262,7 +3262,7 @@ test "Page VT multi-line with styles" {
try s.nextSlice("\x1b[1mfirst\x1b[0m\r\n\x1b[3msecond\x1b[0m");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .vt);
@ -3297,7 +3297,7 @@ test "Page VT duplicate style not emitted twice" {
try s.nextSlice("\x1b[1mhel\x1b[1mlo\x1b[0m");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .vt);
@ -3335,7 +3335,7 @@ test "PageList plain single line" {
var pin_map: std.ArrayList(Pin) = .empty;
defer pin_map.deinit(alloc);
var formatter: PageListFormatter = .init(&t.screen.pages, .plain);
var formatter: PageListFormatter = .init(&t.screens.active.pages, .plain);
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
try formatter.format(&builder.writer);
const output = builder.writer.buffered();
@ -3343,7 +3343,7 @@ test "PageList plain single line" {
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
for (0..output.len) |i| try testing.expectEqual(
Pin{ .node = node, .x = @intCast(i), .y = 0 },
pin_map.items[i],
@ -3366,7 +3366,7 @@ test "PageList plain spanning two pages" {
var s = t.vtStream();
defer s.deinit();
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const first_page_rows = pages.pages.first.?.data.capacity.rows;
// Fill the first page almost completely
@ -3439,7 +3439,7 @@ test "PageList soft-wrapped line spanning two pages without unwrap" {
var s = t.vtStream();
defer s.deinit();
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const first_page_rows = pages.pages.first.?.data.capacity.rows;
// Fill the first page with soft-wrapped content
@ -3503,7 +3503,7 @@ test "PageList soft-wrapped line spanning two pages with unwrap" {
var s = t.vtStream();
defer s.deinit();
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const first_page_rows = pages.pages.first.?.data.capacity.rows;
// Fill the first page with soft-wrapped content
@ -3564,7 +3564,7 @@ test "PageList VT spanning two pages" {
var s = t.vtStream();
defer s.deinit();
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const first_page_rows = pages.pages.first.?.data.capacity.rows;
// Fill the first page almost completely
@ -3626,7 +3626,7 @@ test "PageList plain with x offset on single page" {
try s.nextSlice("hello world\r\ntest case\r\nfoo bar");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const node = pages.pages.first.?;
var pin_map: std.ArrayList(Pin) = .empty;
@ -3670,7 +3670,7 @@ test "PageList plain with x offset spanning two pages" {
var s = t.vtStream();
defer s.deinit();
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const first_page_rows = pages.pages.first.?.data.capacity.rows;
// Fill first page almost completely
@ -3742,7 +3742,7 @@ test "PageList plain with start_x only" {
try s.nextSlice("hello world");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const node = pages.pages.first.?;
var pin_map: std.ArrayList(Pin) = .empty;
@ -3783,7 +3783,7 @@ test "PageList plain with end_x only" {
try s.nextSlice("hello world\r\ntest");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const node = pages.pages.first.?;
var pin_map: std.ArrayList(Pin) = .empty;
@ -3840,7 +3840,7 @@ test "PageList plain rectangle basic" {
try s.nextSlice("eiusmod tempor incididunt\r\n");
try s.nextSlice("ut labore et dolore");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
var formatter: PageListFormatter = .init(pages, .plain);
formatter.top_left = pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?;
@ -3880,7 +3880,7 @@ test "PageList plain rectangle with EOL" {
try s.nextSlice("eiusmod tempor incididunt\r\n");
try s.nextSlice("ut labore et dolore");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
var formatter: PageListFormatter = .init(pages, .plain);
formatter.top_left = pages.pin(.{ .screen = .{ .x = 12, .y = 0 } }).?;
@ -3925,7 +3925,7 @@ test "PageList plain rectangle more complex with breaks" {
try s.nextSlice("magna aliqua. Ut enim\r\n");
try s.nextSlice("ad minim veniam, quis");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
var formatter: PageListFormatter = .init(pages, .plain);
formatter.top_left = pages.pin(.{ .screen = .{ .x = 11, .y = 2 } }).?;
@ -4035,8 +4035,8 @@ test "TerminalFormatter with selection" {
var formatter: TerminalFormatter = .init(&t, .plain);
formatter.content = .{ .selection = .init(
t.screen.pages.pin(.{ .active = .{ .x = 0, .y = 1 } }).?,
t.screen.pages.pin(.{ .active = .{ .x = 4, .y = 1 } }).?,
t.screens.active.pages.pin(.{ .active = .{ .x = 0, .y = 1 } }).?,
t.screens.active.pages.pin(.{ .active = .{ .x = 4, .y = 1 } }).?,
false,
) };
@ -4074,7 +4074,7 @@ test "TerminalFormatter plain with pin_map" {
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
for (0..output.len) |i| try testing.expectEqual(
Pin{ .node = node, .x = @intCast(i), .y = 0 },
pin_map.items[i],
@ -4111,7 +4111,7 @@ test "TerminalFormatter plain multiline with pin_map" {
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
// "hello" (5 chars)
for (0..5) |i| {
try testing.expectEqual(node, pin_map.items[i].node);
@ -4160,7 +4160,7 @@ test "TerminalFormatter vt with palette and pin_map" {
// Verify pin map - palette bytes should be mapped to top left
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
for (0..output.len) |i| {
try testing.expectEqual(node, pin_map.items[i].node);
}
@ -4189,8 +4189,8 @@ test "TerminalFormatter with selection and pin_map" {
var formatter: TerminalFormatter = .init(&t, .plain);
formatter.content = .{ .selection = .init(
t.screen.pages.pin(.{ .active = .{ .x = 0, .y = 1 } }).?,
t.screen.pages.pin(.{ .active = .{ .x = 4, .y = 1 } }).?,
t.screens.active.pages.pin(.{ .active = .{ .x = 0, .y = 1 } }).?,
t.screens.active.pages.pin(.{ .active = .{ .x = 4, .y = 1 } }).?,
false,
) };
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4201,7 +4201,7 @@ test "TerminalFormatter with selection and pin_map" {
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
// "line2" (5 chars) from row 1
for (0..5) |i| {
try testing.expectEqual(node, pin_map.items[i].node);
@ -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.screens.active, .plain);
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
try formatter.format(&builder.writer);
@ -4240,7 +4240,7 @@ test "Screen plain single line" {
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
for (0..output.len) |i| try testing.expectEqual(
Pin{ .node = node, .x = @intCast(i), .y = 0 },
pin_map.items[i],
@ -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.screens.active, .plain);
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
try formatter.format(&builder.writer);
@ -4277,7 +4277,7 @@ test "Screen plain multiline" {
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
// "hello" (5 chars)
for (0..5) |i| {
try testing.expectEqual(node, pin_map.items[i].node);
@ -4316,10 +4316,10 @@ 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.screens.active, .plain);
formatter.content = .{ .selection = .init(
t.screen.pages.pin(.{ .active = .{ .x = 0, .y = 1 } }).?,
t.screen.pages.pin(.{ .active = .{ .x = 4, .y = 1 } }).?,
t.screens.active.pages.pin(.{ .active = .{ .x = 0, .y = 1 } }).?,
t.screens.active.pages.pin(.{ .active = .{ .x = 4, .y = 1 } }).?,
false,
) };
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4330,7 +4330,7 @@ test "Screen plain with selection" {
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
// "line2" (5 chars) from row 1
for (0..5) |i| {
try testing.expectEqual(node, pin_map.items[i].node);
@ -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.screens.active, .vt);
formatter.extra.cursor = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4381,12 +4381,12 @@ test "Screen vt with cursor position" {
try s2.nextSlice(output);
// Verify cursor positions match
try testing.expectEqual(t.screen.cursor.x, t2.screen.cursor.x);
try testing.expectEqual(t.screen.cursor.y, t2.screen.cursor.y);
try testing.expectEqual(t.screens.active.cursor.x, t2.screens.active.cursor.x);
try testing.expectEqual(t.screens.active.cursor.y, t2.screens.active.cursor.y);
// Verify pin map - the extras should be mapped to the last pin
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
const content_len = "hello\r\nworld".len;
// Content bytes map to their positions
for (0..content_len) |i| {
@ -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.screens.active, .vt);
formatter.extra.style = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4440,11 +4440,11 @@ test "Screen vt with style" {
try s2.nextSlice(output);
// Verify styles match
try testing.expect(t.screen.cursor.style.eql(t2.screen.cursor.style));
try testing.expect(t.screens.active.cursor.style.eql(t2.screens.active.cursor.style));
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
for (0..output.len) |i| {
try testing.expectEqual(node, pin_map.items[i].node);
}
@ -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.screens.active, .vt);
formatter.extra.hyperlink = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4492,19 +4492,19 @@ test "Screen vt with hyperlink" {
try s2.nextSlice(output);
// Verify hyperlinks match
const has_link1 = t.screen.cursor.hyperlink != null;
const has_link2 = t2.screen.cursor.hyperlink != null;
const has_link1 = t.screens.active.cursor.hyperlink != null;
const has_link2 = t2.screens.active.cursor.hyperlink != null;
try testing.expectEqual(has_link1, has_link2);
if (has_link1) {
const link1 = t.screen.cursor.hyperlink.?;
const link2 = t2.screen.cursor.hyperlink.?;
const link1 = t.screens.active.cursor.hyperlink.?;
const link2 = t2.screens.active.cursor.hyperlink.?;
try testing.expectEqualStrings(link1.uri, link2.uri);
}
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
for (0..output.len) |i| {
try testing.expectEqual(node, pin_map.items[i].node);
}
@ -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.screens.active, .vt);
formatter.extra.protection = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4552,11 +4552,11 @@ test "Screen vt with protection" {
try s2.nextSlice(output);
// Verify protection state matches
try testing.expectEqual(t.screen.cursor.protected, t2.screen.cursor.protected);
try testing.expectEqual(t.screens.active.cursor.protected, t2.screens.active.cursor.protected);
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
for (0..output.len) |i| {
try testing.expectEqual(node, pin_map.items[i].node);
}
@ -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.screens.active, .vt);
formatter.extra.kitty_keyboard = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4604,13 +4604,13 @@ test "Screen vt with kitty keyboard" {
try s2.nextSlice(output);
// Verify kitty keyboard state matches
const flags1 = t.screen.kitty_keyboard.current().int();
const flags2 = t2.screen.kitty_keyboard.current().int();
const flags1 = t.screens.active.kitty_keyboard.current().int();
const flags2 = t2.screens.active.kitty_keyboard.current().int();
try testing.expectEqual(flags1, flags2);
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
for (0..output.len) |i| {
try testing.expectEqual(node, pin_map.items[i].node);
}
@ -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.screens.active, .vt);
formatter.extra.charsets = true;
formatter.pin_map = .{ .alloc = alloc, .map = &pin_map };
@ -4658,16 +4658,16 @@ test "Screen vt with charsets" {
try s2.nextSlice(output);
// Verify charset state matches
try testing.expectEqual(t.screen.charset.gl, t2.screen.charset.gl);
try testing.expectEqual(t.screen.charset.gr, t2.screen.charset.gr);
try testing.expectEqual(t.screens.active.charset.gl, t2.screens.active.charset.gl);
try testing.expectEqual(t.screens.active.charset.gr, t2.screens.active.charset.gr);
try testing.expectEqual(
t.screen.charset.charsets.get(.G0),
t2.screen.charset.charsets.get(.G0),
t.screens.active.charset.charsets.get(.G0),
t2.screens.active.charset.charsets.get(.G0),
);
// Verify pin map
try testing.expectEqual(output.len, pin_map.items.len);
const node = t.screen.pages.pages.first.?;
const node = t.screens.active.pages.pages.first.?;
for (0..output.len) |i| {
try testing.expectEqual(node, pin_map.items[i].node);
}
@ -4917,7 +4917,7 @@ test "Page html with multiple styles" {
// Set bold, then italic, then reset
try s.nextSlice("\x1b[1mbold\x1b[3mitalic\x1b[0mnormal");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .{ .emit = .html });
@ -4952,7 +4952,7 @@ test "Page html plain text" {
try s.nextSlice("hello, world");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .{ .emit = .html });
@ -4985,7 +4985,7 @@ test "Page html with colors" {
// Set red foreground, blue background
try s.nextSlice("\x1b[31;44mcolored");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .{ .emit = .html });
@ -5055,7 +5055,7 @@ test "Page html with background and foreground colors" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .{
.emit = .html,
@ -5090,7 +5090,7 @@ test "Page html with escaping" {
try s.nextSlice("<tag>&\"'text");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .{ .emit = .html });
@ -5161,7 +5161,7 @@ test "Page html with unicode as numeric entities" {
// Box drawing characters that caused issue #9426
try s.nextSlice("╰─ ");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .{ .emit = .html });
@ -5194,7 +5194,7 @@ test "Page html ascii characters unchanged" {
try s.nextSlice("hello world");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .{ .emit = .html });
@ -5226,7 +5226,7 @@ test "Page html mixed ascii and unicode" {
try s.nextSlice("test ╰─❯ ok");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
var formatter: PageFormatter = .init(page, .{ .emit = .html });
@ -5260,7 +5260,7 @@ test "Page VT with palette option emits RGB" {
try s.nextSlice("\x1b]4;1;rgb:ab/cd/ef\x1b\\");
try s.nextSlice("\x1b[31mred");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
// Without palette option - should emit palette index
@ -5304,7 +5304,7 @@ test "Page html with palette option emits RGB" {
try s.nextSlice("\x1b]4;1;rgb:ab/cd/ef\x1b\\");
try s.nextSlice("\x1b[31mred");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
// Without palette option - should emit CSS variable
@ -5357,7 +5357,7 @@ test "Page VT style reset properly closes styles" {
// Set bold, then reset with SGR 0
try s.nextSlice("\x1b[1mbold\x1b[0mnormal");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
builder.clearRetainingCapacity();
@ -5387,7 +5387,7 @@ test "Page codepoint_map single replacement" {
try s.nextSlice("hello world");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
// Replace 'o' with 'x'
@ -5446,7 +5446,7 @@ test "Page codepoint_map conflicting replacement prefers last" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
// Replace 'o' with 'x', then with 'y' - should prefer last
@ -5488,7 +5488,7 @@ test "Page codepoint_map replace with string" {
try s.nextSlice("hello");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
// Replace 'o' with a multi-byte string
@ -5544,7 +5544,7 @@ test "Page codepoint_map range replacement" {
try s.nextSlice("abcdefg");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
// Replace 'b' through 'e' with 'X'
@ -5582,7 +5582,7 @@ test "Page codepoint_map multiple ranges" {
try s.nextSlice("hello world");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
// Replace 'a'-'m' with 'A' and 'n'-'z' with 'Z'
@ -5626,7 +5626,7 @@ test "Page codepoint_map unicode replacement" {
try s.nextSlice("hello ⚡ world");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
// Replace lightning bolt with fire emoji
@ -5691,7 +5691,7 @@ test "Page codepoint_map with styled formats" {
try s.nextSlice("\x1b[31mred text\x1b[0m");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
// Replace 'e' with 'X' in styled text
@ -5732,7 +5732,7 @@ test "Page codepoint_map empty map" {
try s.nextSlice("hello world");
const pages = &t.screen.pages;
const pages = &t.screens.active.pages;
const page = &pages.pages.last.?.data;
// Empty map should not change anything

View File

@ -30,7 +30,7 @@ pub fn execute(
// If storage is disabled then we disable the full protocol. This means
// we don't even respond to queries so the terminal completely acts as
// if this feature is not supported.
if (!terminal.screen.kitty_images.enabled()) {
if (!terminal.screens.active.kitty_images.enabled()) {
log.debug("kitty graphics requested but disabled", .{});
return null;
}
@ -55,7 +55,7 @@ pub fn execute(
// The `q` setting inherits the value from the starting command
// unless `q` is set >= 1 on this command. If it is, then we save
// that as the new `q` setting.
const storage = &terminal.screen.kitty_images;
const storage = &terminal.screens.active.kitty_images;
if (storage.loading) |loading| switch (cmd.quiet) {
// q=0 we use whatever the start command value is
.no => quiet = loading.quiet,
@ -196,7 +196,7 @@ fn display(
};
// Verify the requested image exists if we have an ID
const storage = &terminal.screen.kitty_images;
const storage = &terminal.screens.active.kitty_images;
const img_: ?Image = if (d.image_id != 0)
storage.imageById(d.image_id)
else
@ -223,8 +223,8 @@ fn display(
// Track a new pin for our cursor. The cursor is always tracked but we
// don't want this one to move with the cursor.
const pin = terminal.screen.pages.trackPin(
terminal.screen.cursor.page_pin.*,
const pin = terminal.screens.active.pages.trackPin(
terminal.screens.active.cursor.page_pin.*,
) catch |err| {
log.warn("failed to create pin for Kitty graphics err={}", .{err});
result.message = "EINVAL: failed to prepare terminal state";
@ -252,7 +252,7 @@ fn display(
result.placement_id,
p,
) catch |err| {
p.deinit(&terminal.screen);
p.deinit(terminal.screens.active);
encodeError(&result, err);
return result;
};
@ -271,7 +271,7 @@ fn display(
};
terminal.setCursorPos(
terminal.screen.cursor.y,
terminal.screens.active.cursor.y,
pin.x + size.cols + 1,
);
},
@ -287,7 +287,7 @@ fn delete(
terminal: *Terminal,
cmd: *const Command,
) Response {
const storage = &terminal.screen.kitty_images;
const storage = &terminal.screens.active.kitty_images;
storage.delete(alloc, terminal, cmd.control.delete);
// Delete never responds on success
@ -304,7 +304,7 @@ fn loadAndAddImage(
display: ?command.Display = null,
} {
const t = cmd.transmission().?;
const storage = &terminal.screen.kitty_images;
const storage = &terminal.screens.active.kitty_images;
// Determine our image. This also handles chunking and early exit.
var loading: LoadingImage = if (storage.loading) |loading| loading: {
@ -496,7 +496,7 @@ test "kittygfx default format is rgba" {
const resp = execute(alloc, &t, &cmd).?;
try testing.expect(resp.ok());
const storage = &t.screen.kitty_images;
const storage = &t.screens.active.kitty_images;
const img = storage.imageById(1).?;
try testing.expectEqual(command.Transmission.Format.rgba, img.format);
}

View File

@ -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.screens.active);
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.screens.active,
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.screens.active,
img.id,
v.placement_id,
v.delete,
@ -269,8 +269,8 @@ pub const ImageStorage = struct {
alloc,
t,
.{ .active = .{
.x = t.screen.cursor.x,
.y = t.screen.cursor.y,
.x = t.screens.active.cursor.x,
.y = t.screens.active.cursor.y,
} },
delete_images,
{},
@ -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.screens.active);
self.placements.removeByPtr(entry.key_ptr);
if (v.delete) self.deleteIfUnused(alloc, img.id);
}
@ -350,7 +350,7 @@ pub const ImageStorage = struct {
// v.y is in active coords so we want to convert it to a pin
// so we can compare by page offsets.
const target_pin = t.screen.pages.pin(.{ .active = .{
const target_pin = t.screens.active.pages.pin(.{ .active = .{
.y = std.math.cast(size.CellCountInt, v.y - 1) orelse break :row,
} }) orelse break :row;
@ -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.screens.active);
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.screens.active);
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.screens.active);
self.placements.removeByPtr(entry.key_ptr);
if (v.delete) self.deleteIfUnused(alloc, image_id);
}
@ -490,7 +490,7 @@ pub const ImageStorage = struct {
comptime filter: ?fn (@TypeOf(filter_ctx), Placement) bool,
) void {
// Convert our target point to a pin for comparison.
const target_pin = t.screen.pages.pin(p) orelse return;
const target_pin = t.screens.active.pages.pin(p) orelse return;
var it = self.placements.iterator();
while (it.next()) |entry| {
@ -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.screens.active);
self.placements.removeByPtr(entry.key_ptr);
if (delete_unused) self.deleteIfUnused(alloc, img.id);
}
@ -811,7 +811,7 @@ fn trackPin(
t: *terminal.Terminal,
pt: point.Coordinate,
) !*PageList.Pin {
return try t.screen.pages.trackPin(t.screen.pages.pin(.{
return try t.screens.active.pages.trackPin(t.screens.active.pages.pin(.{
.active = pt,
}).?);
}
@ -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.screens.active);
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 }) } });
@ -850,10 +850,10 @@ test "storage: delete all placements and images" {
const alloc = testing.allocator;
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
defer t.deinit(alloc);
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
try s.addImage(alloc, .{ .id = 1 });
try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 });
@ -865,7 +865,7 @@ test "storage: delete all placements and images" {
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 0), s.images.count());
try testing.expectEqual(@as(usize, 0), s.placements.count());
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked, t.screens.active.pages.countTrackedPins());
}
test "storage: delete all placements and images preserves limit" {
@ -873,10 +873,10 @@ test "storage: delete all placements and images preserves limit" {
const alloc = testing.allocator;
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
defer t.deinit(alloc);
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
s.total_limit = 5000;
try s.addImage(alloc, .{ .id = 1 });
try s.addImage(alloc, .{ .id = 2 });
@ -890,7 +890,7 @@ test "storage: delete all placements and images preserves limit" {
try testing.expectEqual(@as(usize, 0), s.images.count());
try testing.expectEqual(@as(usize, 0), s.placements.count());
try testing.expectEqual(@as(usize, 5000), s.total_limit);
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked, t.screens.active.pages.countTrackedPins());
}
test "storage: delete all placements" {
@ -898,10 +898,10 @@ test "storage: delete all placements" {
const alloc = testing.allocator;
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
defer t.deinit(alloc);
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
try s.addImage(alloc, .{ .id = 1 });
try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 });
@ -913,7 +913,7 @@ test "storage: delete all placements" {
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 0), s.placements.count());
try testing.expectEqual(@as(usize, 3), s.images.count());
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked, t.screens.active.pages.countTrackedPins());
}
test "storage: delete all placements by image id" {
@ -921,10 +921,10 @@ test "storage: delete all placements by image id" {
const alloc = testing.allocator;
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
defer t.deinit(alloc);
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
try s.addImage(alloc, .{ .id = 1 });
try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 });
@ -936,7 +936,7 @@ test "storage: delete all placements by image id" {
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 1), s.placements.count());
try testing.expectEqual(@as(usize, 3), s.images.count());
try testing.expectEqual(tracked + 1, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked + 1, t.screens.active.pages.countTrackedPins());
}
test "storage: delete all placements by image id and unused images" {
@ -944,10 +944,10 @@ test "storage: delete all placements by image id and unused images" {
const alloc = testing.allocator;
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
defer t.deinit(alloc);
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
try s.addImage(alloc, .{ .id = 1 });
try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 });
@ -959,7 +959,7 @@ test "storage: delete all placements by image id and unused images" {
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 1), s.placements.count());
try testing.expectEqual(@as(usize, 2), s.images.count());
try testing.expectEqual(tracked + 1, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked + 1, t.screens.active.pages.countTrackedPins());
}
test "storage: delete placement by specific id" {
@ -967,10 +967,10 @@ test "storage: delete placement by specific id" {
const alloc = testing.allocator;
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
defer t.deinit(alloc);
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
try s.addImage(alloc, .{ .id = 1 });
try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 });
@ -987,7 +987,7 @@ test "storage: delete placement by specific id" {
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 2), s.placements.count());
try testing.expectEqual(@as(usize, 3), s.images.count());
try testing.expectEqual(tracked + 2, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked + 2, t.screens.active.pages.countTrackedPins());
}
test "storage: delete intersecting cursor" {
@ -997,23 +997,23 @@ test "storage: delete intersecting cursor" {
defer t.deinit(alloc);
t.width_px = 100;
t.height_px = 100;
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
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 }) } });
try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } });
t.screen.cursorAbsolute(12, 12);
t.screens.active.cursorAbsolute(12, 12);
s.dirty = false;
s.delete(alloc, &t, .{ .intersect_cursor = false });
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 1), s.placements.count());
try testing.expectEqual(@as(usize, 2), s.images.count());
try testing.expectEqual(tracked + 1, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked + 1, t.screens.active.pages.countTrackedPins());
// verify the placement is what we expect
try testing.expect(s.placements.get(.{
@ -1029,23 +1029,23 @@ test "storage: delete intersecting cursor plus unused" {
defer t.deinit(alloc);
t.width_px = 100;
t.height_px = 100;
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
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 }) } });
try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } });
t.screen.cursorAbsolute(12, 12);
t.screens.active.cursorAbsolute(12, 12);
s.dirty = false;
s.delete(alloc, &t, .{ .intersect_cursor = true });
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 1), s.placements.count());
try testing.expectEqual(@as(usize, 2), s.images.count());
try testing.expectEqual(tracked + 1, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked + 1, t.screens.active.pages.countTrackedPins());
// verify the placement is what we expect
try testing.expect(s.placements.get(.{
@ -1061,23 +1061,23 @@ test "storage: delete intersecting cursor hits multiple" {
defer t.deinit(alloc);
t.width_px = 100;
t.height_px = 100;
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
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 }) } });
try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } });
t.screen.cursorAbsolute(26, 26);
t.screens.active.cursorAbsolute(26, 26);
s.dirty = false;
s.delete(alloc, &t, .{ .intersect_cursor = true });
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 0), s.placements.count());
try testing.expectEqual(@as(usize, 1), s.images.count());
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked, t.screens.active.pages.countTrackedPins());
}
test "storage: delete by column" {
@ -1087,10 +1087,10 @@ test "storage: delete by column" {
defer t.deinit(alloc);
t.width_px = 100;
t.height_px = 100;
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
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 }) } });
@ -1104,7 +1104,7 @@ test "storage: delete by column" {
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 1), s.placements.count());
try testing.expectEqual(@as(usize, 2), s.images.count());
try testing.expectEqual(tracked + 1, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked + 1, t.screens.active.pages.countTrackedPins());
// verify the placement is what we expect
try testing.expect(s.placements.get(.{
@ -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.screens.active);
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 }) } });
@ -1153,10 +1153,10 @@ test "storage: delete by row" {
defer t.deinit(alloc);
t.width_px = 100;
t.height_px = 100;
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
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 }) } });
@ -1170,7 +1170,7 @@ test "storage: delete by row" {
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 1), s.placements.count());
try testing.expectEqual(@as(usize, 2), s.images.count());
try testing.expectEqual(tracked + 1, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked + 1, t.screens.active.pages.countTrackedPins());
// verify the placement is what we expect
try testing.expect(s.placements.get(.{
@ -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.screens.active);
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 }) } });
@ -1217,10 +1217,10 @@ test "storage: delete images by range 1" {
const alloc = testing.allocator;
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
defer t.deinit(alloc);
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
try s.addImage(alloc, .{ .id = 1 });
try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 });
@ -1234,7 +1234,7 @@ test "storage: delete images by range 1" {
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 3), s.images.count());
try testing.expectEqual(@as(usize, 0), s.placements.count());
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked, t.screens.active.pages.countTrackedPins());
}
test "storage: delete images by range 2" {
@ -1242,10 +1242,10 @@ test "storage: delete images by range 2" {
const alloc = testing.allocator;
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
defer t.deinit(alloc);
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
try s.addImage(alloc, .{ .id = 1 });
try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 });
@ -1259,7 +1259,7 @@ test "storage: delete images by range 2" {
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 1), s.images.count());
try testing.expectEqual(@as(usize, 0), s.placements.count());
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked, t.screens.active.pages.countTrackedPins());
}
test "storage: delete images by range 3" {
@ -1267,10 +1267,10 @@ test "storage: delete images by range 3" {
const alloc = testing.allocator;
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
defer t.deinit(alloc);
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
try s.addImage(alloc, .{ .id = 1 });
try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 });
@ -1284,7 +1284,7 @@ test "storage: delete images by range 3" {
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 3), s.images.count());
try testing.expectEqual(@as(usize, 0), s.placements.count());
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked, t.screens.active.pages.countTrackedPins());
}
test "storage: delete images by range 4" {
@ -1292,10 +1292,10 @@ test "storage: delete images by range 4" {
const alloc = testing.allocator;
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
defer t.deinit(alloc);
const tracked = t.screen.pages.countTrackedPins();
const tracked = t.screens.active.pages.countTrackedPins();
var s: ImageStorage = .{};
defer s.deinit(alloc, &t.screen);
defer s.deinit(alloc, t.screens.active);
try s.addImage(alloc, .{ .id = 1 });
try s.addImage(alloc, .{ .id = 2 });
try s.addImage(alloc, .{ .id = 3 });
@ -1309,7 +1309,7 @@ test "storage: delete images by range 4" {
try testing.expect(s.dirty);
try testing.expectEqual(@as(usize, 1), s.images.count());
try testing.expectEqual(@as(usize, 0), s.placements.count());
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
try testing.expectEqual(tracked, t.screens.active.pages.countTrackedPins());
}
test "storage: aspect ratio calculation when only columns or rows specified" {

View File

@ -893,7 +893,7 @@ test "unicode placement: none" {
try t.printString("hello\nworld\n1\n2");
// No placements
const pin = t.screen.pages.getTopLeft(.viewport);
const pin = t.screens.active.pages.getTopLeft(.viewport);
var it = placementIterator(pin, null);
try testing.expect(it.next() == null);
}
@ -908,7 +908,7 @@ test "unicode placement: single row/col" {
try t.printString("\u{10EEEE}\u{0305}\u{0305}");
// Get our top left pin
const pin = t.screen.pages.getTopLeft(.viewport);
const pin = t.screens.active.pages.getTopLeft(.viewport);
// Should have exactly one placement
var it = placementIterator(pin, null);
@ -933,7 +933,7 @@ test "unicode placement: continuation break" {
try t.printString("\u{10EEEE}\u{0305}\u{030E}");
// Get our top left pin
const pin = t.screen.pages.getTopLeft(.viewport);
const pin = t.screens.active.pages.getTopLeft(.viewport);
// Should have exactly one placement
var it = placementIterator(pin, null);
@ -968,7 +968,7 @@ test "unicode placement: continuation with diacritics set" {
try t.printString("\u{10EEEE}\u{0305}\u{030E}");
// Get our top left pin
const pin = t.screen.pages.getTopLeft(.viewport);
const pin = t.screens.active.pages.getTopLeft(.viewport);
// Should have exactly one placement
var it = placementIterator(pin, null);
@ -995,7 +995,7 @@ test "unicode placement: continuation with no col" {
try t.printString("\u{10EEEE}\u{0305}");
// Get our top left pin
const pin = t.screen.pages.getTopLeft(.viewport);
const pin = t.screens.active.pages.getTopLeft(.viewport);
// Should have exactly one placement
var it = placementIterator(pin, null);
@ -1022,7 +1022,7 @@ test "unicode placement: continuation with no diacritics" {
try t.printString("\u{10EEEE}");
// Get our top left pin
const pin = t.screen.pages.getTopLeft(.viewport);
const pin = t.screens.active.pages.getTopLeft(.viewport);
// Should have exactly one placement
var it = placementIterator(pin, null);
@ -1049,7 +1049,7 @@ test "unicode placement: run ending" {
try t.printString("ABC");
// Get our top left pin
const pin = t.screen.pages.getTopLeft(.viewport);
const pin = t.screens.active.pages.getTopLeft(.viewport);
// Should have exactly one placement
var it = placementIterator(pin, null);
@ -1076,7 +1076,7 @@ test "unicode placement: run starting in the middle" {
try t.printString("\u{10EEEE}\u{0305}\u{030D}");
// Get our top left pin
const pin = t.screen.pages.getTopLeft(.viewport);
const pin = t.screens.active.pages.getTopLeft(.viewport);
// Should have exactly one placement
var it = placementIterator(pin, null);
@ -1102,7 +1102,7 @@ test "unicode placement: specifying image id as palette" {
try t.printString("\u{10EEEE}\u{0305}\u{0305}");
// Get our top left pin
const pin = t.screen.pages.getTopLeft(.viewport);
const pin = t.screens.active.pages.getTopLeft(.viewport);
// Should have exactly one placement
var it = placementIterator(pin, null);
@ -1127,7 +1127,7 @@ test "unicode placement: specifying image id with high bits" {
try t.printString("\u{10EEEE}\u{0305}\u{0305}\u{030E}");
// Get our top left pin
const pin = t.screen.pages.getTopLeft(.viewport);
const pin = t.screens.active.pages.getTopLeft(.viewport);
// Should have exactly one placement
var it = placementIterator(pin, null);
@ -1153,7 +1153,7 @@ test "unicode placement: specifying placement id as palette" {
try t.printString("\u{10EEEE}\u{0305}\u{0305}");
// Get our top left pin
const pin = t.screen.pages.getTopLeft(.viewport);
const pin = t.screens.active.pages.getTopLeft(.viewport);
// Should have exactly one placement
var it = placementIterator(pin, null);
@ -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.screens.active);
const image: Image = .{ .id = 1, .width = 500, .height = 306 };
try s.addImage(alloc, image);
@ -1193,7 +1193,7 @@ test "unicode render placement: dog 4x2" {
// Row 1
{
const p: Placement = .{
.pin = t.screen.cursor.page_pin.*,
.pin = t.screens.active.cursor.page_pin.*,
.image_id = 1,
.placement_id = 0,
.col = 0,
@ -1214,7 +1214,7 @@ test "unicode render placement: dog 4x2" {
// Row 2
{
const p: Placement = .{
.pin = t.screen.cursor.page_pin.*,
.pin = t.screens.active.cursor.page_pin.*,
.image_id = 1,
.placement_id = 0,
.col = 0,
@ -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.screens.active);
const image: Image = .{ .id = 1, .width = 500, .height = 306 };
try s.addImage(alloc, image);
@ -1260,7 +1260,7 @@ test "unicode render placement: dog 2x2 with blank cells" {
// Row 1
{
const p: Placement = .{
.pin = t.screen.cursor.page_pin.*,
.pin = t.screens.active.cursor.page_pin.*,
.image_id = 1,
.placement_id = 0,
.col = 0,
@ -1281,7 +1281,7 @@ test "unicode render placement: dog 2x2 with blank cells" {
// Row 2
{
const p: Placement = .{
.pin = t.screen.cursor.page_pin.*,
.pin = t.screens.active.cursor.page_pin.*,
.image_id = 1,
.placement_id = 0,
.col = 0,
@ -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.screens.active);
const image: Image = .{ .id = 1, .width = 500, .height = 306 };
try s.addImage(alloc, image);
@ -1326,7 +1326,7 @@ test "unicode render placement: dog 1x1" {
// Row 1
{
const p: Placement = .{
.pin = t.screen.cursor.page_pin.*,
.pin = t.screens.active.cursor.page_pin.*,
.image_id = 1,
.placement_id = 0,
.col = 0,

View File

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

View File

@ -112,29 +112,29 @@ test "simple search" {
var search: ActiveSearch = try .init(alloc, "Fizz");
defer search.deinit();
_ = try search.update(&t.screen.pages);
_ = try search.update(&t.screens.active.pages);
{
const sel = search.next().?;
try testing.expectEqual(point.Point{ .active = .{
.x = 0,
.y = 0,
} }, t.screen.pages.pointFromPin(.active, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.start()).?);
try testing.expectEqual(point.Point{ .active = .{
.x = 3,
.y = 0,
} }, t.screen.pages.pointFromPin(.active, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.end()).?);
}
{
const sel = search.next().?;
try testing.expectEqual(point.Point{ .active = .{
.x = 0,
.y = 2,
} }, t.screen.pages.pointFromPin(.active, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.start()).?);
try testing.expectEqual(point.Point{ .active = .{
.x = 3,
.y = 2,
} }, t.screen.pages.pointFromPin(.active, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.end()).?);
}
try testing.expect(search.next() == null);
}
@ -150,23 +150,23 @@ test "clear screen and search" {
var search: ActiveSearch = try .init(alloc, "Fizz");
defer search.deinit();
_ = try search.update(&t.screen.pages);
_ = try search.update(&t.screens.active.pages);
try s.nextSlice("\x1b[2J"); // Clear screen
try s.nextSlice("\x1b[H"); // Move cursor home
try s.nextSlice("Buzz\r\nFizz\r\nBuzz");
_ = try search.update(&t.screen.pages);
_ = try search.update(&t.screens.active.pages);
{
const sel = search.next().?;
try testing.expectEqual(point.Point{ .active = .{
.x = 0,
.y = 1,
} }, t.screen.pages.pointFromPin(.active, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.start()).?);
try testing.expectEqual(point.Point{ .active = .{
.x = 3,
.y = 1,
} }, t.screen.pages.pointFromPin(.active, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.end()).?);
}
try testing.expect(search.next() == null);
}

View File

@ -143,8 +143,8 @@ test "simple search" {
var search: PageListSearch = try .init(
alloc,
"Fizz",
&t.screen.pages,
t.screen.pages.pages.last.?,
&t.screens.active.pages,
t.screens.active.pages.pages.last.?,
);
defer search.deinit();
@ -153,22 +153,22 @@ test "simple search" {
try testing.expectEqual(point.Point{ .active = .{
.x = 0,
.y = 2,
} }, t.screen.pages.pointFromPin(.active, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.start()).?);
try testing.expectEqual(point.Point{ .active = .{
.x = 3,
.y = 2,
} }, t.screen.pages.pointFromPin(.active, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.end()).?);
}
{
const sel = search.next().?;
try testing.expectEqual(point.Point{ .active = .{
.x = 0,
.y = 0,
} }, t.screen.pages.pointFromPin(.active, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.start()).?);
try testing.expectEqual(point.Point{ .active = .{
.x = 3,
.y = 0,
} }, t.screen.pages.pointFromPin(.active, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.end()).?);
}
try testing.expect(search.next() == null);
@ -185,21 +185,21 @@ test "feed multiple pages with matches" {
defer s.deinit();
// Fill up first page
const first_page_rows = t.screen.pages.pages.first.?.data.capacity.rows;
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
for (0..first_page_rows - 1) |_| try s.nextSlice("\r\n");
try s.nextSlice("Fizz");
try testing.expect(t.screen.pages.pages.first == t.screen.pages.pages.last);
try testing.expect(t.screens.active.pages.pages.first == t.screens.active.pages.pages.last);
// Create second page
try s.nextSlice("\r\n");
try testing.expect(t.screen.pages.pages.first != t.screen.pages.pages.last);
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
try s.nextSlice("Buzz\r\nFizz");
var search: PageListSearch = try .init(
alloc,
"Fizz",
&t.screen.pages,
t.screen.pages.pages.last.?,
&t.screens.active.pages,
t.screens.active.pages.pages.last.?,
);
defer search.deinit();
@ -229,20 +229,20 @@ test "feed multiple pages no matches" {
defer s.deinit();
// Fill up first page
const first_page_rows = t.screen.pages.pages.first.?.data.capacity.rows;
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
for (0..first_page_rows - 1) |_| try s.nextSlice("\r\n");
try s.nextSlice("Hello");
// Create second page
try s.nextSlice("\r\n");
try testing.expect(t.screen.pages.pages.first != t.screen.pages.pages.last);
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
try s.nextSlice("World");
var search: PageListSearch = try .init(
alloc,
"Nope",
&t.screen.pages,
t.screen.pages.pages.last.?,
&t.screens.active.pages,
t.screens.active.pages.pages.last.?,
);
defer search.deinit();
@ -267,23 +267,23 @@ test "feed iteratively through multiple matches" {
var s = t.vtStream();
defer s.deinit();
const first_page_rows = t.screen.pages.pages.first.?.data.capacity.rows;
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
// Fill first page with a match at the end
for (0..first_page_rows - 1) |_| try s.nextSlice("\r\n");
try s.nextSlice("Page1Test");
try testing.expect(t.screen.pages.pages.first == t.screen.pages.pages.last);
try testing.expect(t.screens.active.pages.pages.first == t.screens.active.pages.pages.last);
// Create second page with a match
try s.nextSlice("\r\n");
try testing.expect(t.screen.pages.pages.first != t.screen.pages.pages.last);
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
try s.nextSlice("Page2Test");
var search: PageListSearch = try .init(
alloc,
"Test",
&t.screen.pages,
t.screen.pages.pages.last.?,
&t.screens.active.pages,
t.screens.active.pages.pages.last.?,
);
defer search.deinit();
@ -308,23 +308,23 @@ test "feed with match spanning page boundary" {
var s = t.vtStream();
defer s.deinit();
const first_page_rows = t.screen.pages.pages.first.?.data.capacity.rows;
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
// Fill first page ending with "Te"
for (0..first_page_rows - 1) |_| try s.nextSlice("\r\n");
for (0..t.screen.pages.cols - 2) |_| try s.nextSlice("x");
for (0..t.screens.active.pages.cols - 2) |_| try s.nextSlice("x");
try s.nextSlice("Te");
try testing.expect(t.screen.pages.pages.first == t.screen.pages.pages.last);
try testing.expect(t.screens.active.pages.pages.first == t.screens.active.pages.pages.last);
// Second page starts with "st"
try s.nextSlice("st");
try testing.expect(t.screen.pages.pages.first != t.screen.pages.pages.last);
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
var search: PageListSearch = try .init(
alloc,
"Test",
&t.screen.pages,
t.screen.pages.pages.last.?,
&t.screens.active.pages,
t.screens.active.pages.pages.last.?,
);
defer search.deinit();
@ -338,7 +338,7 @@ test "feed with match spanning page boundary" {
const sel = search.next().?;
try testing.expect(sel.start().node != sel.end().node);
{
const str = try t.screen.selectionString(
const str = try t.screens.active.selectionString(
alloc,
.{ .sel = sel },
);
@ -361,24 +361,24 @@ test "feed with match spanning page boundary with newline" {
var s = t.vtStream();
defer s.deinit();
const first_page_rows = t.screen.pages.pages.first.?.data.capacity.rows;
const first_page_rows = t.screens.active.pages.pages.first.?.data.capacity.rows;
// Fill first page ending with "Te"
for (0..first_page_rows - 1) |_| try s.nextSlice("\r\n");
for (0..t.screen.pages.cols - 2) |_| try s.nextSlice("x");
for (0..t.screens.active.pages.cols - 2) |_| try s.nextSlice("x");
try s.nextSlice("Te");
try testing.expect(t.screen.pages.pages.first == t.screen.pages.pages.last);
try testing.expect(t.screens.active.pages.pages.first == t.screens.active.pages.pages.last);
// Second page starts with "st"
try s.nextSlice("\r\n");
try testing.expect(t.screen.pages.pages.first != t.screen.pages.pages.last);
try testing.expect(t.screens.active.pages.pages.first != t.screens.active.pages.pages.last);
try s.nextSlice("st");
var search: PageListSearch = try .init(
alloc,
"Test",
&t.screen.pages,
t.screen.pages.pages.last.?,
&t.screens.active.pages,
t.screens.active.pages.pages.last.?,
);
defer search.deinit();

View File

@ -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.screens.active, "Fizz");
defer search.deinit();
try search.searchAll();
try testing.expectEqual(2, search.active_results.items.len);
@ -407,22 +407,22 @@ test "simple search" {
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
.y = 2,
} }, t.screen.pages.pointFromPin(.screen, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.screen, sel.start()).?);
try testing.expectEqual(point.Point{ .screen = .{
.x = 3,
.y = 2,
} }, t.screen.pages.pointFromPin(.screen, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.screen, sel.end()).?);
}
{
const sel = matches[1];
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
.y = 0,
} }, t.screen.pages.pointFromPin(.screen, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.screen, sel.start()).?);
try testing.expectEqual(point.Point{ .screen = .{
.x = 3,
.y = 0,
} }, t.screen.pages.pointFromPin(.screen, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.screen, sel.end()).?);
}
}
@ -434,7 +434,7 @@ test "simple search with history" {
.max_scrollback = std.math.maxInt(usize),
});
defer t.deinit(alloc);
const list: *PageList = &t.screen.pages;
const list: *PageList = &t.screens.active.pages;
var s = t.vtStream();
defer s.deinit();
@ -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.screens.active, "Fizz");
defer search.deinit();
try search.searchAll();
try testing.expectEqual(0, search.active_results.items.len);
@ -459,11 +459,11 @@ test "simple search with history" {
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
.y = 0,
} }, t.screen.pages.pointFromPin(.screen, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.screen, sel.start()).?);
try testing.expectEqual(point.Point{ .screen = .{
.x = 3,
.y = 0,
} }, t.screen.pages.pointFromPin(.screen, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.screen, sel.end()).?);
}
}
@ -475,14 +475,14 @@ test "reload active with history change" {
.max_scrollback = std.math.maxInt(usize),
});
defer t.deinit(alloc);
const list: *PageList = &t.screen.pages;
const list: *PageList = &t.screens.active.pages;
var s = t.vtStream();
defer s.deinit();
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.screens.active, "Fizz");
defer search.deinit();
try search.searchAll();
{
@ -510,22 +510,22 @@ test "reload active with history change" {
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
.y = 0,
} }, t.screen.pages.pointFromPin(.screen, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.screen, sel.start()).?);
try testing.expectEqual(point.Point{ .screen = .{
.x = 3,
.y = 0,
} }, t.screen.pages.pointFromPin(.screen, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.screen, sel.end()).?);
}
{
const sel = matches[0];
try testing.expectEqual(point.Point{ .active = .{
.x = 1,
.y = 1,
} }, t.screen.pages.pointFromPin(.active, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.start()).?);
try testing.expectEqual(point.Point{ .active = .{
.x = 4,
.y = 1,
} }, t.screen.pages.pointFromPin(.active, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.end()).?);
}
}
@ -544,11 +544,11 @@ test "reload active with history change" {
try testing.expectEqual(point.Point{ .active = .{
.x = 2,
.y = 0,
} }, t.screen.pages.pointFromPin(.active, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.start()).?);
try testing.expectEqual(point.Point{ .active = .{
.x = 5,
.y = 0,
} }, t.screen.pages.pointFromPin(.active, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.active, sel.end()).?);
}
}
}
@ -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.screens.active, "Fizz");
defer search.deinit();
try search.searchAll();
try testing.expectEqual(1, search.active_results.items.len);
@ -585,10 +585,10 @@ test "active change contents" {
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
.y = 1,
} }, t.screen.pages.pointFromPin(.screen, sel.start()).?);
} }, t.screens.active.pages.pointFromPin(.screen, sel.start()).?);
try testing.expectEqual(point.Point{ .screen = .{
.x = 3,
.y = 1,
} }, t.screen.pages.pointFromPin(.screen, sel.end()).?);
} }, t.screens.active.pages.pointFromPin(.screen, sel.end()).?);
}
}

View File

@ -494,7 +494,7 @@ test "SlidingWindow single append" {
var w: SlidingWindow = try .init(alloc, .forward, "boo!");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 0);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("hello. boo! hello. boo!");
@ -537,7 +537,7 @@ test "SlidingWindow single append no match" {
var w: SlidingWindow = try .init(alloc, .forward, "nope!");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 0);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("hello. boo! hello. boo!");
@ -561,7 +561,7 @@ test "SlidingWindow two pages" {
var w: SlidingWindow = try .init(alloc, .forward, "boo!");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 1000);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 });
defer s.deinit();
// Fill up the first page. The final bytes in the first page
@ -614,7 +614,7 @@ test "SlidingWindow two pages match across boundary" {
var w: SlidingWindow = try .init(alloc, .forward, "hello, world");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 1000);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 });
defer s.deinit();
// Fill up the first page. The final bytes in the first page
@ -658,7 +658,7 @@ test "SlidingWindow two pages no match across boundary with newline" {
var w: SlidingWindow = try .init(alloc, .forward, "hello, world");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 1000);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 });
defer s.deinit();
// Fill up the first page. The final bytes in the first page
@ -691,7 +691,7 @@ test "SlidingWindow two pages no match across boundary with newline reverse" {
var w: SlidingWindow = try .init(alloc, .reverse, "hello, world");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 1000);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 });
defer s.deinit();
// Fill up the first page. The final bytes in the first page
@ -721,7 +721,7 @@ test "SlidingWindow two pages no match prunes first page" {
var w: SlidingWindow = try .init(alloc, .forward, "nope!");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 1000);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 });
defer s.deinit();
// Fill up the first page. The final bytes in the first page
@ -753,7 +753,7 @@ test "SlidingWindow two pages no match keeps both pages" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try Screen.init(alloc, 80, 24, 1000);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 });
defer s.deinit();
// Fill up the first page. The final bytes in the first page
@ -796,7 +796,7 @@ test "SlidingWindow single append across circular buffer boundary" {
var w: SlidingWindow = try .init(alloc, .forward, "abc");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 0);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("XXXXXXXXXXXXXXXXXXXboo!XXXXX");
@ -851,7 +851,7 @@ test "SlidingWindow single append match on boundary" {
var w: SlidingWindow = try .init(alloc, .forward, "abcd");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 0);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("o!XXXXXXXXXXXXXXXXXXXbo");
@ -909,7 +909,7 @@ test "SlidingWindow single append reversed" {
var w: SlidingWindow = try .init(alloc, .reverse, "boo!");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 0);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("hello. boo! hello. boo!");
@ -952,7 +952,7 @@ test "SlidingWindow single append no match reversed" {
var w: SlidingWindow = try .init(alloc, .reverse, "nope!");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 0);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("hello. boo! hello. boo!");
@ -976,7 +976,7 @@ test "SlidingWindow two pages reversed" {
var w: SlidingWindow = try .init(alloc, .reverse, "boo!");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 1000);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 });
defer s.deinit();
// Fill up the first page. The final bytes in the first page
@ -1029,7 +1029,7 @@ test "SlidingWindow two pages match across boundary reversed" {
var w: SlidingWindow = try .init(alloc, .reverse, "hello, world");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 1000);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 });
defer s.deinit();
// Fill up the first page. The final bytes in the first page
@ -1074,7 +1074,7 @@ test "SlidingWindow two pages no match prunes first page reversed" {
var w: SlidingWindow = try .init(alloc, .reverse, "nope!");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 1000);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 });
defer s.deinit();
// Fill up the first page. The final bytes in the first page
@ -1106,7 +1106,7 @@ test "SlidingWindow two pages no match keeps both pages reversed" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try Screen.init(alloc, 80, 24, 1000);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 });
defer s.deinit();
// Fill up the first page. The final bytes in the first page
@ -1149,7 +1149,7 @@ test "SlidingWindow single append across circular buffer boundary reversed" {
var w: SlidingWindow = try .init(alloc, .reverse, "abc");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 0);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("XXXXXXXXXXXXXXXXXXXboo!XXXXX");
@ -1205,7 +1205,7 @@ test "SlidingWindow single append match on boundary reversed" {
var w: SlidingWindow = try .init(alloc, .reverse, "abcd");
defer w.deinit();
var s = try Screen.init(alloc, 80, 24, 0);
var s = try Screen.init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 });
defer s.deinit();
try s.testWriteString("o!XXXXXXXXXXXXXXXXXXXbo");

View File

@ -63,15 +63,15 @@ pub const Handler = struct {
.cursor_left => self.terminal.cursorLeft(value.value),
.cursor_right => self.terminal.cursorRight(value.value),
.cursor_pos => self.terminal.setCursorPos(value.row, value.col),
.cursor_col => self.terminal.setCursorPos(self.terminal.screen.cursor.y + 1, value.value),
.cursor_row => self.terminal.setCursorPos(value.value, self.terminal.screen.cursor.x + 1),
.cursor_col => self.terminal.setCursorPos(self.terminal.screens.active.cursor.y + 1, value.value),
.cursor_row => self.terminal.setCursorPos(value.value, self.terminal.screens.active.cursor.x + 1),
.cursor_col_relative => self.terminal.setCursorPos(
self.terminal.screen.cursor.y + 1,
self.terminal.screen.cursor.x + 1 +| value.value,
self.terminal.screens.active.cursor.y + 1,
self.terminal.screens.active.cursor.x + 1 +| value.value,
),
.cursor_row_relative => self.terminal.setCursorPos(
self.terminal.screen.cursor.y + 1 +| value.value,
self.terminal.screen.cursor.x + 1,
self.terminal.screens.active.cursor.y + 1 +| value.value,
self.terminal.screens.active.cursor.x + 1,
),
.cursor_style => {
const blink = switch (value) {
@ -84,7 +84,7 @@ pub const Handler = struct {
.blinking_underline, .steady_underline => .underline,
};
self.terminal.modes.set(.cursor_blinking, blink);
self.terminal.screen.cursor.cursor_style = style;
self.terminal.screens.active.cursor.cursor_style = style;
},
.erase_display_below => self.terminal.eraseDisplay(.below, value),
.erase_display_above => self.terminal.eraseDisplay(.above, value),
@ -136,11 +136,11 @@ pub const Handler = struct {
.protected_mode_iso => self.terminal.setProtectedMode(.iso),
.protected_mode_dec => self.terminal.setProtectedMode(.dec),
.mouse_shift_capture => self.terminal.flags.mouse_shift_capture = if (value) .true else .false,
.kitty_keyboard_push => self.terminal.screen.kitty_keyboard.push(value.flags),
.kitty_keyboard_pop => self.terminal.screen.kitty_keyboard.pop(@intCast(value)),
.kitty_keyboard_set => self.terminal.screen.kitty_keyboard.set(.set, value.flags),
.kitty_keyboard_set_or => self.terminal.screen.kitty_keyboard.set(.@"or", value.flags),
.kitty_keyboard_set_not => self.terminal.screen.kitty_keyboard.set(.not, value.flags),
.kitty_keyboard_push => self.terminal.screens.active.kitty_keyboard.push(value.flags),
.kitty_keyboard_pop => self.terminal.screens.active.kitty_keyboard.pop(@intCast(value)),
.kitty_keyboard_set => self.terminal.screens.active.kitty_keyboard.set(.set, value.flags),
.kitty_keyboard_set_or => self.terminal.screens.active.kitty_keyboard.set(.@"or", value.flags),
.kitty_keyboard_set_not => self.terminal.screens.active.kitty_keyboard.set(.not, value.flags),
.modify_key_format => {
self.terminal.flags.modify_other_keys_2 = false;
switch (value) {
@ -151,16 +151,16 @@ pub const Handler = struct {
.active_status_display => self.terminal.status_display = value,
.decaln => try self.terminal.decaln(),
.full_reset => self.terminal.fullReset(),
.start_hyperlink => try self.terminal.screen.startHyperlink(value.uri, value.id),
.end_hyperlink => self.terminal.screen.endHyperlink(),
.start_hyperlink => try self.terminal.screens.active.startHyperlink(value.uri, value.id),
.end_hyperlink => self.terminal.screens.active.endHyperlink(),
.prompt_start => {
self.terminal.screen.cursor.page_row.semantic_prompt = .prompt;
self.terminal.screens.active.cursor.page_row.semantic_prompt = .prompt;
self.terminal.flags.shell_redraws_prompt = value.redraw;
},
.prompt_continuation => self.terminal.screen.cursor.page_row.semantic_prompt = .prompt_continuation,
.prompt_continuation => self.terminal.screens.active.cursor.page_row.semantic_prompt = .prompt_continuation,
.prompt_end => self.terminal.markSemanticPrompt(.input),
.end_of_input => self.terminal.markSemanticPrompt(.command),
.end_of_command => self.terminal.screen.cursor.page_row.semantic_prompt = .input,
.end_of_command => self.terminal.screens.active.cursor.page_row.semantic_prompt = .input,
.mouse_shape => self.terminal.mouse_shape = value,
.color_operation => try self.colorOperation(value.op, &value.requests),
.kitty_color_report => try self.kittyColorOperation(value),
@ -202,17 +202,17 @@ pub const Handler = struct {
inline fn horizontalTab(self: *Handler, count: u16) !void {
for (0..count) |_| {
const x = self.terminal.screen.cursor.x;
const x = self.terminal.screens.active.cursor.x;
try self.terminal.horizontalTab();
if (x == self.terminal.screen.cursor.x) break;
if (x == self.terminal.screens.active.cursor.x) break;
}
}
inline fn horizontalTabBack(self: *Handler, count: u16) !void {
for (0..count) |_| {
const x = self.terminal.screen.cursor.x;
const x = self.terminal.screens.active.cursor.x;
try self.terminal.horizontalTabBack();
if (x == self.terminal.screen.cursor.x) break;
if (x == self.terminal.screens.active.cursor.x) break;
}
}
@ -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();
@ -246,7 +246,7 @@ pub const Handler = struct {
.enable_mode_3 => {},
.@"132_column" => try self.terminal.deccolm(
self.terminal.screen.alloc,
self.terminal.screens.active.alloc,
if (enabled) .@"132_cols" else .@"80_cols",
),
@ -410,8 +410,8 @@ test "basic print" {
defer s.deinit();
try s.nextSlice("Hello");
try testing.expectEqual(@as(usize, 5), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 5), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
@ -427,13 +427,13 @@ test "cursor movement" {
// Move cursor using escape sequences
try s.nextSlice("Hello\x1B[1;1H");
try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
// Move to position 2,3
try s.nextSlice("\x1B[2;3H");
try testing.expectEqual(@as(usize, 2), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 1), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y);
}
test "erase operations" {
@ -445,8 +445,8 @@ test "erase operations" {
// Print some text
try s.nextSlice("Hello World");
try testing.expectEqual(@as(usize, 11), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 11), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
// Move cursor to position 1,6 and erase from cursor to end of line
try s.nextSlice("\x1B[1;6H");
@ -465,7 +465,7 @@ test "tabs" {
defer s.deinit();
try s.nextSlice("A\tB");
try testing.expectEqual(@as(usize, 9), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.x);
const str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
@ -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);
@ -554,21 +554,21 @@ test "cursor save and restore" {
// Move cursor to 10,15
try s.nextSlice("\x1B[10;15H");
try testing.expectEqual(@as(usize, 14), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 9), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 14), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.y);
// Save cursor
try s.nextSlice("\x1B7");
// Move cursor elsewhere
try s.nextSlice("\x1B[1;1H");
try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
// Restore cursor
try s.nextSlice("\x1B8");
try testing.expectEqual(@as(usize, 14), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 9), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 14), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.y);
}
test "attributes" {
@ -603,8 +603,8 @@ test "DECALN screen alignment" {
try testing.expectEqualStrings("EEEEEEEEEE\nEEEEEEEEEE\nEEEEEEEEEE", str);
// Cursor should be at 1,1
try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
}
test "full reset" {
@ -624,8 +624,8 @@ test "full reset" {
try s.nextSlice("\x1Bc");
// Verify reset state
try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y);
try testing.expectEqual(@as(usize, 0), t.scrolling_region.top);
try testing.expectEqual(@as(usize, 23), t.scrolling_region.bottom);
try testing.expect(t.modes.get(.wraparound));

View File

@ -246,19 +246,18 @@ 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(
var it = term.screens.all.iterator();
while (it.next()) |entry| {
const screen: *terminalpkg.Screen = entry.value.*;
try screen.kitty_images.setLimit(
alloc,
&term.screen,
opts.config.image_storage_limit,
);
try term.secondary_screen.kitty_images.setLimit(
alloc,
&term.secondary_screen,
screen,
opts.config.image_storage_limit,
);
}
// Set our default cursor style
term.screen.cursor.cursor_style = opts.config.cursor_style;
term.screens.active.cursor.cursor_style = opts.config.cursor_style;
// Setup our terminal size in pixels for certain requests.
term.width_px = term.cols * opts.size.cell.width;
@ -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(
var it = self.terminal.screens.all.iterator();
while (it.next()) |entry| {
const screen: *terminalpkg.Screen = entry.value.*;
try 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,
screen,
config.image_storage_limit,
);
}
}
/// Resize the terminal.
@ -578,20 +576,20 @@ 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();
self.terminal.screens.active.clearSelection();
// Clear our scrollback
if (history) self.terminal.eraseDisplay(.scrollback, false);
// If we're not at a prompt, we just delete above the cursor.
if (!self.terminal.cursorIsAtPrompt()) {
if (self.terminal.screen.cursor.y > 0) {
self.terminal.screen.eraseRows(
if (self.terminal.screens.active.cursor.y > 0) {
self.terminal.screens.active.eraseRows(
.{ .active = .{ .y = 0 } },
.{ .active = .{ .y = self.terminal.screen.cursor.y - 1 } },
.{ .active = .{ .y = self.terminal.screens.active.cursor.y - 1 } },
);
}
@ -601,8 +599,8 @@ pub fn clearScreen(self: *Termio, td: *ThreadData, history: bool) !void {
// graphics that are placed baove the cursor or if it deletes
// all of them. We delete all of them for now but if this behavior
// isn't fully correct we should fix this later.
self.terminal.screen.kitty_images.delete(
self.terminal.screen.alloc,
self.terminal.screens.active.kitty_images.delete(
self.terminal.screens.active.alloc,
&self.terminal,
.{ .all = true },
);
@ -635,7 +633,7 @@ pub fn jumpToPrompt(self: *Termio, delta: isize) !void {
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.terminal.screen.scroll(.{ .delta_prompt = delta });
self.terminal.screens.active.scroll(.{ .delta_prompt = delta });
}
try self.renderer_wakeup.notify();

View File

@ -181,15 +181,15 @@ pub const StreamHandler = struct {
.cursor_left => self.terminal.cursorLeft(value.value),
.cursor_right => self.terminal.cursorRight(value.value),
.cursor_pos => self.terminal.setCursorPos(value.row, value.col),
.cursor_col => self.terminal.setCursorPos(self.terminal.screen.cursor.y + 1, value.value),
.cursor_row => self.terminal.setCursorPos(value.value, self.terminal.screen.cursor.x + 1),
.cursor_col => self.terminal.setCursorPos(self.terminal.screens.active.cursor.y + 1, value.value),
.cursor_row => self.terminal.setCursorPos(value.value, self.terminal.screens.active.cursor.x + 1),
.cursor_col_relative => self.terminal.setCursorPos(
self.terminal.screen.cursor.y + 1,
self.terminal.screen.cursor.x + 1 +| value.value,
self.terminal.screens.active.cursor.y + 1,
self.terminal.screens.active.cursor.x + 1 +| value.value,
),
.cursor_row_relative => self.terminal.setCursorPos(
self.terminal.screen.cursor.y + 1 +| value.value,
self.terminal.screen.cursor.x + 1,
self.terminal.screens.active.cursor.y + 1 +| value.value,
self.terminal.screens.active.cursor.x + 1,
),
.cursor_style => try self.setCursorStyle(value),
.erase_display_below => self.terminal.eraseDisplay(.below, value),
@ -254,23 +254,23 @@ pub const StreamHandler = struct {
.kitty_keyboard_query => try self.queryKittyKeyboard(),
.kitty_keyboard_push => {
log.debug("pushing kitty keyboard mode: {}", .{value.flags});
self.terminal.screen.kitty_keyboard.push(value.flags);
self.terminal.screens.active.kitty_keyboard.push(value.flags);
},
.kitty_keyboard_pop => {
log.debug("popping kitty keyboard mode n={}", .{value});
self.terminal.screen.kitty_keyboard.pop(@intCast(value));
self.terminal.screens.active.kitty_keyboard.pop(@intCast(value));
},
.kitty_keyboard_set => {
log.debug("setting kitty keyboard mode: set {}", .{value.flags});
self.terminal.screen.kitty_keyboard.set(.set, value.flags);
self.terminal.screens.active.kitty_keyboard.set(.set, value.flags);
},
.kitty_keyboard_set_or => {
log.debug("setting kitty keyboard mode: or {}", .{value.flags});
self.terminal.screen.kitty_keyboard.set(.@"or", value.flags);
self.terminal.screens.active.kitty_keyboard.set(.@"or", value.flags);
},
.kitty_keyboard_set_not => {
log.debug("setting kitty keyboard mode: not {}", .{value.flags});
self.terminal.screen.kitty_keyboard.set(.not, value.flags);
self.terminal.screens.active.kitty_keyboard.set(.not, value.flags);
},
.kitty_color_report => try self.kittyColorReport(value),
.color_operation => try self.colorOperation(value.op, &value.requests, value.terminator),
@ -371,7 +371,7 @@ pub const StreamHandler = struct {
.decscusr => {
const blink = self.terminal.modes.get(.cursor_blinking);
const style: u8 = switch (self.terminal.screen.cursor.cursor_style) {
const style: u8 = switch (self.terminal.screens.active.cursor.cursor_style) {
.block => if (blink) 1 else 2,
.underline => if (blink) 3 else 4,
.bar => if (blink) 5 else 6,
@ -443,17 +443,17 @@ pub const StreamHandler = struct {
inline fn horizontalTab(self: *StreamHandler, count: u16) !void {
for (0..count) |_| {
const x = self.terminal.screen.cursor.x;
const x = self.terminal.screens.active.cursor.x;
try self.terminal.horizontalTab();
if (x == self.terminal.screen.cursor.x) break;
if (x == self.terminal.screens.active.cursor.x) break;
}
}
inline fn horizontalTabBack(self: *StreamHandler, count: u16) !void {
for (0..count) |_| {
const x = self.terminal.screen.cursor.x;
const x = self.terminal.screens.active.cursor.x;
try self.terminal.horizontalTabBack();
if (x == self.terminal.screen.cursor.x) break;
if (x == self.terminal.screens.active.cursor.x) break;
}
}
@ -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
@ -688,11 +688,11 @@ pub const StreamHandler = struct {
}
inline fn startHyperlink(self: *StreamHandler, uri: []const u8, id: ?[]const u8) !void {
try self.terminal.screen.startHyperlink(uri, id);
try self.terminal.screens.active.startHyperlink(uri, id);
}
pub inline fn endHyperlink(self: *StreamHandler) !void {
self.terminal.screen.endHyperlink();
self.terminal.screens.active.endHyperlink();
}
pub fn deviceAttributes(
@ -732,11 +732,11 @@ pub const StreamHandler = struct {
x: usize,
y: usize,
} = if (self.terminal.modes.get(.origin)) .{
.x = self.terminal.screen.cursor.x -| self.terminal.scrolling_region.left,
.y = self.terminal.screen.cursor.y -| self.terminal.scrolling_region.top,
.x = self.terminal.screens.active.cursor.x -| self.terminal.scrolling_region.left,
.y = self.terminal.screens.active.cursor.y -| self.terminal.scrolling_region.top,
} else .{
.x = self.terminal.screen.cursor.x,
.y = self.terminal.screen.cursor.y,
.x = self.terminal.screens.active.cursor.x,
.y = self.terminal.screens.active.cursor.y,
};
// Response always is at least 4 chars, so this leaves the
@ -766,7 +766,7 @@ pub const StreamHandler = struct {
switch (style) {
.default => {
self.default_cursor = true;
self.terminal.screen.cursor.cursor_style = self.default_cursor_style;
self.terminal.screens.active.cursor.cursor_style = self.default_cursor_style;
self.terminal.modes.set(
.cursor_blinking,
self.default_cursor_blink orelse true,
@ -774,32 +774,32 @@ pub const StreamHandler = struct {
},
.blinking_block => {
self.terminal.screen.cursor.cursor_style = .block;
self.terminal.screens.active.cursor.cursor_style = .block;
self.terminal.modes.set(.cursor_blinking, true);
},
.steady_block => {
self.terminal.screen.cursor.cursor_style = .block;
self.terminal.screens.active.cursor.cursor_style = .block;
self.terminal.modes.set(.cursor_blinking, false);
},
.blinking_underline => {
self.terminal.screen.cursor.cursor_style = .underline;
self.terminal.screens.active.cursor.cursor_style = .underline;
self.terminal.modes.set(.cursor_blinking, true);
},
.steady_underline => {
self.terminal.screen.cursor.cursor_style = .underline;
self.terminal.screens.active.cursor.cursor_style = .underline;
self.terminal.modes.set(.cursor_blinking, false);
},
.blinking_bar => {
self.terminal.screen.cursor.cursor_style = .bar;
self.terminal.screens.active.cursor.cursor_style = .bar;
self.terminal.modes.set(.cursor_blinking, true);
},
.steady_bar => {
self.terminal.screen.cursor.cursor_style = .bar;
self.terminal.screens.active.cursor.cursor_style = .bar;
self.terminal.modes.set(.cursor_blinking, false);
},
}
@ -844,7 +844,7 @@ pub const StreamHandler = struct {
log.debug("querying kitty keyboard mode", .{});
var data: termio.Message.WriteReq.Small.Array = undefined;
const resp = try std.fmt.bufPrint(&data, "\x1b[?{}u", .{
self.terminal.screen.kitty_keyboard.current().int(),
self.terminal.screens.active.kitty_keyboard.current().int(),
});
self.messageWriter(.{