From 368f4f565a79d322c7cca749e4ceb37d6249268d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Nov 2025 13:19:16 -0800 Subject: [PATCH 1/5] terminal: Screen opts is a structure --- src/Surface.zig | 4 +- src/font/shaper/coretext.zig | 58 ++-- src/font/shaper/harfbuzz.zig | 32 +-- src/renderer/cell.zig | 2 +- src/renderer/link.zig | 6 +- src/terminal/Screen.zig | 364 +++++++++++++------------ src/terminal/Selection.zig | 36 +-- src/terminal/StringMap.zig | 2 +- src/terminal/Terminal.zig | 6 +- src/terminal/search/sliding_window.zig | 36 +-- 10 files changed, 282 insertions(+), 264 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index a44563ad4..f41e2f409 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -5696,7 +5696,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 +5765,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 diff --git a/src/font/shaper/coretext.zig b/src/font/shaper/coretext.zig index f1368679d..d73b191b8 100644 --- a/src/font/shaper/coretext.zig +++ b/src/font/shaper/coretext.zig @@ -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 diff --git a/src/font/shaper/harfbuzz.zig b/src/font/shaper/harfbuzz.zig index b5c96797f..634afd0de 100644 --- a/src/font/shaper/harfbuzz.zig +++ b/src/font/shaper/harfbuzz.zig @@ -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("👍"); @@ -563,7 +563,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 +598,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]); @@ -704,7 +704,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 +737,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 +860,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"); @@ -1092,7 +1092,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(">="); diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index d8427689b..1e371b07e 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -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 diff --git a/src/renderer/link.zig b/src/renderer/link.zig index 9f489ed48..40a25ea19 100644 --- a/src/renderer/link.zig +++ b/src/renderer/link.zig @@ -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); diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 5b90bf41b..c4c3bed57 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -175,6 +175,21 @@ pub const CharsetState = struct { const CharsetArray = std.EnumArray(charsets.Slots, charsets.Charset); }; +pub const Options = struct { + cols: size.CellCountInt, + rows: size.CellCountInt, + max_scrollback: usize = 0, + + /// A simple, default terminal. If you rely on specific dimensions or + /// scrollback (or lack of) then do not use this directly. This is just + /// for callers that need some defaults. + pub const default: Options = .{ + .cols = 80, + .rows = 24, + .max_scrollback = 0, + }; +}; + /// Initialize a new screen. /// /// max_scrollback is the amount of scrollback to keep in bytes. This @@ -184,12 +199,15 @@ pub const CharsetState = struct { /// If max scrollback is 0, then no scrollback is kept at all. pub fn init( alloc: Allocator, - cols: size.CellCountInt, - rows: size.CellCountInt, - max_scrollback: usize, + opts: Options, ) !Screen { // Initialize our backing pages. - var pages = try PageList.init(alloc, cols, rows, max_scrollback); + var pages = try PageList.init( + alloc, + opts.cols, + opts.rows, + opts.max_scrollback, + ); errdefer pages.deinit(); // Create our tracked pin for the cursor. @@ -200,7 +218,7 @@ pub fn init( return .{ .alloc = alloc, .pages = pages, - .no_scrollback = max_scrollback == 0, + .no_scrollback = opts.max_scrollback == 0, .cursor = .{ .x = 0, .y = 0, @@ -3028,7 +3046,7 @@ test "Screen read and write" { 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(); try testing.expectEqual(@as(style.Id, 0), s.cursor.style_id); @@ -3042,7 +3060,7 @@ test "Screen read and write newline" { 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(); try testing.expectEqual(@as(style.Id, 0), s.cursor.style_id); @@ -3056,7 +3074,7 @@ test "Screen read and write scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try Screen.init(alloc, 80, 2, 1000); + var s = try Screen.init(alloc, .{ .cols = 80, .rows = 2, .max_scrollback = 1000 }); defer s.deinit(); try s.testWriteString("hello\nworld\ntest"); @@ -3076,7 +3094,7 @@ test "Screen read and write no scrollback small" { const testing = std.testing; const alloc = testing.allocator; - var s = try Screen.init(alloc, 80, 2, 0); + var s = try Screen.init(alloc, .{ .cols = 80, .rows = 2, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString("hello\nworld\ntest"); @@ -3096,7 +3114,7 @@ test "Screen read and write no scrollback large" { const testing = std.testing; const alloc = testing.allocator; - var s = try Screen.init(alloc, 80, 2, 0); + var s = try Screen.init(alloc, .{ .cols = 80, .rows = 2, .max_scrollback = 0 }); defer s.deinit(); for (0..1_000) |i| { @@ -3117,13 +3135,13 @@ test "Screen cursorCopy x/y" { const testing = std.testing; const alloc = testing.allocator; - var s = try Screen.init(alloc, 10, 10, 0); + var s = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); s.cursorAbsolute(2, 3); try testing.expect(s.cursor.x == 2); try testing.expect(s.cursor.y == 3); - var s2 = try Screen.init(alloc, 10, 10, 0); + var s2 = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s2.deinit(); try s2.cursorCopy(s.cursor, .{}); try testing.expect(s2.cursor.x == 2); @@ -3141,10 +3159,10 @@ test "Screen cursorCopy style deref" { const testing = std.testing; const alloc = testing.allocator; - var s = try Screen.init(alloc, 10, 10, 0); + var s = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); - var s2 = try Screen.init(alloc, 10, 10, 0); + var s2 = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s2.deinit(); const page = &s2.cursor.page_pin.node.data; @@ -3163,10 +3181,10 @@ test "Screen cursorCopy style deref new page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); - var s2 = try Screen.init(alloc, 10, 10, 2048); + var s2 = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 2048 }); defer s2.deinit(); // We need to get the cursor on a new page. @@ -3236,11 +3254,11 @@ test "Screen cursorCopy style copy" { const testing = std.testing; const alloc = testing.allocator; - var s = try Screen.init(alloc, 10, 10, 0); + var s = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); try s.setAttribute(.{ .bold = {} }); - var s2 = try Screen.init(alloc, 10, 10, 0); + var s2 = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s2.deinit(); const page = &s2.cursor.page_pin.node.data; try s2.cursorCopy(s.cursor, .{}); @@ -3252,10 +3270,10 @@ test "Screen cursorCopy hyperlink deref" { const testing = std.testing; const alloc = testing.allocator; - var s = try Screen.init(alloc, 10, 10, 0); + var s = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); - var s2 = try Screen.init(alloc, 10, 10, 0); + var s2 = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s2.deinit(); const page = &s2.cursor.page_pin.node.data; @@ -3274,10 +3292,10 @@ test "Screen cursorCopy hyperlink deref new page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); - var s2 = try Screen.init(alloc, 10, 10, 2048); + var s2 = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 2048 }); defer s2.deinit(); // We need to get the cursor on a new page. @@ -3347,7 +3365,7 @@ test "Screen cursorCopy hyperlink copy" { const testing = std.testing; const alloc = testing.allocator; - var s = try Screen.init(alloc, 10, 10, 0); + var s = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); // Create a hyperlink for the cursor. @@ -3355,7 +3373,7 @@ test "Screen cursorCopy hyperlink copy" { try testing.expectEqual(@as(usize, 1), s.cursor.page_pin.node.data.hyperlink_set.count()); try testing.expect(s.cursor.hyperlink_id != 0); - var s2 = try Screen.init(alloc, 10, 10, 0); + var s2 = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s2.deinit(); const page = &s2.cursor.page_pin.node.data; @@ -3372,7 +3390,7 @@ test "Screen cursorCopy hyperlink copy disabled" { const testing = std.testing; const alloc = testing.allocator; - var s = try Screen.init(alloc, 10, 10, 0); + var s = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); // Create a hyperlink for the cursor. @@ -3380,7 +3398,7 @@ test "Screen cursorCopy hyperlink copy disabled" { try testing.expectEqual(@as(usize, 1), s.cursor.page_pin.node.data.hyperlink_set.count()); try testing.expect(s.cursor.hyperlink_id != 0); - var s2 = try Screen.init(alloc, 10, 10, 0); + var s2 = try Screen.init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s2.deinit(); const page = &s2.cursor.page_pin.node.data; @@ -3397,7 +3415,7 @@ test "Screen style basics" { 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(); const page = &s.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); @@ -3419,7 +3437,7 @@ test "Screen style reset to default" { 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(); const page = &s.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); @@ -3439,7 +3457,7 @@ test "Screen style reset with unset" { 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(); const page = &s.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); @@ -3459,7 +3477,7 @@ test "Screen clearRows active one line" { 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(); try s.testWriteString("hello, world"); @@ -3474,7 +3492,7 @@ test "Screen clearRows active multi line" { 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(); try s.testWriteString("hello\nworld"); @@ -3490,7 +3508,7 @@ test "Screen clearRows active styled line" { 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(); try s.setAttribute(.{ .bold = {} }); @@ -3515,7 +3533,7 @@ test "Screen clearRows protected" { 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(); try s.testWriteString("UNPROTECTED"); @@ -3543,7 +3561,7 @@ test "Screen eraseRows history" { const testing = std.testing; const alloc = testing.allocator; - var s = try Screen.init(alloc, 5, 5, 1000); + var s = try Screen.init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 1000 }); defer s.deinit(); try s.testWriteString("1\n2\n3\n4\n5\n6"); @@ -3577,7 +3595,7 @@ test "Screen eraseRows history with more lines" { const testing = std.testing; const alloc = testing.allocator; - var s = try Screen.init(alloc, 5, 5, 1000); + var s = try Screen.init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 1000 }); defer s.deinit(); try s.testWriteString("A\nB\nC\n1\n2\n3\n4\n5\n6"); @@ -3611,7 +3629,7 @@ test "Screen eraseRows active partial" { const testing = std.testing; const alloc = testing.allocator; - 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(); try s.testWriteString("1\n2\n3"); @@ -3640,7 +3658,7 @@ test "Screen: clearPrompt" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); // Set one of the rows to be a prompt @@ -3661,7 +3679,7 @@ test "Screen: clearPrompt continuation" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 4, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 4, .max_scrollback = 0 }); defer s.deinit(); // Set one of the rows to be a prompt followed by a continuation row @@ -3683,7 +3701,7 @@ test "Screen: clearPrompt consecutive inputs" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); // Set both rows to be inputs @@ -3704,7 +3722,7 @@ test "Screen: clearPrompt no prompt" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -3722,7 +3740,7 @@ test "Screen: cursorDown across pages preserves style" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 1); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); // Scroll down enough to go to another page @@ -3774,7 +3792,7 @@ test "Screen: cursorUp across pages preserves style" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 1); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); // Scroll down enough to go to another page @@ -3821,7 +3839,7 @@ test "Screen: cursorAbsolute across pages preserves style" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 1); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); // Scroll down enough to go to another page @@ -3876,7 +3894,7 @@ test "Screen: cursorAbsolute to page with insufficient capacity" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 1); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); // Scroll down enough to go to another page @@ -3943,7 +3961,7 @@ test "Screen: scrolling" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); try s.setAttribute(.{ .direct_color_bg = .{ .r = 155 } }); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -3985,7 +4003,7 @@ test "Screen: scrolling with a single-row screen no scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 1, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 1, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString("1ABCD"); @@ -4005,7 +4023,7 @@ test "Screen: scrolling with a single-row screen with scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 1, 1); + var s = try init(alloc, .{ .cols = 10, .rows = 1, .max_scrollback = 1 }); defer s.deinit(); try s.testWriteString("1ABCD"); @@ -4035,7 +4053,7 @@ test "Screen: scrolling across pages preserves style" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 1); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); try s.setAttribute(.{ .bold = {} }); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -4064,7 +4082,7 @@ test "Screen: scroll down from 0" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -4083,7 +4101,7 @@ test "Screen: scrollback various cases" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 1); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); try s.cursorDownScroll(); @@ -4164,7 +4182,7 @@ test "Screen: scrollback with multi-row delta" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 3); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 3 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH\n6IJKL"); @@ -4190,7 +4208,7 @@ test "Screen: scrollback empty" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 50); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 50 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); s.scroll(.{ .delta_row = 1 }); @@ -4205,7 +4223,7 @@ test "Screen: scrollback doesn't move viewport if not at bottom" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 3); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 3 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"); @@ -4240,7 +4258,7 @@ test "Screen: scrolling moves selection" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -4319,7 +4337,7 @@ test "Screen: scrolling moves viewport" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 1); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n"); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -4344,7 +4362,7 @@ test "Screen: scrolling when viewport is pruned" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 215, 3, 1); + var s = try init(alloc, .{ .cols = 215, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); // Write some to create scrollback and move back into our scrollback. @@ -4370,7 +4388,7 @@ test "Screen: scroll and clear full screen" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 5); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -4397,7 +4415,7 @@ test "Screen: scroll and clear partial screen" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 5); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH"); @@ -4424,7 +4442,7 @@ test "Screen: scroll and clear empty screen" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 5); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); try s.scrollClear(); { @@ -4443,7 +4461,7 @@ test "Screen: scroll and clear ignore blank lines" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 10); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH"); try s.scrollClear(); @@ -4486,7 +4504,7 @@ test "Screen: scroll above same page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 10); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); defer s.deinit(); try s.setAttribute(.{ .direct_color_bg = .{ .r = 155 } }); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -4545,7 +4563,7 @@ test "Screen: scroll above same page but cursor on previous page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 5, 10); + var s = try init(alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 10 }); defer s.deinit(); // We need to get the cursor to a new page @@ -4626,7 +4644,7 @@ test "Screen: scroll above same page but cursor on previous page last row" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 5, 10); + var s = try init(alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 10 }); defer s.deinit(); // We need to get the cursor to a new page @@ -4716,7 +4734,7 @@ test "Screen: scroll above creates new page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 10); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); defer s.deinit(); // We need to get the cursor to a new page @@ -4788,7 +4806,7 @@ test "Screen: scroll above with cursor on non-final row" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 4, 10); + var s = try init(alloc, .{ .cols = 10, .rows = 4, .max_scrollback = 10 }); defer s.deinit(); // Get the cursor to be 2 rows above a new page @@ -4865,7 +4883,7 @@ test "Screen: scroll above no scrollback bottom of page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const first_page_size = s.pages.pages.first.?.data.capacity.rows; @@ -4930,7 +4948,7 @@ test "Screen: clone" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 10); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH"); { @@ -4972,7 +4990,7 @@ test "Screen: clone partial" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 10); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH"); { @@ -5001,7 +5019,7 @@ test "Screen: clone partial cursor out of bounds" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 10); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 10 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH"); { @@ -5034,7 +5052,7 @@ test "Screen: clone contains full selection" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -5071,7 +5089,7 @@ test "Screen: clone contains none of selection" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -5098,7 +5116,7 @@ test "Screen: clone contains selection start cutoff" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -5135,7 +5153,7 @@ test "Screen: clone contains selection end cutoff" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -5172,7 +5190,7 @@ test "Screen: clone contains selection end cutoff reversed" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -5209,7 +5227,7 @@ test "Screen: clone contains subset of selection" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 4, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 4, .max_scrollback = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD"); @@ -5246,7 +5264,7 @@ test "Screen: clone contains subset of rectangle selection" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 4, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 4, .max_scrollback = 1 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD"); @@ -5285,7 +5303,7 @@ test "Screen: clone basic" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); @@ -5322,7 +5340,7 @@ test "Screen: clone empty viewport" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); { @@ -5344,7 +5362,7 @@ test "Screen: clone one line viewport" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString("1ABC"); @@ -5367,7 +5385,7 @@ test "Screen: clone empty active" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); { @@ -5389,7 +5407,7 @@ test "Screen: clone one line active with extra space" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString("1ABC"); @@ -5412,7 +5430,7 @@ test "Screen: clear history with no history" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 3); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 3 }); defer s.deinit(); try s.testWriteString("4ABCD\n5EFGH\n6IJKL"); try testing.expect(s.pages.viewport == .active); @@ -5436,7 +5454,7 @@ test "Screen: clear history" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 3); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 3 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH\n6IJKL"); try testing.expect(s.pages.viewport == .active); @@ -5470,7 +5488,7 @@ test "Screen: clear above cursor" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 3); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 3 }); defer s.deinit(); try s.testWriteString("4ABCD\n5EFGH\n6IJKL"); s.clearRows( @@ -5497,7 +5515,7 @@ test "Screen: clear above cursor with history" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 3); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 3 }); defer s.deinit(); try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n"); try s.testWriteString("4ABCD\n5EFGH\n6IJKL"); @@ -5525,7 +5543,7 @@ test "Screen: resize (no reflow) more rows" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -5543,7 +5561,7 @@ test "Screen: resize (no reflow) less rows" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -5566,7 +5584,7 @@ test "Screen: resize (no reflow) less rows trims blank lines" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD"; try s.testWriteString(str); @@ -5601,7 +5619,7 @@ test "Screen: resize (no reflow) more rows trims blank lines" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD"; try s.testWriteString(str); @@ -5636,7 +5654,7 @@ test "Screen: resize (no reflow) more cols" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -5653,7 +5671,7 @@ test "Screen: resize (no reflow) less cols" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -5671,7 +5689,7 @@ test "Screen: resize (no reflow) more rows with scrollback cursor end" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 7, 3, 2); + var s = try init(alloc, .{ .cols = 7, .rows = 3, .max_scrollback = 2 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -5688,7 +5706,7 @@ test "Screen: resize (no reflow) less rows with scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 7, 3, 2); + var s = try init(alloc, .{ .cols = 7, .rows = 3, .max_scrollback = 2 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -5707,7 +5725,7 @@ test "Screen: resize (no reflow) less rows with empty trailing" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 5); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); const str = "1\n2\n3\n4\n5\n6\n7\n8"; try s.testWriteString(str); @@ -5731,7 +5749,7 @@ test "Screen: resize (no reflow) more rows with soft wrapping" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 3, 3); + var s = try init(alloc, .{ .cols = 2, .rows = 3, .max_scrollback = 3 }); defer s.deinit(); const str = "1A2B\n3C4E\n5F6G"; try s.testWriteString(str); @@ -5772,7 +5790,7 @@ test "Screen: resize more rows no scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -5799,7 +5817,7 @@ test "Screen: resize more rows with empty scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 10); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 10 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -5826,7 +5844,7 @@ test "Screen: resize more rows with populated scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 5); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -5871,7 +5889,7 @@ test "Screen: resize more cols no reflow" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -5900,7 +5918,7 @@ test "Screen: resize more cols perfect split" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD2EFGH3IJKL"; try s.testWriteString(str); @@ -5918,7 +5936,7 @@ test "Screen: resize (no reflow) more cols with scrollback scrolled up" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 5); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); const str = "1\n2\n3\n4\n5\n6\n7\n8"; try s.testWriteString(str); @@ -5951,7 +5969,7 @@ test "Screen: resize (no reflow) less cols with scrollback scrolled up" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 5); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); const str = "1\n2\n3\n4\n5\n6\n7\n8"; try s.testWriteString(str); @@ -5995,7 +6013,7 @@ test "Screen: resize more cols no reflow preserves semantic prompt" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); // Set one of the rows to be a prompt @@ -6036,7 +6054,7 @@ test "Screen: resize more cols with reflow that fits full width" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD2EFGH\n3IJKL"; try s.testWriteString(str); @@ -6076,7 +6094,7 @@ test "Screen: resize more cols with reflow that ends in newline" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 6, 3, 0); + var s = try init(alloc, .{ .cols = 6, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD2EFGH\n3IJKL"; try s.testWriteString(str); @@ -6121,7 +6139,7 @@ test "Screen: resize more cols with reflow that forces more wrapping" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD2EFGH\n3IJKL"; try s.testWriteString(str); @@ -6162,7 +6180,7 @@ test "Screen: resize more cols with reflow that unwraps multiple times" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD2EFGH3IJKL"; try s.testWriteString(str); @@ -6203,7 +6221,7 @@ test "Screen: resize more cols with populated scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 5); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL\n4ABCD5EFGH"; try s.testWriteString(str); @@ -6247,7 +6265,7 @@ test "Screen: resize more cols with reflow" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 3, 5); + var s = try init(alloc, .{ .cols = 2, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); const str = "1ABC\n2DEF\n3ABC\n4DEF"; try s.testWriteString(str); @@ -6288,7 +6306,7 @@ test "Screen: resize more rows and cols with wrapping" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 4, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 4, .max_scrollback = 0 }); defer s.deinit(); const str = "1A2B\n3C4D"; try s.testWriteString(str); @@ -6321,7 +6339,7 @@ test "Screen: resize less rows no scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -6352,7 +6370,7 @@ test "Screen: resize less rows moving cursor" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -6392,7 +6410,7 @@ test "Screen: resize less rows with empty scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 10); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 10 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -6415,7 +6433,7 @@ test "Screen: resize less rows with populated scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 5); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -6446,7 +6464,7 @@ test "Screen: resize less rows with full scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 3); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 3 }); defer s.deinit(); const str = "00000\n1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -6486,7 +6504,7 @@ test "Screen: resize less cols no reflow" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1AB\n2EF\n3IJ"; try s.testWriteString(str); @@ -6515,7 +6533,7 @@ test "Screen: resize less cols with reflow but row space" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); const str = "1ABCD"; try s.testWriteString(str); @@ -6553,7 +6571,7 @@ test "Screen: resize less cols with reflow with trimmed rows" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -6577,7 +6595,7 @@ test "Screen: resize less cols with reflow with trimmed rows and scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 1); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 1 }); defer s.deinit(); const str = "3IJKL\n4ABCD\n5EFGH"; try s.testWriteString(str); @@ -6601,7 +6619,7 @@ test "Screen: resize less cols with reflow previously wrapped" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "3IJKL4ABCD5EFGH"; try s.testWriteString(str); @@ -6634,7 +6652,7 @@ test "Screen: resize less cols with reflow and scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 5); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); const str = "1A\n2B\n3C\n4D\n5E"; try s.testWriteString(str); @@ -6667,7 +6685,7 @@ test "Screen: resize less cols with reflow previously wrapped and scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 2); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 2 }); defer s.deinit(); const str = "1ABCD2EFGH3IJKL4ABCD5EFGH"; try s.testWriteString(str); @@ -6721,7 +6739,7 @@ test "Screen: resize less cols with scrollback keeps cursor row" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 5); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); const str = "1A\n2B\n3C\n4D\n5E"; try s.testWriteString(str); @@ -6750,7 +6768,7 @@ test "Screen: resize more rows, less cols with reflow with scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 3); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 3 }); defer s.deinit(); const str = "1ABCD\n2EFGH3IJKL\n4MNOP"; try s.testWriteString(str); @@ -6791,7 +6809,7 @@ test "Screen: resize more rows then shrink again" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 10); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 10 }); defer s.deinit(); const str = "1ABC"; try s.testWriteString(str); @@ -6840,7 +6858,7 @@ test "Screen: resize less cols to eliminate wide char" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 1, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 1, .max_scrollback = 0 }); defer s.deinit(); const str = "😀"; try s.testWriteString(str); @@ -6875,7 +6893,7 @@ test "Screen: resize less cols to wrap wide char" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 3, 3, 0); + var s = try init(alloc, .{ .cols = 3, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "x😀"; try s.testWriteString(str); @@ -6914,7 +6932,7 @@ test "Screen: resize less cols to eliminate wide char with row space" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_scrollback = 0 }); defer s.deinit(); const str = "😀"; try s.testWriteString(str); @@ -6947,7 +6965,7 @@ test "Screen: resize more cols with wide spacer head" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 3, 2, 0); + var s = try init(alloc, .{ .cols = 3, .rows = 2, .max_scrollback = 0 }); defer s.deinit(); const str = " 😀"; try s.testWriteString(str); @@ -7000,7 +7018,7 @@ test "Screen: resize more cols with wide spacer head multiple lines" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 3, 3, 0); + var s = try init(alloc, .{ .cols = 3, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "xxxyy😀"; try s.testWriteString(str); @@ -7051,7 +7069,7 @@ test "Screen: resize more cols requiring a wide spacer head" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 2, 0); + var s = try init(alloc, .{ .cols = 2, .rows = 2, .max_scrollback = 0 }); defer s.deinit(); const str = "xx😀"; try s.testWriteString(str); @@ -7102,7 +7120,7 @@ test "Screen: select untracked" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString("ABC DEF\n 123\n456"); @@ -7122,7 +7140,7 @@ test "Screen: selectAll" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); { @@ -7158,7 +7176,7 @@ test "Screen: selectLine" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString("ABC DEF\n 123\n456"); @@ -7239,7 +7257,7 @@ test "Screen: selectLine across soft-wrap" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 10, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString(" 12 34012 \n 123"); @@ -7265,7 +7283,7 @@ test "Screen: selectLine across full soft-wrap" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 5, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString("1ABCD2EFGH\n3IJKL"); @@ -7290,7 +7308,7 @@ test "Screen: selectLine across soft-wrap ignores blank lines" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 10, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString(" 12 34012 \n 123"); @@ -7350,7 +7368,7 @@ test "Screen: selectLine disabled whitespace trimming" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 10, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString(" 12 34012 \n 123"); @@ -7399,7 +7417,7 @@ test "Screen: selectLine with scrollback" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 2, 3, 5); + var s = try init(alloc, .{ .cols = 2, .rows = 3, .max_scrollback = 5 }); defer s.deinit(); try s.testWriteString("1A\n2B\n3C\n4D\n5E"); @@ -7443,7 +7461,7 @@ test "Screen: selectLine semantic prompt boundary" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 10, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteSemanticString("ABCDE\n", .unknown); try s.testWriteSemanticString("A ", .prompt); @@ -7492,7 +7510,7 @@ test "Screen: selectWord" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 10, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString("ABC DEF\n 123\n456"); @@ -7607,7 +7625,7 @@ test "Screen: selectWord across soft-wrap" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 10, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString(" 1234012\n 123"); @@ -7673,7 +7691,7 @@ test "Screen: selectWord whitespace across soft-wrap" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 10, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString("1 1\n 123"); @@ -7754,7 +7772,7 @@ test "Screen: selectWord with character boundary" { }; for (cases) |case| { - var s = try init(alloc, 20, 10, 0); + var s = try init(alloc, .{ .cols = 20, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); try s.testWriteString(case); @@ -7834,7 +7852,7 @@ test "Screen: selectOutput" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 15, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 15, .max_scrollback = 0 }); defer s.deinit(); // zig fmt: off @@ -7908,7 +7926,7 @@ test "Screen: selectOutput" { // input / prompt at y = 0, pt.y = 0 { s.deinit(); - s = try init(alloc, 10, 5, 0); + s = try init(alloc, .{ .cols = 10, .rows = 5, .max_scrollback = 0 }); try s.testWriteSemanticString("$ ", .prompt); try s.testWriteSemanticString("input1\n", .input); try s.testWriteSemanticString("output1\n", .command); @@ -7924,7 +7942,7 @@ test "Screen: selectPrompt basics" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 15, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 15, .max_scrollback = 0 }); defer s.deinit(); // zig fmt: off @@ -7999,7 +8017,7 @@ test "Screen: selectPrompt prompt at start" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 15, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 15, .max_scrollback = 0 }); defer s.deinit(); // zig fmt: off @@ -8043,7 +8061,7 @@ test "Screen: selectPrompt prompt at end" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 15, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 15, .max_scrollback = 0 }); defer s.deinit(); // zig fmt: off @@ -8087,7 +8105,7 @@ test "Screen: promptPath" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 15, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 15, .max_scrollback = 0 }); defer s.deinit(); // zig fmt: off @@ -8162,7 +8180,7 @@ test "Screen: selectionString basic" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -8187,7 +8205,7 @@ test "Screen: selectionString start outside of written area" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 10, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -8212,7 +8230,7 @@ test "Screen: selectionString end outside of written area" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 10, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -8237,7 +8255,7 @@ test "Screen: selectionString trim space" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1AB \n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -8274,7 +8292,7 @@ test "Screen: selectionString trim empty line" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 5, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); defer s.deinit(); const str = "1AB \n\n2EFGH\n3IJKL"; try s.testWriteString(str); @@ -8311,7 +8329,7 @@ test "Screen: selectionString soft wrap" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD2EFGH3IJKL"; try s.testWriteString(str); @@ -8336,7 +8354,7 @@ test "Screen: selectionString wide char" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1A⚡"; try s.testWriteString(str); @@ -8391,7 +8409,7 @@ test "Screen: selectionString wide char with header" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 3, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABC⚡"; try s.testWriteString(str); @@ -8417,7 +8435,7 @@ test "Screen: selectionString empty with soft wrap" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 2, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 2, .max_scrollback = 0 }); defer s.deinit(); // Let me describe the situation that caused this because this @@ -8450,7 +8468,7 @@ test "Screen: selectionString with zero width joiner" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 1, 0); + var s = try init(alloc, .{ .cols = 10, .rows = 1, .max_scrollback = 0 }); defer s.deinit(); const str = "👨‍"; // this has a ZWJ try s.testWriteString(str); @@ -8486,7 +8504,7 @@ test "Screen: selectionString, rectangle, basic" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 30, 5, 0); + var s = try init(alloc, .{ .cols = 30, .rows = 5, .max_scrollback = 0 }); defer s.deinit(); const str = \\Lorem ipsum dolor @@ -8519,7 +8537,7 @@ test "Screen: selectionString, rectangle, w/EOL" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 30, 5, 0); + var s = try init(alloc, .{ .cols = 30, .rows = 5, .max_scrollback = 0 }); defer s.deinit(); const str = \\Lorem ipsum dolor @@ -8554,7 +8572,7 @@ test "Screen: selectionString, rectangle, more complex w/breaks" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 30, 8, 0); + var s = try init(alloc, .{ .cols = 30, .rows = 8, .max_scrollback = 0 }); defer s.deinit(); const str = \\Lorem ipsum dolor @@ -8593,7 +8611,7 @@ test "Screen: selectionString multi-page" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 10, 3, 2048); + var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 2048 }); defer s.deinit(); const first_page_size = s.pages.pages.first.?.data.capacity.rows; @@ -8627,7 +8645,7 @@ test "Screen: lineIterator" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 5, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD\n2EFGH"; try s.testWriteString(str); @@ -8658,7 +8676,7 @@ test "Screen: lineIterator soft wrap" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 5, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); defer s.deinit(); const str = "1ABCD2EFGH\n3ABCD"; try s.testWriteString(str); @@ -8690,7 +8708,7 @@ test "Screen: hyperlink start/end" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 5, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); defer s.deinit(); try testing.expect(s.cursor.hyperlink_id == 0); { @@ -8717,7 +8735,7 @@ test "Screen: hyperlink reuse" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 5, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); defer s.deinit(); try testing.expect(s.cursor.hyperlink_id == 0); @@ -8755,7 +8773,7 @@ test "Screen: hyperlink cursor state on resize" { // it may be invalid one day. It's here to document/verify the // current behavior. - var s = try init(alloc, 5, 10, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 10, .max_scrollback = 0 }); defer s.deinit(); // Start a hyperlink @@ -8786,7 +8804,7 @@ test "Screen: cursorSetHyperlink OOM + URI too large for string alloc" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, 0); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); defer s.deinit(); // Start a hyperlink with a URI that just barely fits in the string alloc. @@ -8820,7 +8838,7 @@ test "Screen: adjustCapacity cursor style ref count" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 5, 5, 0); + var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 }); defer s.deinit(); try s.setAttribute(.{ .bold = {} }); @@ -8854,7 +8872,7 @@ test "Screen: adjustCapacity cursor hyperlink exceeds string alloc size" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, 0); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 0 }); defer s.deinit(); // Start a hyperlink with a URI that just barely fits in the string alloc. @@ -8896,7 +8914,7 @@ test "Screen: adjustCapacity cursor style exceeds style set capacity" { const testing = std.testing; const alloc = testing.allocator; - var s = try init(alloc, 80, 24, 1000); + var s = try init(alloc, .{ .cols = 80, .rows = 24, .max_scrollback = 1000 }); defer s.deinit(); const page = &s.cursor.page_pin.node.data; diff --git a/src/terminal/Selection.zig b/src/terminal/Selection.zig index 267f223d5..59cb4ef50 100644 --- a/src/terminal/Selection.zig +++ b/src/terminal/Selection.zig @@ -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(); { diff --git a/src/terminal/StringMap.zig b/src/terminal/StringMap.zig index ae34f5fc8..4ac47eeab 100644 --- a/src/terminal/StringMap.zig +++ b/src/terminal/StringMap.zig @@ -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); diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 8013110b7..d9ad62ae1 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -39,7 +39,7 @@ const log = std.log.scoped(.terminal); const TABSTOP_INTERVAL = 8; /// Screen type is an enum that tracks whether a screen is primary or alternate. -pub const ScreenType = enum { +pub const ScreenType = enum(u1) { primary, alternate, }; @@ -225,8 +225,8 @@ pub fn init( .cols = cols, .rows = rows, .active_screen = .primary, - .screen = try .init(alloc, cols, rows, opts.max_scrollback), - .secondary_screen = try .init(alloc, cols, rows, 0), + .screen = try .init(alloc, .{ .cols = cols, .rows = rows, .max_scrollback = opts.max_scrollback }), + .secondary_screen = try .init(alloc, .{ .cols = cols, .rows = rows, .max_scrollback = 0 }), .tabstops = try .init(alloc, cols, TABSTOP_INTERVAL), .scrolling_region = .{ .top = 0, diff --git a/src/terminal/search/sliding_window.zig b/src/terminal/search/sliding_window.zig index 4a2c3eb7d..db60a6670 100644 --- a/src/terminal/search/sliding_window.zig +++ b/src/terminal/search/sliding_window.zig @@ -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"); From 3aff5f0aff4d725e22e28aff1afb3a9f08e63371 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Nov 2025 13:19:16 -0800 Subject: [PATCH 2/5] ScreenSet --- src/Surface.zig | 59 +++---- src/apprt/embedded.zig | 4 +- src/inspector/Inspector.zig | 4 +- src/renderer/generic.zig | 6 +- src/renderer/link.zig | 6 +- src/terminal/Screen.zig | 21 ++- src/terminal/ScreenSet.zig | 106 +++++++++++ src/terminal/Terminal.zig | 222 +++++++++++++----------- src/terminal/formatter.zig | 20 +-- src/terminal/kitty/graphics_exec.zig | 2 +- src/terminal/kitty/graphics_storage.zig | 52 +++--- src/terminal/kitty/graphics_unicode.zig | 6 +- src/terminal/main.zig | 2 +- src/terminal/search/screen.zig | 8 +- src/terminal/stream_readonly.zig | 12 +- src/termio/Termio.zig | 40 ++--- src/termio/stream_handler.zig | 6 +- 17 files changed, 357 insertions(+), 219 deletions(-) create mode 100644 src/terminal/ScreenSet.zig diff --git a/src/Surface.zig b/src/Surface.zig index f41e2f409..db8b4474e 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -186,7 +186,7 @@ const Mouse = struct { /// The point at which the left mouse click happened. This is in screen /// coordinates so that scrolling preserves the location. left_click_pin: ?*terminal.Pin = null, - left_click_screen: terminal.ScreenType = .primary, + left_click_screen: terminal.ScreenSet.Key = .primary, /// The starting xpos/ypos of the left click. Note that if scrolling occurs, /// these will point to different "cells", but the xpos/ypos will stay @@ -1065,7 +1065,7 @@ fn selectionScrollTick(self: *Surface) !void { // If our screen changed while this is happening, we stop our // selection scroll. - if (self.mouse.left_click_screen != t.active_screen) { + if (self.mouse.left_click_screen != t.screens.active_key) { self.io.queueMessage( .{ .selection_scroll = false }, .locked, @@ -1703,7 +1703,7 @@ pub fn dumpTextLocked( // If our bottom right pin is before the viewport, then we can't // possibly have this text be within the viewport. const vp_tl_pin = self.io.terminal.screen.pages.getTopLeft(.viewport); - const br_pin = sel.bottomRight(&self.io.terminal.screen); + const br_pin = sel.bottomRight(self.io.terminal.screen); if (br_pin.before(vp_tl_pin)) break :viewport null; // If our top-left pin is after the viewport, then we can't possibly @@ -1714,7 +1714,7 @@ pub fn dumpTextLocked( log.warn("viewport bottom-right pin not found, bug?", .{}); break :viewport null; }; - const tl_pin = sel.topLeft(&self.io.terminal.screen); + const tl_pin = sel.topLeft(self.io.terminal.screen); if (vp_br_pin.before(tl_pin)) break :viewport null; // We established that our top-left somewhere before the viewport @@ -1984,7 +1984,7 @@ fn copySelectionToClipboards( var contents: std.ArrayList(apprt.ClipboardContent) = .empty; switch (format) { .plain => { - var formatter: ScreenFormatter = .init(&self.io.terminal.screen, opts); + var formatter: ScreenFormatter = .init(self.io.terminal.screen, opts); formatter.content = .{ .selection = sel }; try formatter.format(&aw.writer); try contents.append(alloc, .{ @@ -1994,7 +1994,7 @@ fn copySelectionToClipboards( }, .vt => { - var formatter: ScreenFormatter = .init(&self.io.terminal.screen, opts: { + var formatter: ScreenFormatter = .init(self.io.terminal.screen, opts: { var copy = opts; copy.emit = .vt; break :opts copy; @@ -2011,7 +2011,7 @@ fn copySelectionToClipboards( }, .html => { - var formatter: ScreenFormatter = .init(&self.io.terminal.screen, opts: { + var formatter: ScreenFormatter = .init(self.io.terminal.screen, opts: { var copy = opts; copy.emit = .html; break :opts copy; @@ -2029,7 +2029,7 @@ fn copySelectionToClipboards( .mixed => { // First, generate plain text with codepoint mappings applied - var formatter: ScreenFormatter = .init(&self.io.terminal.screen, opts); + var formatter: ScreenFormatter = .init(self.io.terminal.screen, opts); formatter.content = .{ .selection = sel }; try formatter.format(&aw.writer); try contents.append(alloc, .{ @@ -2039,7 +2039,7 @@ fn copySelectionToClipboards( assert(aw.written().len == 0); // Second, generate HTML without codepoint mappings - formatter = .init(&self.io.terminal.screen, opts: { + formatter = .init(self.io.terminal.screen, opts: { var copy = opts; copy.emit = .html; @@ -3098,7 +3098,7 @@ pub fn scrollCallback( // we convert to cursor keys. This only happens if we're: // (1) alt screen (2) no explicit mouse reporting and (3) alt // scroll mode enabled. - if (self.io.terminal.active_screen == .alternate and + if (self.io.terminal.screens.active_key == .alternate and self.io.terminal.flags.mouse_event == .none and self.io.terminal.modes.get(.mouse_alternate_scroll)) { @@ -3506,7 +3506,7 @@ pub fn mouseButtonCallback( { const pos = try self.rt_surface.getCursorPos(); const point = self.posToViewport(pos.x, pos.y); - const screen = &self.renderer_state.terminal.screen; + const screen: *terminal.Screen = self.renderer_state.terminal.screen; const p = screen.pages.pin(.{ .viewport = point }) orelse { log.warn("failed to get pin for clicked point", .{}); return false; @@ -3681,7 +3681,7 @@ pub fn mouseButtonCallback( self.renderer_state.mutex.lock(); defer self.renderer_state.mutex.unlock(); const t: *terminal.Terminal = self.renderer_state.terminal; - const screen = &self.renderer_state.terminal.screen; + const screen: *terminal.Screen = self.renderer_state.terminal.screen; const pos = try self.rt_surface.getCursorPos(); const pin = pin: { @@ -3717,14 +3717,15 @@ pub fn mouseButtonCallback( } if (self.mouse.left_click_pin) |prev| { - const pin_screen = t.getScreen(self.mouse.left_click_screen); - pin_screen.pages.untrackPin(prev); + if (t.screens.get(self.mouse.left_click_screen)) |pin_screen| { + pin_screen.pages.untrackPin(prev); + } self.mouse.left_click_pin = null; } // Store it self.mouse.left_click_pin = pin; - self.mouse.left_click_screen = t.active_screen; + self.mouse.left_click_screen = t.screens.active_key; self.mouse.left_click_xpos = pos.x; self.mouse.left_click_ypos = pos.y; @@ -3808,7 +3809,7 @@ pub fn mouseButtonCallback( defer self.renderer_state.mutex.unlock(); // Get our viewport pin - const screen = &self.renderer_state.terminal.screen; + const screen: *terminal.Screen = self.renderer_state.terminal.screen; const pin = pin: { const pos = try self.rt_surface.getCursorPos(); const pt_viewport = self.posToViewport(pos.x, pos.y); @@ -3911,7 +3912,7 @@ fn clickMoveCursor(self: *Surface, to: terminal.Pin) !void { // Click to move cursor only works on the primary screen where prompts // exist. This means that alt screen multiplexers like tmux will not // support this feature. It is just too messy. - if (t.active_screen != .primary) return; + if (t.screens.active_key != .primary) return; // This flag is only set if we've seen at least one semantic prompt // OSC sequence. If we've never seen that sequence, we can't possibly @@ -3964,7 +3965,7 @@ fn linkAtPos( terminal.Selection, } { // Convert our cursor position to a screen point. - const screen = &self.renderer_state.terminal.screen; + const screen: *terminal.Screen = self.renderer_state.terminal.screen; const mouse_pin: terminal.Pin = mouse_pin: { const point = self.posToViewport(pos.x, pos.y); const pin = screen.pages.pin(.{ .viewport = point }) orelse { @@ -4237,7 +4238,7 @@ pub fn cursorPosCallback( insp.mouse.last_xpos = pos.x; insp.mouse.last_ypos = pos.y; - const screen = &self.renderer_state.terminal.screen; + const screen: *terminal.Screen = self.renderer_state.terminal.screen; insp.mouse.last_point = screen.pages.pin(.{ .viewport = .{ .x = pos_vp.x, .y = pos_vp.y, @@ -4303,7 +4304,7 @@ pub fn cursorPosCallback( // invalidate our pin or mouse state because if the screen switches // back then we can continue our selection. const t: *terminal.Terminal = self.renderer_state.terminal; - if (self.mouse.left_click_screen != t.active_screen) break :select; + if (self.mouse.left_click_screen != t.screens.active_key) break :select; // All roads lead to requiring a re-render at this point. try self.queueRender(); @@ -4328,7 +4329,7 @@ pub fn cursorPosCallback( } // Convert to points - const screen = &t.screen; + const screen: *terminal.Screen = t.screen; const pin = screen.pages.pin(.{ .viewport = .{ .x = pos_vp.x, @@ -4357,7 +4358,7 @@ fn dragLeftClickDouble( self: *Surface, drag_pin: terminal.Pin, ) !void { - const screen = &self.io.terminal.screen; + const screen: *terminal.Screen = self.io.terminal.screen; const click_pin = self.mouse.left_click_pin.?.*; // Get the word closest to our starting click. @@ -4397,7 +4398,7 @@ fn dragLeftClickTriple( self: *Surface, drag_pin: terminal.Pin, ) !void { - const screen = &self.io.terminal.screen; + const screen: *terminal.Screen = self.io.terminal.screen; const click_pin = self.mouse.left_click_pin.?.*; // Get the line selection under our current drag point. If there isn't a @@ -4930,7 +4931,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool { self.renderer_state.mutex.lock(); defer self.renderer_state.mutex.unlock(); - if (self.io.terminal.active_screen == .alternate) return false; + if (self.io.terminal.screens.active_key == .alternate) return false; } self.io.queueMessage(.{ @@ -4966,7 +4967,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool self.renderer_state.mutex.lock(); defer self.renderer_state.mutex.unlock(); const sel = self.io.terminal.screen.selection orelse return false; - const tl = sel.topLeft(&self.io.terminal.screen); + const tl = sel.topLeft(self.io.terminal.screen); self.io.terminal.screen.scroll(.{ .pin = tl }); } @@ -5220,7 +5221,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool self.renderer_state.mutex.lock(); defer self.renderer_state.mutex.unlock(); - const screen = &self.io.terminal.screen; + const screen: *terminal.Screen = self.io.terminal.screen; const sel = if (screen.selection) |*sel| sel else { // If we don't have a selection we do not perform this // action, allowing the keybind to fall through to the @@ -5340,7 +5341,7 @@ fn writeScreenFile( .history => history: { // We do not support this for alternate screens // because they don't have scrollback anyways. - if (self.io.terminal.active_screen == .alternate) { + if (self.io.terminal.screens.active_key == .alternate) { break :history null; } @@ -5371,7 +5372,7 @@ fn writeScreenFile( }; const ScreenFormatter = terminal.formatter.ScreenFormatter; - var formatter: ScreenFormatter = .init(&self.io.terminal.screen, .{ + var formatter: ScreenFormatter = .init(self.io.terminal.screen, .{ .emit = switch (write_screen.emit) { .plain => .plain, .vt => .vt, @@ -5384,7 +5385,7 @@ fn writeScreenFile( .palette = &self.io.terminal.colors.palette.current, }); formatter.content = .{ .selection = sel.ordered( - &self.io.terminal.screen, + self.io.terminal.screen, .forward, ) }; try formatter.format(buf_writer); diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 1faa0b9c6..1a713310f 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1578,7 +1578,7 @@ pub const CAPI = struct { defer surface.core_surface.renderer_state.mutex.unlock(); const core_sel = sel.core( - &surface.core_surface.renderer_state.terminal.screen, + surface.core_surface.renderer_state.terminal.screen, ) orelse return false; return readTextLocked(surface, core_sel, result); @@ -2137,7 +2137,7 @@ pub const CAPI = struct { // Get our word selection const sel = sel: { - const screen = &surface.renderer_state.terminal.screen; + const screen: *terminal.Screen = surface.renderer_state.terminal.screen; const pos = try ptr.getCursorPos(); const pt_viewport = surface.posToViewport(pos.x, pos.y); const pin = screen.pages.pin(.{ diff --git a/src/inspector/Inspector.zig b/src/inspector/Inspector.zig index 92da5a362..0f2371400 100644 --- a/src/inspector/Inspector.zig +++ b/src/inspector/Inspector.zig @@ -304,7 +304,7 @@ fn renderScreenWindow(self: *Inspector) void { )) return; const t = self.surface.renderer_state.terminal; - const screen = &t.screen; + const screen: *terminal.Screen = t.screen; { _ = cimgui.c.igBeginTable( @@ -324,7 +324,7 @@ fn renderScreenWindow(self: *Inspector) void { } { _ = cimgui.c.igTableSetColumnIndex(1); - cimgui.c.igText("%s", @tagName(t.active_screen).ptr); + cimgui.c.igText("%s", @tagName(t.screens.active_key).ptr); } } } diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 0b4c55896..905261b9f 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -1066,7 +1066,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { bg: terminal.color.RGB, fg: terminal.color.RGB, screen: terminal.Screen, - screen_type: terminal.ScreenType, + screen_type: terminal.ScreenSet.Key, mouse: renderer.State.Mouse, preedit: ?renderer.State.Preedit, cursor_color: ?terminal.color.RGB, @@ -1207,7 +1207,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .bg = bg, .fg = fg, .screen = screen_copy, - .screen_type = state.terminal.active_screen, + .screen_type = state.terminal.screens.active_key, .mouse = state.mouse, .preedit = preedit, .cursor_color = state.terminal.colors.cursor.get(), @@ -2317,7 +2317,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { self: *Self, wants_rebuild: bool, screen: *terminal.Screen, - screen_type: terminal.ScreenType, + screen_type: terminal.ScreenSet.Key, mouse: renderer.State.Mouse, preedit: ?renderer.State.Preedit, cursor_style_: ?renderer.CursorStyle, diff --git a/src/renderer/link.zig b/src/renderer/link.zig index 40a25ea19..ec4000f65 100644 --- a/src/renderer/link.zig +++ b/src/renderer/link.zig @@ -609,7 +609,7 @@ test "matchset osc8" { // Initialize our terminal var t = try Terminal.init(alloc, .{ .cols = 10, .rows = 10 }); defer t.deinit(alloc); - const s = &t.screen; + const s: *terminal.Screen = t.screen; try t.printString("ABC"); try t.screen.startHyperlink("http://example.com", null); @@ -624,7 +624,7 @@ test "matchset osc8" { { var match = try set.matchSet( alloc, - &t.screen, + t.screen, .{ .x = 2, .y = 0 }, inputpkg.ctrlOrSuper(.{}), ); @@ -635,7 +635,7 @@ test "matchset osc8" { // Match over link var match = try set.matchSet( alloc, - &t.screen, + t.screen, .{ .x = 3, .y = 0 }, inputpkg.ctrlOrSuper(.{}), ); diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index c4c3bed57..8ed256869 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -178,8 +178,15 @@ pub const CharsetState = struct { pub const Options = struct { cols: size.CellCountInt, rows: size.CellCountInt, + + /// The maximum size of scrollback in bytes. Zero means unlimited. Any + /// other value will be clamped to support a minimum of the active area. max_scrollback: usize = 0, + /// The total storage limit for Kitty images in bytes for this + /// screen. Kitty image storage is per-screen. + kitty_image_storage_limit: usize = 320 * 1000 * 1000, // 320MB + /// A simple, default terminal. If you rely on specific dimensions or /// scrollback (or lack of) then do not use this directly. This is just /// for callers that need some defaults. @@ -215,7 +222,7 @@ pub fn init( errdefer pages.untrackPin(page_pin); const page_rac = page_pin.rowAndCell(); - return .{ + var result: Screen = .{ .alloc = alloc, .pages = pages, .no_scrollback = opts.max_scrollback == 0, @@ -227,6 +234,18 @@ pub fn init( .page_cell = page_rac.cell, }, }; + + if (comptime build_options.kitty_graphics) { + // This can't fail because the storage is always empty at this point + // and the only fail-able case is that we have to evict images. + result.kitty_images.setLimit( + alloc, + &result, + opts.kitty_image_storage_limit, + ) catch unreachable; + } + + return result; } pub fn deinit(self: *Screen) void { diff --git a/src/terminal/ScreenSet.zig b/src/terminal/ScreenSet.zig new file mode 100644 index 000000000..1b6b053fe --- /dev/null +++ b/src/terminal/ScreenSet.zig @@ -0,0 +1,106 @@ +/// A ScreenSet holds multiple terminal screens. This is initially created +/// to handle simple primary vs alternate screens, but could be extended +/// in the future to handle N screens. +/// +/// One of the goals of this is to allow lazy initialization of screens +/// as needed. The primary screen is always initialized, but the alternate +/// screen may not be until first used. +const ScreenSet = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const testing = std.testing; +const Allocator = std.mem.Allocator; +const Screen = @import("Screen.zig"); + +/// The possible keys for screens in the screen set. +pub const Key = enum(u1) { + primary, + alternate, +}; + +/// The key value of the currently active screen. Useful for simple +/// comparisons, e.g. "is this screen the primary screen". +active_key: Key, + +/// The active screen pointer. +active: *Screen, + +/// All screens that are initialized. +all: std.EnumMap(Key, *Screen), + +pub fn init( + alloc: Allocator, + opts: Screen.Options, +) !ScreenSet { + // We need to initialize our initial primary screen + const screen = try alloc.create(Screen); + errdefer alloc.destroy(screen); + screen.* = try .init(alloc, opts); + return .{ + .active_key = .primary, + .active = screen, + .all = .init(.{ .primary = screen }), + }; +} + +pub fn deinit(self: *ScreenSet, alloc: Allocator) void { + // Destroy all initialized screens + var it = self.all.iterator(); + while (it.next()) |entry| { + entry.value.*.deinit(); + alloc.destroy(entry.value.*); + } +} + +/// Get the screen for the given key, if it is initialized. +pub fn get(self: *const ScreenSet, key: Key) ?*Screen { + return self.all.get(key); +} + +/// Get the screen for the given key, initializing it if necessary. +pub fn getInit( + self: *ScreenSet, + alloc: Allocator, + key: Key, + opts: Screen.Options, +) !*Screen { + if (self.get(key)) |screen| return screen; + const screen = try alloc.create(Screen); + errdefer alloc.destroy(screen); + screen.* = try .init(alloc, opts); + self.all.put(key, screen); + return screen; +} + +/// Remove a key from the set. The primary screen cannot be removed (asserted). +pub fn remove( + self: *ScreenSet, + alloc: Allocator, + key: Key, +) void { + assert(key != .primary); + if (self.all.fetchRemove(key)) |screen| { + screen.deinit(); + alloc.destroy(screen); + } +} + +/// Switch the active screen to the given key. Requires that the +/// screen is initialized. +pub fn switchTo(self: *ScreenSet, key: Key) void { + self.active_key = key; + self.active = self.all.get(key).?; +} + +test ScreenSet { + const alloc = testing.allocator; + var set: ScreenSet = try .init(alloc, .default); + defer set.deinit(alloc); + try testing.expectEqual(.primary, set.active_key); + + // Initialize a secondary screen + _ = try set.getInit(alloc, .alternate, .default); + set.switchTo(.alternate); + try testing.expectEqual(.alternate, set.active_key); +} diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index d9ad62ae1..185537115 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -29,6 +29,7 @@ const size = @import("size.zig"); const pagepkg = @import("page.zig"); const style = @import("style.zig"); const Screen = @import("Screen.zig"); +const ScreenSet = @import("ScreenSet.zig"); const Page = pagepkg.Page; const Cell = pagepkg.Cell; const Row = pagepkg.Row; @@ -38,18 +39,17 @@ const log = std.log.scoped(.terminal); /// Default tabstop interval const TABSTOP_INTERVAL = 8; -/// Screen type is an enum that tracks whether a screen is primary or alternate. -pub const ScreenType = enum(u1) { - primary, - alternate, -}; +/// The currently active screen. To get the type of screen this is, +/// inspect screens.active_key instead. +/// +/// Note: long term I'd like to get rid of this and force everyone +/// to go through screens instead but there's SO MUCH code that relies +/// on this property existing and it was really nasty to change all of +/// that today. +screen: *Screen, -/// Screen is the current screen state. The "active_screen" field says what -/// the current screen is. The backup screen is the opposite of the active -/// screen. -active_screen: ScreenType, -screen: Screen, -secondary_screen: Screen, +/// The set of screens behind this terminal (e.g. primary vs alternate). +screens: ScreenSet, /// Whether we're currently writing to the status line (DECSASD and DECSSDT). /// We don't support a status line currently so we just black hole this @@ -221,12 +221,19 @@ pub fn init( ) !Terminal { const cols = opts.cols; const rows = opts.rows; + + var screen_set: ScreenSet = try .init(alloc, .{ + .cols = cols, + .rows = rows, + .max_scrollback = opts.max_scrollback, + }); + errdefer screen_set.deinit(alloc); + return .{ .cols = cols, .rows = rows, - .active_screen = .primary, - .screen = try .init(alloc, .{ .cols = cols, .rows = rows, .max_scrollback = opts.max_scrollback }), - .secondary_screen = try .init(alloc, .{ .cols = cols, .rows = rows, .max_scrollback = 0 }), + .screen = screen_set.active, + .screens = screen_set, .tabstops = try .init(alloc, cols, TABSTOP_INTERVAL), .scrolling_region = .{ .top = 0, @@ -245,8 +252,7 @@ pub fn init( pub fn deinit(self: *Terminal, alloc: Allocator) void { self.tabstops.deinit(alloc); - self.screen.deinit(); - self.secondary_screen.deinit(); + self.screens.deinit(alloc); self.pwd.deinit(alloc); self.* = undefined; } @@ -266,7 +272,7 @@ pub fn vtHandler(self: *Terminal) ReadonlyHandler { /// The general allocator we should use for this terminal. fn gpa(self: *Terminal) Allocator { - return self.screen.alloc; + return self.screens.active.alloc; } /// Print UTF-8 encoded string to the terminal. @@ -1074,7 +1080,7 @@ pub fn markSemanticPrompt(self: *Terminal, p: SemanticPrompt) void { /// If the shell integration doesn't exist, this will always return false. pub fn cursorIsAtPrompt(self: *Terminal) bool { // If we're on the secondary screen, we're never at a prompt. - if (self.active_screen == .alternate) return false; + if (self.screens.active_key == .alternate) return false; // Reverse through the active const start_x, const start_y = .{ self.screen.cursor.x, self.screen.cursor.y }; @@ -2202,7 +2208,7 @@ pub fn eraseDisplay( // at a prompt scrolls the screen contents prior to clearing. // Most shells send `ESC [ H ESC [ 2 J` so we can't just check // our current cursor position. See #905 - if (self.active_screen == .primary) at_prompt: { + if (self.screens.active_key == .primary) at_prompt: { // Go from the bottom of the active up and see if we're // at a prompt. const active_br = self.screen.pages.getBottomRight( @@ -2531,25 +2537,22 @@ pub fn resize( self.tabstops = try .init(alloc, cols, 8); } - // If we're making the screen smaller, dealloc the unused items. - if (self.active_screen == .primary) { - if (self.flags.shell_redraws_prompt) { - self.screen.clearPrompt(); - } - - if (self.modes.get(.wraparound)) { - try self.screen.resize(cols, rows); - } else { - try self.screen.resizeWithoutReflow(cols, rows); - } - try self.secondary_screen.resizeWithoutReflow(cols, rows); + // Resize primary screen, which supports reflow + const primary = self.screens.get(.primary).?; + if (self.screens.active_key == .primary and + self.flags.shell_redraws_prompt) + { + primary.clearPrompt(); + } + if (self.modes.get(.wraparound)) { + try primary.resize(cols, rows); } else { - try self.screen.resizeWithoutReflow(cols, rows); - if (self.modes.get(.wraparound)) { - try self.secondary_screen.resize(cols, rows); - } else { - try self.secondary_screen.resizeWithoutReflow(cols, rows); - } + try primary.resizeWithoutReflow(cols, rows); + } + + // Alternate screen, if it exists, doesn't reflow + if (self.screens.get(.alternate)) |alt| { + try alt.resizeWithoutReflow(cols, rows); } // Whenever we resize we just mark it as a screen clear @@ -2581,14 +2584,6 @@ pub fn getPwd(self: *const Terminal) ?[]const u8 { return self.pwd.items; } -/// Get the screen pointer for the given type. -pub fn getScreen(self: *Terminal, t: ScreenType) *Screen { - return if (self.active_screen == t) - &self.screen - else - &self.secondary_screen; -} - /// Switch to the given screen type (alternate or primary). /// /// This does NOT handle behaviors such as clearing the screen, @@ -2604,40 +2599,60 @@ pub fn getScreen(self: *Terminal, t: ScreenType) *Screen { /// more than two screens in the future if needed. There isn't /// currently a spec for this, but it is something I think might /// be useful in the future. -pub fn switchScreen(self: *Terminal, t: ScreenType) ?*Screen { +pub fn switchScreen(self: *Terminal, key: ScreenSet.Key) !?*Screen { // If we're already on the requested screen we do nothing. - if (self.active_screen == t) return null; + if (self.screens.active_key == key) return null; + const old = self.screens.active; // We always end hyperlink state when switching screens. // We need to do this on the original screen. - self.screen.endHyperlink(); + old.endHyperlink(); - // Switch the screens - const old = self.screen; - self.screen = self.secondary_screen; - self.secondary_screen = old; - self.active_screen = t; + // Switch the screens/ + const new = self.screens.get(key) orelse new: { + const primary = self.screens.get(.primary).?; + break :new try self.screens.getInit( + old.alloc, + key, + .{ + .cols = self.cols, + .rows = self.rows, + .max_scrollback = switch (key) { + .primary => primary.pages.explicit_max_size, + .alternate => 0, + }, + + // Inherit our Kitty image storage limit from the primary + // screen if we have to initialize. + .kitty_image_storage_limit = primary.kitty_images.total_limit, + }, + ); + }; // The new screen should not have any hyperlinks set - assert(self.screen.cursor.hyperlink_id == 0); + assert(new.cursor.hyperlink_id == 0); // Bring our charset state with us - self.screen.charset = old.charset; + new.charset = old.charset; // Clear our selection - self.screen.clearSelection(); + new.clearSelection(); if (comptime build_options.kitty_graphics) { // Mark kitty images as dirty so they redraw. Without this set // the images will remain where they were (the dirty bit on // the screen only tracks the terminal grid, not the images). - self.screen.kitty_images.dirty = true; + new.kitty_images.dirty = true; } // Mark our terminal as dirty to redraw the grid. self.flags.dirty.clear = true; - return &self.secondary_screen; + // Finalize the switch + self.screens.switchTo(key); + self.screen = new; + + return old; } /// Switch screen via a mode switch (e.g. mode 47, 1047, 1049). @@ -2653,7 +2668,7 @@ pub fn switchScreenMode( self: *Terminal, mode: SwitchScreenMode, enabled: bool, -) void { +) !void { // The behavior in this function is completely based on reading // the xterm source, specifically "charproc.c" for // `srm_ALTBUF`, `srm_OPT_ALTBUF`, and `srm_OPT_ALTBUF_CURSOR`. @@ -2665,7 +2680,7 @@ pub fn switchScreenMode( // If we're disabling 1047 and we're on alt screen then // we clear the screen. - .@"1047" => if (!enabled and self.active_screen == .alternate) { + .@"1047" => if (!enabled and self.screens.active_key == .alternate) { self.eraseDisplay(.complete, false); }, @@ -2675,8 +2690,8 @@ pub fn switchScreenMode( } // Switch screens first to whatever we're going to. - const to: ScreenType = if (enabled) .alternate else .primary; - const old_ = self.switchScreen(to); + const to: ScreenSet.Key = if (enabled) .alternate else .primary; + const old_ = try self.switchScreen(to); switch (mode) { // For these modes, we need to copy the cursor. We only copy @@ -2697,7 +2712,7 @@ pub fn switchScreenMode( // Mode 1049 restores cursor on the primary screen when // we disable it. .@"1049" => if (enabled) { - assert(self.active_screen == .alternate); + assert(self.screens.active_key == .alternate); self.eraseDisplay(.complete, false); // When we enter alt screen with 1049, we always copy the @@ -2714,7 +2729,7 @@ pub fn switchScreenMode( }; } } else { - assert(self.active_screen == .primary); + assert(self.screens.active_key == .primary); self.restoreCursor() catch |err| { log.warn( "restore cursor on switch screen failed to={} err={}", @@ -2765,17 +2780,16 @@ pub fn plainStringUnwrapped(self: *Terminal, alloc: Allocator) ![]const u8 { /// this will reuse the existing memory. In the latter case, memory may /// be wasted (since its unused) but it isn't leaked. pub fn fullReset(self: *Terminal) void { - // Reset our screens - self.screen.reset(); - self.secondary_screen.reset(); - // Ensure we're back on primary screen - if (self.active_screen != .primary) { - const old = self.screen; - self.screen = self.secondary_screen; - self.secondary_screen = old; - self.active_screen = .primary; - } + self.screens.switchTo(.primary); + self.screens.remove( + self.screens.active.alloc, + .alternate, + ); + self.screen = self.screens.active; + + // Reset our screens + self.screens.active.reset(); // Rest our basic state self.modes.reset(); @@ -10757,7 +10771,7 @@ test "Terminal: cursorIsAtPrompt alternate screen" { try testing.expect(t.cursorIsAtPrompt()); // Secondary screen is never a prompt - t.switchScreenMode(.@"1049", true); + try t.switchScreenMode(.@"1049", true); try testing.expect(!t.cursorIsAtPrompt()); t.markSemanticPrompt(.prompt); try testing.expect(!t.cursorIsAtPrompt()); @@ -10841,7 +10855,7 @@ test "Terminal: fullReset clears alt screen kitty keyboard state" { var t = try init(testing.allocator, .{ .cols = 10, .rows = 10 }); defer t.deinit(testing.allocator); - t.switchScreenMode(.@"1049", true); + try t.switchScreenMode(.@"1049", true); t.screen.kitty_keyboard.push(.{ .disambiguate = true, .report_events = false, @@ -10849,10 +10863,10 @@ test "Terminal: fullReset clears alt screen kitty keyboard state" { .report_all = true, .report_associated = true, }); - t.switchScreenMode(.@"1049", false); + try t.switchScreenMode(.@"1049", false); t.fullReset(); - try testing.expectEqual(0, t.secondary_screen.kitty_keyboard.current().int()); + try testing.expect(t.screens.get(.alternate) == null); } test "Terminal: fullReset default modes" { @@ -11164,8 +11178,8 @@ test "Terminal: mode 47 alt screen plain" { try t.printString("1A"); // Go to alt screen with mode 47 - t.switchScreenMode(.@"47", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"47", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should be empty { @@ -11184,8 +11198,8 @@ test "Terminal: mode 47 alt screen plain" { } // Go back to primary - t.switchScreenMode(.@"47", false); - try testing.expectEqual(ScreenType.primary, t.active_screen); + try t.switchScreenMode(.@"47", false); + try testing.expectEqual(.primary, t.screens.active_key); // Primary screen should still have the original content { @@ -11195,8 +11209,8 @@ test "Terminal: mode 47 alt screen plain" { } // Go back to alt screen with mode 47 - t.switchScreenMode(.@"47", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"47", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should retain content { @@ -11215,8 +11229,8 @@ test "Terminal: mode 47 copies cursor both directions" { try t.setAttribute(.{ .direct_color_fg = .{ .r = 0xFF, .g = 0, .b = 0x7F } }); // Go to alt screen with mode 47 - t.switchScreenMode(.@"47", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"47", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Verify that our style is set { @@ -11230,8 +11244,8 @@ test "Terminal: mode 47 copies cursor both directions" { try t.setAttribute(.{ .direct_color_fg = .{ .r = 0, .g = 0xFF, .b = 0 } }); // Go back to primary - t.switchScreenMode(.@"47", false); - try testing.expectEqual(ScreenType.primary, t.active_screen); + try t.switchScreenMode(.@"47", false); + try testing.expectEqual(.primary, t.screens.active_key); // Verify that our style is still set { @@ -11251,8 +11265,8 @@ test "Terminal: mode 1047 alt screen plain" { try t.printString("1A"); // Go to alt screen with mode 47 - t.switchScreenMode(.@"1047", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"1047", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should be empty { @@ -11271,8 +11285,8 @@ test "Terminal: mode 1047 alt screen plain" { } // Go back to primary - t.switchScreenMode(.@"1047", false); - try testing.expectEqual(ScreenType.primary, t.active_screen); + try t.switchScreenMode(.@"1047", false); + try testing.expectEqual(.primary, t.screens.active_key); // Primary screen should still have the original content { @@ -11282,8 +11296,8 @@ test "Terminal: mode 1047 alt screen plain" { } // Go back to alt screen with mode 1047 - t.switchScreenMode(.@"1047", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"1047", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should be empty { @@ -11302,8 +11316,8 @@ test "Terminal: mode 1047 copies cursor both directions" { try t.setAttribute(.{ .direct_color_fg = .{ .r = 0xFF, .g = 0, .b = 0x7F } }); // Go to alt screen with mode 47 - t.switchScreenMode(.@"1047", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"1047", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Verify that our style is set { @@ -11317,8 +11331,8 @@ test "Terminal: mode 1047 copies cursor both directions" { try t.setAttribute(.{ .direct_color_fg = .{ .r = 0, .g = 0xFF, .b = 0 } }); // Go back to primary - t.switchScreenMode(.@"1047", false); - try testing.expectEqual(ScreenType.primary, t.active_screen); + try t.switchScreenMode(.@"1047", false); + try testing.expectEqual(.primary, t.screens.active_key); // Verify that our style is still set { @@ -11338,8 +11352,8 @@ test "Terminal: mode 1049 alt screen plain" { try t.printString("1A"); // Go to alt screen with mode 47 - t.switchScreenMode(.@"1049", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"1049", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should be empty { @@ -11358,8 +11372,8 @@ test "Terminal: mode 1049 alt screen plain" { } // Go back to primary - t.switchScreenMode(.@"1049", false); - try testing.expectEqual(ScreenType.primary, t.active_screen); + try t.switchScreenMode(.@"1049", false); + try testing.expectEqual(.primary, t.screens.active_key); // Primary screen should still have the original content { @@ -11377,8 +11391,8 @@ test "Terminal: mode 1049 alt screen plain" { } // Go back to alt screen with mode 1049 - t.switchScreenMode(.@"1049", true); - try testing.expectEqual(ScreenType.alternate, t.active_screen); + try t.switchScreenMode(.@"1049", true); + try testing.expectEqual(.alternate, t.screens.active_key); // Screen should be empty { diff --git a/src/terminal/formatter.zig b/src/terminal/formatter.zig index 6683b3453..0a742ccb1 100644 --- a/src/terminal/formatter.zig +++ b/src/terminal/formatter.zig @@ -331,7 +331,7 @@ pub const TerminalFormatter = struct { } } - var screen_formatter: ScreenFormatter = .init(&self.terminal.screen, self.opts); + var screen_formatter: ScreenFormatter = .init(self.terminal.screen, self.opts); screen_formatter.content = self.content; screen_formatter.extra = self.extra.screen; screen_formatter.pin_map = self.pin_map; @@ -4231,7 +4231,7 @@ test "Screen plain single line" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .plain); + var formatter: ScreenFormatter = .init(t.screen, .plain); formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; try formatter.format(&builder.writer); @@ -4268,7 +4268,7 @@ test "Screen plain multiline" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .plain); + var formatter: ScreenFormatter = .init(t.screen, .plain); formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; try formatter.format(&builder.writer); @@ -4316,7 +4316,7 @@ test "Screen plain with selection" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .plain); + var formatter: ScreenFormatter = .init(t.screen, .plain); formatter.content = .{ .selection = .init( t.screen.pages.pin(.{ .active = .{ .x = 0, .y = 1 } }).?, t.screen.pages.pin(.{ .active = .{ .x = 4, .y = 1 } }).?, @@ -4361,7 +4361,7 @@ test "Screen vt with cursor position" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.cursor = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; @@ -4420,7 +4420,7 @@ test "Screen vt with style" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.style = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; @@ -4472,7 +4472,7 @@ test "Screen vt with hyperlink" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.hyperlink = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; @@ -4532,7 +4532,7 @@ test "Screen vt with protection" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.protection = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; @@ -4584,7 +4584,7 @@ test "Screen vt with kitty keyboard" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.kitty_keyboard = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; @@ -4638,7 +4638,7 @@ test "Screen vt with charsets" { var pin_map: std.ArrayList(Pin) = .empty; defer pin_map.deinit(alloc); - var formatter: ScreenFormatter = .init(&t.screen, .vt); + var formatter: ScreenFormatter = .init(t.screen, .vt); formatter.extra.charsets = true; formatter.pin_map = .{ .alloc = alloc, .map = &pin_map }; diff --git a/src/terminal/kitty/graphics_exec.zig b/src/terminal/kitty/graphics_exec.zig index f917c104a..9cf0bda0c 100644 --- a/src/terminal/kitty/graphics_exec.zig +++ b/src/terminal/kitty/graphics_exec.zig @@ -252,7 +252,7 @@ fn display( result.placement_id, p, ) catch |err| { - p.deinit(&terminal.screen); + p.deinit(terminal.screen); encodeError(&result, err); return result; }; diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig index 8aef0ece5..34b9a6e85 100644 --- a/src/terminal/kitty/graphics_storage.zig +++ b/src/terminal/kitty/graphics_storage.zig @@ -232,7 +232,7 @@ pub const ImageStorage = struct { // Deinit the placement and remove it const image_id = entry.key_ptr.image_id; - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (delete_images) self.deleteIfUnused(alloc, image_id); } @@ -247,7 +247,7 @@ pub const ImageStorage = struct { .id => |v| self.deleteById( alloc, - &t.screen, + t.screen, v.image_id, v.placement_id, v.delete, @@ -257,7 +257,7 @@ pub const ImageStorage = struct { const img = self.imageByNumber(v.image_number) orelse break :newest; self.deleteById( alloc, - &t.screen, + t.screen, img.id, v.placement_id, v.delete, @@ -332,7 +332,7 @@ pub const ImageStorage = struct { const img = self.imageById(entry.key_ptr.image_id) orelse continue; const rect = entry.value_ptr.rect(img, t) orelse continue; if (rect.top_left.x <= x and rect.bottom_right.x >= x) { - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (v.delete) self.deleteIfUnused(alloc, img.id); } @@ -364,7 +364,7 @@ pub const ImageStorage = struct { var target_pin_copy = target_pin; target_pin_copy.x = rect.top_left.x; if (target_pin_copy.isBetween(rect.top_left, rect.bottom_right)) { - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (v.delete) self.deleteIfUnused(alloc, img.id); } @@ -387,7 +387,7 @@ pub const ImageStorage = struct { if (entry.value_ptr.z == v.z) { const image_id = entry.key_ptr.image_id; - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (v.delete) self.deleteIfUnused(alloc, image_id); } @@ -411,7 +411,7 @@ pub const ImageStorage = struct { while (it.next()) |entry| { if (entry.key_ptr.image_id >= v.first or entry.key_ptr.image_id <= v.last) { const image_id = entry.key_ptr.image_id; - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (v.delete) self.deleteIfUnused(alloc, image_id); } @@ -498,7 +498,7 @@ pub const ImageStorage = struct { const rect = entry.value_ptr.rect(img, t) orelse continue; if (target_pin.isBetween(rect.top_left, rect.bottom_right)) { if (filter) |f| if (!f(filter_ctx, entry.value_ptr.*)) continue; - entry.value_ptr.deinit(&t.screen); + entry.value_ptr.deinit(t.screen); self.placements.removeByPtr(entry.key_ptr); if (delete_unused) self.deleteIfUnused(alloc, img.id); } @@ -825,7 +825,7 @@ test "storage: add placement with zero placement id" { t.height_px = 100; var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 0, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 25, .y = 25 }) } }); @@ -853,7 +853,7 @@ test "storage: delete all placements and images" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -876,7 +876,7 @@ test "storage: delete all placements and images preserves limit" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); s.total_limit = 5000; try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); @@ -901,7 +901,7 @@ test "storage: delete all placements" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -924,7 +924,7 @@ test "storage: delete all placements by image id" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -947,7 +947,7 @@ test "storage: delete all placements by image id and unused images" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -970,7 +970,7 @@ test "storage: delete placement by specific id" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -1000,7 +1000,7 @@ test "storage: delete intersecting cursor" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); @@ -1032,7 +1032,7 @@ test "storage: delete intersecting cursor plus unused" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); @@ -1064,7 +1064,7 @@ test "storage: delete intersecting cursor hits multiple" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); @@ -1090,7 +1090,7 @@ test "storage: delete by column" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); @@ -1122,7 +1122,7 @@ test "storage: delete by column 1x1" { t.height_px = 100; var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 1, .height = 1 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 0 }) } }); @@ -1156,7 +1156,7 @@ test "storage: delete by row" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 50, .height = 50 }); try s.addImage(alloc, .{ .id = 2, .width = 25, .height = 25 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 0, .y = 0 }) } }); @@ -1188,7 +1188,7 @@ test "storage: delete by row 1x1" { t.height_px = 100; var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1, .width = 1, .height = 1 }); try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 0 }) } }); try s.addPlacement(alloc, 1, 2, .{ .location = .{ .pin = try trackPin(&t, .{ .y = 1 }) } }); @@ -1220,7 +1220,7 @@ test "storage: delete images by range 1" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -1245,7 +1245,7 @@ test "storage: delete images by range 2" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -1270,7 +1270,7 @@ test "storage: delete images by range 3" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); @@ -1295,7 +1295,7 @@ test "storage: delete images by range 4" { const tracked = t.screen.pages.countTrackedPins(); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); try s.addImage(alloc, .{ .id = 1 }); try s.addImage(alloc, .{ .id = 2 }); try s.addImage(alloc, .{ .id = 3 }); diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index a4a25e751..491c3e110 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -1180,7 +1180,7 @@ test "unicode render placement: dog 4x2" { var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 }); defer t.deinit(alloc); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); const image: Image = .{ .id = 1, .width = 500, .height = 306 }; try s.addImage(alloc, image); @@ -1247,7 +1247,7 @@ test "unicode render placement: dog 2x2 with blank cells" { var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 }); defer t.deinit(alloc); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); const image: Image = .{ .id = 1, .width = 500, .height = 306 }; try s.addImage(alloc, image); @@ -1313,7 +1313,7 @@ test "unicode render placement: dog 1x1" { var t = try terminal.Terminal.init(alloc, .{ .cols = 100, .rows = 100 }); defer t.deinit(alloc); var s: ImageStorage = .{}; - defer s.deinit(alloc, &t.screen); + defer s.deinit(alloc, t.screen); const image: Image = .{ .id = 1, .width = 500, .height = 306 }; try s.addImage(alloc, image); diff --git a/src/terminal/main.zig b/src/terminal/main.zig index bdcbfe77f..3f67b78b3 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -41,7 +41,7 @@ pub const Point = point.Point; pub const ReadonlyHandler = stream_readonly.Handler; pub const ReadonlyStream = stream_readonly.Stream; pub const Screen = @import("Screen.zig"); -pub const ScreenType = Terminal.ScreenType; +pub const ScreenSet = @import("ScreenSet.zig"); pub const Scrollbar = PageList.Scrollbar; pub const Selection = @import("Selection.zig"); pub const SizeReportStyle = csi.SizeReportStyle; diff --git a/src/terminal/search/screen.zig b/src/terminal/search/screen.zig index 0ffeb76c4..674f08b8c 100644 --- a/src/terminal/search/screen.zig +++ b/src/terminal/search/screen.zig @@ -391,7 +391,7 @@ test "simple search" { defer s.deinit(); try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang"); - var search: ScreenSearch = try .init(alloc, &t.screen, "Fizz"); + var search: ScreenSearch = try .init(alloc, t.screen, "Fizz"); defer search.deinit(); try search.searchAll(); try testing.expectEqual(2, search.active_results.items.len); @@ -444,7 +444,7 @@ test "simple search with history" { for (0..list.rows) |_| try s.nextSlice("\r\n"); try s.nextSlice("hello."); - var search: ScreenSearch = try .init(alloc, &t.screen, "Fizz"); + var search: ScreenSearch = try .init(alloc, t.screen, "Fizz"); defer search.deinit(); try search.searchAll(); try testing.expectEqual(0, search.active_results.items.len); @@ -482,7 +482,7 @@ test "reload active with history change" { try s.nextSlice("Fizz\r\n"); // Start up our search which will populate our initial active area. - var search: ScreenSearch = try .init(alloc, &t.screen, "Fizz"); + var search: ScreenSearch = try .init(alloc, t.screen, "Fizz"); defer search.deinit(); try search.searchAll(); { @@ -562,7 +562,7 @@ test "active change contents" { defer s.deinit(); try s.nextSlice("Fuzz\r\nBuzz\r\nFizz\r\nBang"); - var search: ScreenSearch = try .init(alloc, &t.screen, "Fizz"); + var search: ScreenSearch = try .init(alloc, t.screen, "Fizz"); defer search.deinit(); try search.searchAll(); try testing.expectEqual(1, search.active_results.items.len); diff --git a/src/terminal/stream_readonly.zig b/src/terminal/stream_readonly.zig index 907c48762..d34e4c84c 100644 --- a/src/terminal/stream_readonly.zig +++ b/src/terminal/stream_readonly.zig @@ -233,9 +233,9 @@ pub const Handler = struct { self.terminal.scrolling_region.right = self.terminal.cols - 1; }, - .alt_screen_legacy => self.terminal.switchScreenMode(.@"47", enabled), - .alt_screen => self.terminal.switchScreenMode(.@"1047", enabled), - .alt_screen_save_cursor_clear_enter => self.terminal.switchScreenMode(.@"1049", enabled), + .alt_screen_legacy => try self.terminal.switchScreenMode(.@"47", enabled), + .alt_screen => try self.terminal.switchScreenMode(.@"1047", enabled), + .alt_screen_save_cursor_clear_enter => try self.terminal.switchScreenMode(.@"1049", enabled), .save_cursor => if (enabled) { self.terminal.saveCursor(); @@ -527,18 +527,18 @@ test "alt screen" { // Write to primary screen try s.nextSlice("Primary"); - try testing.expectEqual(Terminal.ScreenType.primary, t.active_screen); + try testing.expectEqual(.primary, t.screens.active_key); // Switch to alt screen try s.nextSlice("\x1B[?1049h"); - try testing.expectEqual(Terminal.ScreenType.alternate, t.active_screen); + try testing.expectEqual(.alternate, t.screens.active_key); // Write to alt screen try s.nextSlice("Alt"); // Switch back to primary try s.nextSlice("\x1B[?1049l"); - try testing.expectEqual(Terminal.ScreenType.primary, t.active_screen); + try testing.expectEqual(.primary, t.screens.active_key); const str = try t.plainString(testing.allocator); defer testing.allocator.free(str); diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index 1e181a137..6371722b5 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -246,16 +246,15 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { errdefer term.deinit(alloc); // Set the image size limits - try term.screen.kitty_images.setLimit( - alloc, - &term.screen, - opts.config.image_storage_limit, - ); - try term.secondary_screen.kitty_images.setLimit( - alloc, - &term.secondary_screen, - opts.config.image_storage_limit, - ); + var it = term.screens.all.iterator(); + while (it.next()) |entry| { + const screen: *terminalpkg.Screen = entry.value.*; + try screen.kitty_images.setLimit( + alloc, + screen, + opts.config.image_storage_limit, + ); + } // Set our default cursor style term.screen.cursor.cursor_style = opts.config.cursor_style; @@ -451,16 +450,15 @@ pub fn changeConfig(self: *Termio, td: *ThreadData, config: *DerivedConfig) !voi }; // Set the image size limits - try self.terminal.screen.kitty_images.setLimit( - self.alloc, - &self.terminal.screen, - config.image_storage_limit, - ); - try self.terminal.secondary_screen.kitty_images.setLimit( - self.alloc, - &self.terminal.secondary_screen, - config.image_storage_limit, - ); + var it = self.terminal.screens.all.iterator(); + while (it.next()) |entry| { + const screen: *terminalpkg.Screen = entry.value.*; + try screen.kitty_images.setLimit( + self.alloc, + screen, + config.image_storage_limit, + ); + } } /// Resize the terminal. @@ -578,7 +576,7 @@ pub fn clearScreen(self: *Termio, td: *ThreadData, history: bool) !void { // emulator-level screen clear, this messes up the running programs // knowledge of where the cursor is and causes rendering issues. So, // for alt screen, we do nothing. - if (self.terminal.active_screen == .alternate) return; + if (self.terminal.screens.active_key == .alternate) return; // Clear our selection self.terminal.screen.clearSelection(); diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 0131ff2e1..4b40ff3cf 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -583,15 +583,15 @@ pub const StreamHandler = struct { }, .alt_screen_legacy => { - self.terminal.switchScreenMode(.@"47", enabled); + try self.terminal.switchScreenMode(.@"47", enabled); }, .alt_screen => { - self.terminal.switchScreenMode(.@"1047", enabled); + try self.terminal.switchScreenMode(.@"1047", enabled); }, .alt_screen_save_cursor_clear_enter => { - self.terminal.switchScreenMode(.@"1049", enabled); + try self.terminal.switchScreenMode(.@"1049", enabled); }, // Mode 1048 is xterm's conditional save cursor depending From 580f9f057bc8f2bad5bd93442a96158de4ca8b9d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Nov 2025 15:10:54 -0800 Subject: [PATCH 3/5] convert t.screen to t.screens.active --- src/Surface.zig | 136 +- src/apprt/embedded.zig | 8 +- src/input/key_encode.zig | 2 +- src/inspector/Inspector.zig | 6 +- src/inspector/termio.zig | 2 +- src/renderer/cursor.zig | 10 +- src/renderer/generic.zig | 32 +- src/renderer/link.zig | 10 +- src/terminal/Terminal.zig | 1514 +++++++++++------------ src/terminal/formatter.zig | 234 ++-- src/terminal/kitty/graphics_exec.zig | 20 +- src/terminal/kitty/graphics_storage.zig | 128 +- src/terminal/kitty/graphics_unicode.zig | 38 +- src/terminal/search/active.zig | 18 +- src/terminal/search/pagelist.zig | 66 +- src/terminal/search/screen.zig | 40 +- src/terminal/stream_readonly.zig | 82 +- src/termio/Termio.zig | 16 +- src/termio/stream_handler.zig | 60 +- 19 files changed, 1205 insertions(+), 1217 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index db8b4474e..308b6d1f7 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -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; @@ -3506,7 +3506,7 @@ pub fn mouseButtonCallback( { const pos = try self.rt_surface.getCursorPos(); const point = self.posToViewport(pos.x, pos.y); - const screen: *terminal.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: *terminal.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: { @@ -3758,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(); } }, @@ -3776,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(); } }, @@ -3809,7 +3809,7 @@ pub fn mouseButtonCallback( defer self.renderer_state.mutex.unlock(); // Get our viewport pin - const screen: *terminal.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); @@ -3835,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 @@ -3850,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}, @@ -3861,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}, @@ -3920,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. @@ -3965,7 +3965,7 @@ fn linkAtPos( terminal.Selection, } { // Convert our cursor position to a screen point. - const screen: *terminal.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 { @@ -4053,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, }); @@ -4139,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(); } } @@ -4197,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 @@ -4238,7 +4238,7 @@ pub fn cursorPosCallback( insp.mouse.last_xpos = pos.x; insp.mouse.last_ypos = pos.y; - const screen: *terminal.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, @@ -4329,7 +4329,7 @@ pub fn cursorPosCallback( } // Convert to points - const screen: *terminal.Screen = t.screen; + const screen: *terminal.Screen = t.screens.active; const pin = screen.pages.pin(.{ .viewport = .{ .x = pos_vp.x, @@ -4358,7 +4358,7 @@ fn dragLeftClickDouble( self: *Surface, drag_pin: terminal.Pin, ) !void { - const screen: *terminal.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. @@ -4379,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, @@ -4398,7 +4398,7 @@ fn dragLeftClickTriple( self: *Surface, drag_pin: terminal.Pin, ) !void { - const screen: *terminal.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 @@ -4417,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( @@ -4426,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)), @@ -4773,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}, @@ -4806,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| { @@ -4956,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(); @@ -4966,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(); @@ -5177,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(); @@ -5221,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: *terminal.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 @@ -5336,7 +5336,7 @@ 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 @@ -5362,7 +5362,7 @@ fn writeScreenFile( ); }, - .selection => self.io.terminal.screen.selection, + .selection => self.io.terminal.screens.active.selection, }; const sel = sel_ orelse { @@ -5372,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, @@ -5385,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); diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 1a713310f..25d09271e 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -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: *terminal.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 diff --git a/src/input/key_encode.zig b/src/input/key_encode.zig index f3dfee0b6..b63de6f6d 100644 --- a/src/input/key_encode.zig +++ b/src/input/key_encode.zig @@ -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, diff --git a/src/inspector/Inspector.zig b/src/inspector/Inspector.zig index 0f2371400..3f9888841 100644 --- a/src/inspector/Inspector.zig +++ b/src/inspector/Inspector.zig @@ -304,7 +304,7 @@ fn renderScreenWindow(self: *Inspector) void { )) return; const t = self.surface.renderer_state.terminal; - const screen: *terminal.Screen = t.screen; + const screen: *terminal.Screen = t.screens.active; { _ = cimgui.c.igBeginTable( @@ -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 .{}; diff --git a/src/inspector/termio.zig b/src/inspector/termio.zig index 840a587bf..7e2b51ee1 100644 --- a/src/inspector/termio.zig +++ b/src/inspector/termio.zig @@ -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, }; diff --git a/src/renderer/cursor.zig b/src/renderer/cursor.zig index 287b83450..ee79ead29 100644 --- a/src/renderer/cursor.zig +++ b/src/renderer/cursor.zig @@ -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); diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 905261b9f..912dcc457 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -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, @@ -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; diff --git a/src/renderer/link.zig b/src/renderer/link.zig index ec4000f65..39283cf5f 100644 --- a/src/renderer/link.zig +++ b/src/renderer/link.zig @@ -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: *terminal.Screen = 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(.{}), ); diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 185537115..f67cb119c 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -39,15 +39,6 @@ const log = std.log.scoped(.terminal); /// Default tabstop interval const TABSTOP_INTERVAL = 8; -/// The currently active screen. To get the type of screen this is, -/// inspect screens.active_key instead. -/// -/// Note: long term I'd like to get rid of this and force everyone -/// to go through screens instead but there's SO MUCH code that relies -/// on this property existing and it was really nasty to change all of -/// that today. -screen: *Screen, - /// The set of screens behind this terminal (e.g. primary vs alternate). screens: ScreenSet, @@ -232,7 +223,6 @@ pub fn init( return .{ .cols = cols, .rows = rows, - .screen = screen_set.active, .screens = screen_set, .tabstops = try .init(alloc, cols, TABSTOP_INTERVAL), .scrolling_region = .{ @@ -300,17 +290,17 @@ pub fn printRepeat(self: *Terminal, count_req: usize) !void { } pub fn print(self: *Terminal, c: u21) !void { - // log.debug("print={x} y={} x={}", .{ c, self.screen.cursor.y, self.screen.cursor.x }); + // log.debug("print={x} y={} x={}", .{ c, self.screens.active.cursor.y, self.screens.active.cursor.x }); // If we're not on the main display, do nothing for now if (self.status_display != .main) return; // After doing any printing, wrapping, scrolling, etc. we want to ensure // that our screen remains in a consistent state. - defer self.screen.assertIntegrity(); + defer self.screens.active.assertIntegrity(); // Our right margin depends where our cursor is now. - const right_limit = if (self.screen.cursor.x > self.scrolling_region.right) + const right_limit = if (self.screens.active.cursor.x > self.scrolling_region.right) self.cols else self.scrolling_region.right + 1; @@ -321,7 +311,7 @@ pub fn print(self: *Terminal, c: u21) !void { // as quickly as possible. if (c > 255 and self.modes.get(.grapheme_cluster) and - self.screen.cursor.x > 0) + self.screens.active.cursor.x > 0) grapheme: { // We need the previous cell to determine if we're at a grapheme // break or not. If we are NOT, then we are still combining the @@ -336,18 +326,18 @@ pub fn print(self: *Terminal, c: u21) !void { // we're not on the last column, then we just use the previous // column. Otherwise, we need to check if there is text to // figure out if we're attaching to the prev or current. - if (self.screen.cursor.x != right_limit - 1) break :left 1; - break :left @intFromBool(self.screen.cursor.page_cell.codepoint() == 0); + if (self.screens.active.cursor.x != right_limit - 1) break :left 1; + break :left @intFromBool(self.screens.active.cursor.page_cell.codepoint() == 0); }; // If the previous cell is a wide spacer tail, then we actually // want to use the cell before that because that has the actual // content. - const immediate = self.screen.cursorCellLeft(left); + const immediate = self.screens.active.cursorCellLeft(left); break :prev switch (immediate.wide) { else => .{ .cell = immediate, .left = left }, .spacer_tail => .{ - .cell = self.screen.cursorCellLeft(left + 1), + .cell = self.screens.active.cursorCellLeft(left + 1), .left = left + 1, }, }; @@ -361,7 +351,7 @@ pub fn print(self: *Terminal, c: u21) !void { var state: unicode.GraphemeBreakState = .{}; var cp1: u21 = prev.cell.content.codepoint; if (prev.cell.hasGrapheme()) { - const cps = self.screen.cursor.page_pin.node.data.lookupGrapheme(prev.cell).?; + const cps = self.screens.active.cursor.page_pin.node.data.lookupGrapheme(prev.cell).?; for (cps) |cp2| { // log.debug("cp1={x} cp2={x}", .{ cp1, cp2 }); assert(!unicode.graphemeBreak(cp1, cp2, &state)); @@ -401,12 +391,12 @@ pub fn print(self: *Terminal, c: u21) !void { // Move our cursor back to the previous. We'll move // the cursor within this block to the proper location. - self.screen.cursorLeft(prev.left); + self.screens.active.cursorLeft(prev.left); // If we don't have space for the wide char, we need // to insert spacers and wrap. Then we just print the wide // char as normal. - if (self.screen.cursor.x == right_limit - 1) { + if (self.screens.active.cursor.x == right_limit - 1) { if (!self.modes.get(.wraparound)) return; self.printCell( 0, @@ -418,14 +408,14 @@ pub fn print(self: *Terminal, c: u21) !void { self.printCell(prev.cell.content.codepoint, .wide); // Write our spacer - self.screen.cursorRight(1); + self.screens.active.cursorRight(1); self.printCell(0, .spacer_tail); // Move the cursor again so we're beyond our spacer - if (self.screen.cursor.x == right_limit - 1) { - self.screen.cursor.pending_wrap = true; + if (self.screens.active.cursor.x == right_limit - 1) { + self.screens.active.cursor.pending_wrap = true; } else { - self.screen.cursorRight(1); + self.screens.active.cursorRight(1); } }, @@ -435,7 +425,7 @@ pub fn print(self: *Terminal, c: u21) !void { prev.cell.wide = .narrow; // Remove the wide spacer tail - const cell = self.screen.cursorCellLeft(prev.left - 1); + const cell = self.screens.active.cursorCellLeft(prev.left - 1); cell.wide = .narrow; // Back track the cursor so that we don't end up with @@ -445,15 +435,15 @@ pub fn print(self: *Terminal, c: u21) !void { // least surprise, and also matches the behavior that // can be observed in Kitty, which is one of the only // other VS aware terminals. - if (self.screen.cursor.x == right_limit - 1) { + if (self.screens.active.cursor.x == right_limit - 1) { // If we're already at the right edge, we stay // here and set the pending wrap to false since // when we pend a wrap, we only move our cursor once // even for wide chars (tests verify). - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; } else { // Otherwise, move back. - self.screen.cursorLeft(1); + self.screens.active.cursorLeft(1); } break :narrow; @@ -468,8 +458,8 @@ pub fn print(self: *Terminal, c: u21) !void { prev.left, prev.cell.codepoint(), }); - self.screen.cursorMarkDirty(); - try self.screen.appendGrapheme(prev.cell, c); + self.screens.active.cursorMarkDirty(); + try self.screens.active.appendGrapheme(prev.cell, c); return; } } @@ -497,16 +487,16 @@ pub fn print(self: *Terminal, c: u21) !void { // print anything or even store this. Zero-width characters are ALWAYS // attached to some other non-zero-width character at the time of // writing. - if (self.screen.cursor.x == 0) { + if (self.screens.active.cursor.x == 0) { log.warn("zero-width character with no prior character, ignoring", .{}); return; } // Find our previous cell const prev = prev: { - const immediate = self.screen.cursorCellLeft(1); + const immediate = self.screens.active.cursorCellLeft(1); if (immediate.wide != .spacer_tail) break :prev immediate; - break :prev self.screen.cursorCellLeft(2); + break :prev self.screens.active.cursorCellLeft(2); }; // If our previous cell has no text, just ignore the zero-width character @@ -522,7 +512,7 @@ pub fn print(self: *Terminal, c: u21) !void { if (!emoji) return; } - try self.screen.appendGrapheme(prev, c); + try self.screens.active.appendGrapheme(prev, c); return; } @@ -530,14 +520,14 @@ pub fn print(self: *Terminal, c: u21) !void { self.previous_char = c; // If we're soft-wrapping, then handle that first. - if (self.screen.cursor.pending_wrap and self.modes.get(.wraparound)) { + if (self.screens.active.cursor.pending_wrap and self.modes.get(.wraparound)) { try self.printWrap(); } // If we have insert mode enabled then we need to handle that. We // only do insert mode if we're not at the end of the line. if (self.modes.get(.insert) and - self.screen.cursor.x + width < self.cols) + self.screens.active.cursor.x + width < self.cols) { self.insertBlanks(width); } @@ -545,7 +535,7 @@ pub fn print(self: *Terminal, c: u21) !void { switch (width) { // Single cell is very easy: just write in the cell 1 => { - self.screen.cursorMarkDirty(); + self.screens.active.cursorMarkDirty(); @call(.always_inline, printCell, .{ self, c, .narrow }); }, @@ -557,7 +547,7 @@ pub fn print(self: *Terminal, c: u21) !void { // If we don't have space for the wide char, we need // to insert spacers and wrap. Then we just print the wide // char as normal. - if (self.screen.cursor.x == right_limit - 1) { + if (self.screens.active.cursor.x == right_limit - 1) { // If we don't have wraparound enabled then we don't print // this character at all and don't move the cursor. This is // how xterm behaves. @@ -570,14 +560,14 @@ pub fn print(self: *Terminal, c: u21) !void { try self.printWrap(); } - self.screen.cursorMarkDirty(); + self.screens.active.cursorMarkDirty(); self.printCell(c, .wide); - self.screen.cursorRight(1); + self.screens.active.cursorRight(1); self.printCell(0, .spacer_tail); } else { // This is pretty broken, terminals should never be only 1-wide. // We should prevent this downstream. - self.screen.cursorMarkDirty(); + self.screens.active.cursorMarkDirty(); self.printCell(0, .narrow); }, @@ -586,13 +576,13 @@ pub fn print(self: *Terminal, c: u21) !void { // If we're at the column limit, then we need to wrap the next time. // In this case, we don't move the cursor. - if (self.screen.cursor.x == right_limit - 1) { - self.screen.cursor.pending_wrap = true; + if (self.screens.active.cursor.x == right_limit - 1) { + self.screens.active.cursor.pending_wrap = true; return; } // Move the cursor - self.screen.cursorRight(1); + self.screens.active.cursorRight(1); } fn printCell( @@ -600,7 +590,7 @@ fn printCell( unmapped_c: u21, wide: Cell.Wide, ) void { - defer self.screen.assertIntegrity(); + defer self.screens.active.assertIntegrity(); // TODO: spacers should use a bgcolor only cell @@ -608,11 +598,11 @@ fn printCell( // TODO: non-utf8 handling, gr // If we're single shifting, then we use the key exactly once. - const key = if (self.screen.charset.single_shift) |key_once| blk: { - self.screen.charset.single_shift = null; + const key = if (self.screens.active.charset.single_shift) |key_once| blk: { + self.screens.active.charset.single_shift = null; break :blk key_once; - } else self.screen.charset.gl; - const set = self.screen.charset.charsets.get(key); + } else self.screens.active.charset.gl; + const set = self.screens.active.charset.charsets.get(key); // UTF-8 or ASCII is used as-is if (set == .utf8 or set == .ascii) break :c unmapped_c; @@ -626,7 +616,7 @@ fn printCell( break :c @intCast(table[@intCast(unmapped_c)]); }; - const cell = self.screen.cursor.page_cell; + const cell = self.screens.active.cursor.page_cell; // If the wide property of this cell is the same, then we don't // need to do the special handling here because the structure will @@ -639,22 +629,22 @@ fn printCell( // Previous cell was wide. We need to clear the tail and head. .wide => wide: { - if (self.screen.cursor.x >= self.cols - 1) break :wide; + if (self.screens.active.cursor.x >= self.cols - 1) break :wide; - const spacer_cell = self.screen.cursorCellRight(1); - self.screen.clearCells( - &self.screen.cursor.page_pin.node.data, - self.screen.cursor.page_row, + const spacer_cell = self.screens.active.cursorCellRight(1); + self.screens.active.clearCells( + &self.screens.active.cursor.page_pin.node.data, + self.screens.active.cursor.page_row, spacer_cell[0..1], ); - if (self.screen.cursor.y > 0 and self.screen.cursor.x <= 1) { - const head_cell = self.screen.cursorCellEndOfPrev(); + if (self.screens.active.cursor.y > 0 and self.screens.active.cursor.x <= 1) { + const head_cell = self.screens.active.cursorCellEndOfPrev(); head_cell.wide = .narrow; } }, .spacer_tail => { - assert(self.screen.cursor.x > 0); + assert(self.screens.active.cursor.x > 0); // So integrity checks pass. We fix this up later so we don't // need to do this without safety checks. @@ -662,14 +652,14 @@ fn printCell( cell.wide = .narrow; } - const wide_cell = self.screen.cursorCellLeft(1); - self.screen.clearCells( - &self.screen.cursor.page_pin.node.data, - self.screen.cursor.page_row, + const wide_cell = self.screens.active.cursorCellLeft(1); + self.screens.active.clearCells( + &self.screens.active.cursor.page_pin.node.data, + self.screens.active.cursor.page_row, wide_cell[0..1], ); - if (self.screen.cursor.y > 0 and self.screen.cursor.x <= 1) { - const head_cell = self.screen.cursorCellEndOfPrev(); + if (self.screens.active.cursor.y > 0 and self.screens.active.cursor.x <= 1) { + const head_cell = self.screens.active.cursorCellEndOfPrev(); head_cell.wide = .narrow; } }, @@ -683,21 +673,21 @@ fn printCell( // If the prior value had graphemes, clear those if (cell.hasGrapheme()) { - self.screen.cursor.page_pin.node.data.clearGrapheme( - self.screen.cursor.page_row, + self.screens.active.cursor.page_pin.node.data.clearGrapheme( + self.screens.active.cursor.page_row, cell, ); } // We don't need to update the style refs unless the // cell's new style will be different after writing. - const style_changed = cell.style_id != self.screen.cursor.style_id; + const style_changed = cell.style_id != self.screens.active.cursor.style_id; if (style_changed) { - var page = &self.screen.cursor.page_pin.node.data; + var page = &self.screens.active.cursor.page_pin.node.data; // Release the old style. if (cell.style_id != style.default_id) { - assert(self.screen.cursor.page_row.styled); + assert(self.screens.active.cursor.page_row.styled); page.styles.release(page.memory, cell.style_id); } } @@ -709,18 +699,18 @@ fn printCell( cell.* = .{ .content_tag = .codepoint, .content = .{ .codepoint = c }, - .style_id = self.screen.cursor.style_id, + .style_id = self.screens.active.cursor.style_id, .wide = wide, - .protected = self.screen.cursor.protected, + .protected = self.screens.active.cursor.protected, }; if (style_changed) { - var page = &self.screen.cursor.page_pin.node.data; + var page = &self.screens.active.cursor.page_pin.node.data; // Use the new style. if (cell.style_id != style.default_id) { page.styles.use(page.memory, cell.style_id); - self.screen.cursor.page_row.styled = true; + self.screens.active.cursor.page_row.styled = true; } } @@ -728,22 +718,22 @@ fn printCell( // row so that the renderer can lookup rows with these much faster. if (comptime build_options.kitty_graphics) { if (c == kitty.graphics.unicode.placeholder) { - self.screen.cursor.page_row.kitty_virtual_placeholder = true; + self.screens.active.cursor.page_row.kitty_virtual_placeholder = true; } } // We check for an active hyperlink first because setHyperlink // handles clearing the old hyperlink and an optimization if we're // overwriting the same hyperlink. - if (self.screen.cursor.hyperlink_id > 0) { - self.screen.cursorSetHyperlink() catch |err| { + if (self.screens.active.cursor.hyperlink_id > 0) { + self.screens.active.cursorSetHyperlink() catch |err| { log.warn("error reallocating for more hyperlink space, ignoring hyperlink err={}", .{err}); assert(!cell.hyperlink); }; } else if (had_hyperlink) { // If the previous cell had a hyperlink then we need to clear it. - var page = &self.screen.cursor.page_pin.node.data; - page.clearHyperlink(self.screen.cursor.page_row, cell); + var page = &self.screens.active.cursor.page_pin.node.data; + page.clearHyperlink(self.screens.active.cursor.page_row, cell); } } @@ -751,31 +741,31 @@ fn printWrap(self: *Terminal) !void { // We only mark that we soft-wrapped if we're at the edge of our // full screen. We don't mark the row as wrapped if we're in the // middle due to a right margin. - const mark_wrap = self.screen.cursor.x == self.cols - 1; - if (mark_wrap) self.screen.cursor.page_row.wrap = true; + const mark_wrap = self.screens.active.cursor.x == self.cols - 1; + if (mark_wrap) self.screens.active.cursor.page_row.wrap = true; // Get the old semantic prompt so we can extend it to the next // line. We need to do this before we index() because we may // modify memory. - const old_prompt = self.screen.cursor.page_row.semantic_prompt; + const old_prompt = self.screens.active.cursor.page_row.semantic_prompt; // Move to the next line try self.index(); - self.screen.cursorHorizontalAbsolute(self.scrolling_region.left); + self.screens.active.cursorHorizontalAbsolute(self.scrolling_region.left); if (mark_wrap) { // New line must inherit semantic prompt of the old line - self.screen.cursor.page_row.semantic_prompt = old_prompt; - self.screen.cursor.page_row.wrap_continuation = true; + self.screens.active.cursor.page_row.semantic_prompt = old_prompt; + self.screens.active.cursor.page_row.wrap_continuation = true; } // Assure that our screen is consistent - self.screen.assertIntegrity(); + self.screens.active.assertIntegrity(); } /// Set the charset into the given slot. pub fn configureCharset(self: *Terminal, slot: charsets.Slots, set: charsets.Charset) void { - self.screen.charset.charsets.set(slot, set); + self.screens.active.charset.charsets.set(slot, set); } /// Invoke the charset in slot into the active slot. If single is true, @@ -788,25 +778,25 @@ pub fn invokeCharset( ) void { if (single) { assert(active == .GL); - self.screen.charset.single_shift = slot; + self.screens.active.charset.single_shift = slot; return; } switch (active) { - .GL => self.screen.charset.gl = slot, - .GR => self.screen.charset.gr = slot, + .GL => self.screens.active.charset.gl = slot, + .GR => self.screens.active.charset.gr = slot, } } /// Carriage return moves the cursor to the first column. pub fn carriageReturn(self: *Terminal) void { // Always reset pending wrap state - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; // In origin mode we always move to the left margin - self.screen.cursorHorizontalAbsolute(if (self.modes.get(.origin)) + self.screens.active.cursorHorizontalAbsolute(if (self.modes.get(.origin)) self.scrolling_region.left - else if (self.screen.cursor.x >= self.scrolling_region.left) + else if (self.screens.active.cursor.x >= self.scrolling_region.left) self.scrolling_region.left else 0); @@ -828,17 +818,17 @@ pub fn backspace(self: *Terminal) void { /// 0, adjust it to 1. pub fn cursorUp(self: *Terminal, count_req: usize) void { // Always resets pending wrap - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; // The maximum amount the cursor can move up depends on scrolling regions - const max = if (self.screen.cursor.y >= self.scrolling_region.top) - self.screen.cursor.y - self.scrolling_region.top + const max = if (self.screens.active.cursor.y >= self.scrolling_region.top) + self.screens.active.cursor.y - self.scrolling_region.top else - self.screen.cursor.y; + self.screens.active.cursor.y; const count = @min(max, @max(count_req, 1)); // We can safely intCast below because of the min/max clamping we did above. - self.screen.cursorUp(@intCast(count)); + self.screens.active.cursorUp(@intCast(count)); } /// Move the cursor down amount lines. If amount is greater than the maximum @@ -846,15 +836,15 @@ pub fn cursorUp(self: *Terminal, count_req: usize) void { /// will not scroll the screen or scroll region. If amount is 0, adjust it to 1. pub fn cursorDown(self: *Terminal, count_req: usize) void { // Always resets pending wrap - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; // The max the cursor can move to depends where the cursor currently is - const max = if (self.screen.cursor.y <= self.scrolling_region.bottom) - self.scrolling_region.bottom - self.screen.cursor.y + const max = if (self.screens.active.cursor.y <= self.scrolling_region.bottom) + self.scrolling_region.bottom - self.screens.active.cursor.y else - self.rows - self.screen.cursor.y - 1; + self.rows - self.screens.active.cursor.y - 1; const count = @min(max, @max(count_req, 1)); - self.screen.cursorDown(@intCast(count)); + self.screens.active.cursorDown(@intCast(count)); } /// Move the cursor right amount columns. If amount is greater than the @@ -863,15 +853,15 @@ pub fn cursorDown(self: *Terminal, count_req: usize) void { /// 0, adjust it to 1. pub fn cursorRight(self: *Terminal, count_req: usize) void { // Always resets pending wrap - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; // The max the cursor can move to depends where the cursor currently is - const max = if (self.screen.cursor.x <= self.scrolling_region.right) - self.scrolling_region.right - self.screen.cursor.x + const max = if (self.screens.active.cursor.x <= self.scrolling_region.right) + self.scrolling_region.right - self.screens.active.cursor.x else - self.cols - self.screen.cursor.x - 1; + self.cols - self.screens.active.cursor.x - 1; const count = @min(max, @max(count_req, 1)); - self.screen.cursorRight(@intCast(count)); + self.screens.active.cursorRight(@intCast(count)); } /// Move the cursor to the left amount cells. If amount is 0, adjust it to 1. @@ -890,34 +880,34 @@ pub fn cursorLeft(self: *Terminal, count_req: usize) void { // If we are in no wrap mode, then we move the cursor left and exit // since this is the fastest and most typical path. if (wrap_mode == .none) { - self.screen.cursorLeft(@min(count, self.screen.cursor.x)); - self.screen.cursor.pending_wrap = false; + self.screens.active.cursorLeft(@min(count, self.screens.active.cursor.x)); + self.screens.active.cursor.pending_wrap = false; return; } // If we have a pending wrap state and we are in either reverse wrap // modes then we decrement the amount we move by one to match xterm. - if (self.screen.cursor.pending_wrap) { + if (self.screens.active.cursor.pending_wrap) { count -= 1; - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; } // The margins we can move to. const top = self.scrolling_region.top; const bottom = self.scrolling_region.bottom; const right_margin = self.scrolling_region.right; - const left_margin = if (self.screen.cursor.x < self.scrolling_region.left) + const left_margin = if (self.screens.active.cursor.x < self.scrolling_region.left) 0 else self.scrolling_region.left; // Handle some edge cases when our cursor is already on the left margin. - if (self.screen.cursor.x == left_margin) { + if (self.screens.active.cursor.x == left_margin) { switch (wrap_mode) { // In reverse mode, if we're already before the top margin // then we just set our cursor to the top-left and we're done. - .reverse => if (self.screen.cursor.y <= top) { - self.screen.cursorAbsolute(left_margin, top); + .reverse => if (self.screens.active.cursor.y <= top) { + self.screens.active.cursorAbsolute(left_margin, top); return; }, @@ -931,22 +921,22 @@ pub fn cursorLeft(self: *Terminal, count_req: usize) void { while (true) { // We can move at most to the left margin. - const max = self.screen.cursor.x - left_margin; + const max = self.screens.active.cursor.x - left_margin; // We want to move at most the number of columns we have left // or our remaining count. Do the move. const amount = @min(max, count); count -= amount; - self.screen.cursorLeft(amount); + self.screens.active.cursorLeft(amount); // If we have no more to move, then we're done. if (count == 0) break; // If we are at the top, then we are done. - if (self.screen.cursor.y == top) { + if (self.screens.active.cursor.y == top) { if (wrap_mode != .reverse_extended) break; - self.screen.cursorAbsolute(right_margin, bottom); + self.screens.active.cursorAbsolute(right_margin, bottom); count -= 1; continue; } @@ -958,18 +948,18 @@ pub fn cursorLeft(self: *Terminal, count_req: usize) void { // up to the (0, 0) and stopping there. My reasoning is that for an // appropriately sized value of "count" this is the behavior that xterm // would have. This is unit tested. - if (self.screen.cursor.y == 0) { - assert(self.screen.cursor.x == left_margin); + if (self.screens.active.cursor.y == 0) { + assert(self.screens.active.cursor.x == left_margin); break; } // If our previous line is not wrapped then we are done. if (wrap_mode != .reverse_extended) { - const prev_row = self.screen.cursorRowUp(1); + const prev_row = self.screens.active.cursorRowUp(1); if (!prev_row.wrap) break; } - self.screen.cursorAbsolute(right_margin, self.screen.cursor.y - 1); + self.screens.active.cursorAbsolute(right_margin, self.screens.active.cursor.y - 1); count -= 1; } } @@ -980,14 +970,14 @@ pub fn cursorLeft(self: *Terminal, count_req: usize) void { /// is kept per screen (main / alternative). If for the current screen state /// was already saved it is overwritten. pub fn saveCursor(self: *Terminal) void { - self.screen.saved_cursor = .{ - .x = self.screen.cursor.x, - .y = self.screen.cursor.y, - .style = self.screen.cursor.style, - .protected = self.screen.cursor.protected, - .pending_wrap = self.screen.cursor.pending_wrap, + self.screens.active.saved_cursor = .{ + .x = self.screens.active.cursor.x, + .y = self.screens.active.cursor.y, + .style = self.screens.active.cursor.style, + .protected = self.screens.active.cursor.protected, + .pending_wrap = self.screens.active.cursor.pending_wrap, .origin = self.modes.get(.origin), - .charset = self.screen.charset, + .charset = self.screens.active.charset, }; } @@ -996,7 +986,7 @@ pub fn saveCursor(self: *Terminal) void { /// The primary and alternate screen have distinct save state. /// If no save was done before values are reset to their initial values. pub fn restoreCursor(self: *Terminal) !void { - const saved: Screen.SavedCursor = self.screen.saved_cursor orelse .{ + const saved: Screen.SavedCursor = self.screens.active.saved_cursor orelse .{ .x = 0, .y = 0, .style = .{}, @@ -1007,29 +997,29 @@ pub fn restoreCursor(self: *Terminal) !void { }; // Set the style first because it can fail - const old_style = self.screen.cursor.style; - self.screen.cursor.style = saved.style; - errdefer self.screen.cursor.style = old_style; - try self.screen.manualStyleUpdate(); + const old_style = self.screens.active.cursor.style; + self.screens.active.cursor.style = saved.style; + errdefer self.screens.active.cursor.style = old_style; + try self.screens.active.manualStyleUpdate(); - self.screen.charset = saved.charset; + self.screens.active.charset = saved.charset; self.modes.set(.origin, saved.origin); - self.screen.cursor.pending_wrap = saved.pending_wrap; - self.screen.cursor.protected = saved.protected; - self.screen.cursorAbsolute( + self.screens.active.cursor.pending_wrap = saved.pending_wrap; + self.screens.active.cursor.protected = saved.protected; + self.screens.active.cursorAbsolute( @min(saved.x, self.cols - 1), @min(saved.y, self.rows - 1), ); // Ensure our screen is consistent - self.screen.assertIntegrity(); + self.screens.active.assertIntegrity(); } /// Set the character protection mode for the terminal. pub fn setProtectedMode(self: *Terminal, mode: ansi.ProtectedMode) void { switch (mode) { .off => { - self.screen.cursor.protected = false; + self.screens.active.cursor.protected = false; // screen.protected_mode is NEVER reset to ".off" because // logic such as eraseChars depends on knowing what the @@ -1037,13 +1027,13 @@ pub fn setProtectedMode(self: *Terminal, mode: ansi.ProtectedMode) void { }, .iso => { - self.screen.cursor.protected = true; - self.screen.protected_mode = .iso; + self.screens.active.cursor.protected = true; + self.screens.active.protected_mode = .iso; }, .dec => { - self.screen.cursor.protected = true; - self.screen.protected_mode = .dec; + self.screens.active.cursor.protected = true; + self.screens.active.protected_mode = .dec; }, } } @@ -1064,8 +1054,8 @@ pub const SemanticPrompt = enum { /// (OSC 133) only allow setting this for wherever the current active cursor /// is located. pub fn markSemanticPrompt(self: *Terminal, p: SemanticPrompt) void { - //log.debug("semantic_prompt y={} p={}", .{ self.screen.cursor.y, p }); - self.screen.cursor.page_row.semantic_prompt = switch (p) { + //log.debug("semantic_prompt y={} p={}", .{ self.screens.active.cursor.y, p }); + self.screens.active.cursor.page_row.semantic_prompt = switch (p) { .prompt => .prompt, .prompt_continuation => .prompt_continuation, .input => .input, @@ -1083,12 +1073,12 @@ pub fn cursorIsAtPrompt(self: *Terminal) bool { if (self.screens.active_key == .alternate) return false; // Reverse through the active - const start_x, const start_y = .{ self.screen.cursor.x, self.screen.cursor.y }; - defer self.screen.cursorAbsolute(start_x, start_y); + const start_x, const start_y = .{ self.screens.active.cursor.x, self.screens.active.cursor.y }; + defer self.screens.active.cursorAbsolute(start_x, start_y); for (0..start_y + 1) |i| { - if (i > 0) self.screen.cursorUp(1); - switch (self.screen.cursor.page_row.semantic_prompt) { + if (i > 0) self.screens.active.cursorUp(1); + switch (self.screens.active.cursor.page_row.semantic_prompt) { // If we're at a prompt or input area, then we are at a prompt. .prompt, .prompt_continuation, @@ -1110,14 +1100,14 @@ pub fn cursorIsAtPrompt(self: *Terminal) bool { /// Horizontal tab moves the cursor to the next tabstop, clearing /// the screen to the left the tabstop. pub fn horizontalTab(self: *Terminal) !void { - while (self.screen.cursor.x < self.scrolling_region.right) { + while (self.screens.active.cursor.x < self.scrolling_region.right) { // Move the cursor right - self.screen.cursorRight(1); + self.screens.active.cursorRight(1); // If the last cursor position was a tabstop we return. We do // "last cursor position" because we want a space to be written // at the tabstop unless we're at the end (the while condition). - if (self.tabstops.get(self.screen.cursor.x)) return; + if (self.tabstops.get(self.screens.active.cursor.x)) return; } } @@ -1128,18 +1118,18 @@ pub fn horizontalTabBack(self: *Terminal) !void { while (true) { // If we're already at the edge of the screen, then we're done. - if (self.screen.cursor.x <= left_limit) return; + if (self.screens.active.cursor.x <= left_limit) return; // Move the cursor left - self.screen.cursorLeft(1); - if (self.tabstops.get(self.screen.cursor.x)) return; + self.screens.active.cursorLeft(1); + if (self.tabstops.get(self.screens.active.cursor.x)) return; } } /// Clear tab stops. pub fn tabClear(self: *Terminal, cmd: csi.TabClear) void { switch (cmd) { - .current => self.tabstops.unset(self.screen.cursor.x), + .current => self.tabstops.unset(self.screens.active.cursor.x), .all => self.tabstops.reset(0), else => log.warn("invalid or unknown tab clear setting: {}", .{cmd}), } @@ -1148,7 +1138,7 @@ pub fn tabClear(self: *Terminal, cmd: csi.TabClear) void { /// Set a tab stop on the current cursor. /// TODO: test pub fn tabSet(self: *Terminal) void { - self.tabstops.set(self.screen.cursor.x); + self.tabstops.set(self.screens.active.cursor.x); } /// TODO: test @@ -1170,16 +1160,16 @@ pub fn tabReset(self: *Terminal) void { /// This unsets the pending wrap state without wrapping. pub fn index(self: *Terminal) !void { // Unset pending wrap state - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; // Outside of the scroll region we move the cursor one line down. - if (self.screen.cursor.y < self.scrolling_region.top or - self.screen.cursor.y > self.scrolling_region.bottom) + if (self.screens.active.cursor.y < self.scrolling_region.top or + self.screens.active.cursor.y > self.scrolling_region.bottom) { // We only move down if we're not already at the bottom of // the screen. - if (self.screen.cursor.y < self.rows - 1) { - self.screen.cursorDown(1); + if (self.screens.active.cursor.y < self.rows - 1) { + self.screens.active.cursorDown(1); } return; @@ -1188,13 +1178,13 @@ pub fn index(self: *Terminal) !void { // If the cursor is inside the scrolling region and on the bottom-most // line, then we scroll up. If our scrolling region is the full screen // we create scrollback. - if (self.screen.cursor.y == self.scrolling_region.bottom and - self.screen.cursor.x >= self.scrolling_region.left and - self.screen.cursor.x <= self.scrolling_region.right) + if (self.screens.active.cursor.y == self.scrolling_region.bottom and + self.screens.active.cursor.x >= self.scrolling_region.left and + self.screens.active.cursor.x <= self.scrolling_region.right) { if (comptime build_options.kitty_graphics) { // Scrolling dirties the images because it updates their placements pins. - self.screen.kitty_images.dirty = true; + self.screens.active.kitty_images.dirty = true; } // If our scrolling region is at the top, we create scrollback. @@ -1202,7 +1192,7 @@ pub fn index(self: *Terminal) !void { self.scrolling_region.left == 0 and self.scrolling_region.right == self.cols - 1) { - try self.screen.cursorScrollAbove(); + try self.screens.active.cursorScrollAbove(); return; } @@ -1216,7 +1206,7 @@ pub fn index(self: *Terminal) !void { // However, scrollUp is WAY slower. We should optimize this // case to work in the eraseRowBounded codepath and remove // this check. - !self.screen.blankCell().isZero()) + !self.screens.active.blankCell().isZero()) { self.scrollUp(1); return; @@ -1226,9 +1216,9 @@ pub fn index(self: *Terminal) !void { // scroll the contents of the scrolling region. // Preserve old cursor just for assertions - const old_cursor = self.screen.cursor; + const old_cursor = self.screens.active.cursor; - try self.screen.pages.eraseRowBounded( + try self.screens.active.pages.eraseRowBounded( .{ .active = .{ .y = self.scrolling_region.top } }, self.scrolling_region.bottom - self.scrolling_region.top, ); @@ -1237,26 +1227,26 @@ pub fn index(self: *Terminal) !void { // up by 1, so we need to move it back down. A `cursorReload` // would be better option but this is more efficient and this is // a super hot path so we do this instead. - assert(self.screen.cursor.x == old_cursor.x); - assert(self.screen.cursor.y == old_cursor.y); - self.screen.cursor.y -= 1; - self.screen.cursorDown(1); + assert(self.screens.active.cursor.x == old_cursor.x); + assert(self.screens.active.cursor.y == old_cursor.y); + self.screens.active.cursor.y -= 1; + self.screens.active.cursorDown(1); // The operations above can prune our cursor style so we need to // update. This should never fail because the above can only FREE // memory. - self.screen.manualStyleUpdate() catch |err| { + self.screens.active.manualStyleUpdate() catch |err| { std.log.warn("deleteLines manualStyleUpdate err={}", .{err}); - self.screen.cursor.style = .{}; - self.screen.manualStyleUpdate() catch unreachable; + self.screens.active.cursor.style = .{}; + self.screens.active.manualStyleUpdate() catch unreachable; }; return; } // Increase cursor by 1, maximum to bottom of scroll region - if (self.screen.cursor.y < self.scrolling_region.bottom) { - self.screen.cursorDown(1); + if (self.screens.active.cursor.y < self.scrolling_region.bottom) { + self.screens.active.cursorDown(1); } } @@ -1273,9 +1263,9 @@ pub fn index(self: *Terminal) !void { /// * If the cursor is not on the top-most line of the scrolling region: /// move the cursor one line up pub fn reverseIndex(self: *Terminal) void { - if (self.screen.cursor.y != self.scrolling_region.top or - self.screen.cursor.x < self.scrolling_region.left or - self.screen.cursor.x > self.scrolling_region.right) + if (self.screens.active.cursor.y != self.scrolling_region.top or + self.screens.active.cursor.x < self.scrolling_region.left or + self.screens.active.cursor.x > self.scrolling_region.right) { self.cursorUp(1); return; @@ -1314,7 +1304,7 @@ pub fn setCursorPos(self: *Terminal, row_req: usize, col_req: usize) void { }; // Unset pending wrap state - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; // Calculate our new x/y const row = if (row_req == 0) 1 else row_req; @@ -1323,19 +1313,19 @@ pub fn setCursorPos(self: *Terminal, row_req: usize, col_req: usize) void { const y = @min(params.y_max, row + params.y_offset) -| 1; // If the y is unchanged then this is fast pointer math - if (y == self.screen.cursor.y) { - if (x > self.screen.cursor.x) { - self.screen.cursorRight(x - self.screen.cursor.x); + if (y == self.screens.active.cursor.y) { + if (x > self.screens.active.cursor.x) { + self.screens.active.cursorRight(x - self.screens.active.cursor.x); } else { - self.screen.cursorLeft(self.screen.cursor.x - x); + self.screens.active.cursorLeft(self.screens.active.cursor.x - x); } return; } // If everything changed we do an absolute change which is slightly slower - self.screen.cursorAbsolute(x, y); - // log.info("set cursor position: col={} row={}", .{ self.screen.cursor.x, self.screen.cursor.y }); + self.screens.active.cursorAbsolute(x, y); + // log.info("set cursor position: col={} row={}", .{ self.screens.active.cursor.x, self.screens.active.cursor.y }); } /// Set Top and Bottom Margins If bottom is not specified, 0 or bigger than @@ -1377,16 +1367,16 @@ pub fn setLeftAndRightMargin(self: *Terminal, left_req: usize, right_req: usize) /// Scroll the text down by one row. pub fn scrollDown(self: *Terminal, count: usize) void { // Preserve our x/y to restore. - const old_x = self.screen.cursor.x; - const old_y = self.screen.cursor.y; - const old_wrap = self.screen.cursor.pending_wrap; + const old_x = self.screens.active.cursor.x; + const old_y = self.screens.active.cursor.y; + const old_wrap = self.screens.active.cursor.pending_wrap; defer { - self.screen.cursorAbsolute(old_x, old_y); - self.screen.cursor.pending_wrap = old_wrap; + self.screens.active.cursorAbsolute(old_x, old_y); + self.screens.active.cursor.pending_wrap = old_wrap; } // Move to the top of the scroll region - self.screen.cursorAbsolute(self.scrolling_region.left, self.scrolling_region.top); + self.screens.active.cursorAbsolute(self.scrolling_region.left, self.scrolling_region.top); self.insertLines(count); } @@ -1399,16 +1389,16 @@ pub fn scrollDown(self: *Terminal, count: usize) void { /// Does not change the (absolute) cursor position. pub fn scrollUp(self: *Terminal, count: usize) void { // Preserve our x/y to restore. - const old_x = self.screen.cursor.x; - const old_y = self.screen.cursor.y; - const old_wrap = self.screen.cursor.pending_wrap; + const old_x = self.screens.active.cursor.x; + const old_y = self.screens.active.cursor.y; + const old_wrap = self.screens.active.cursor.pending_wrap; defer { - self.screen.cursorAbsolute(old_x, old_y); - self.screen.cursor.pending_wrap = old_wrap; + self.screens.active.cursorAbsolute(old_x, old_y); + self.screens.active.cursor.pending_wrap = old_wrap; } // Move to the top of the scroll region - self.screen.cursorAbsolute(self.scrolling_region.left, self.scrolling_region.top); + self.screens.active.cursorAbsolute(self.scrolling_region.left, self.scrolling_region.top); self.deleteLines(count); } @@ -1426,7 +1416,7 @@ pub const ScrollViewport = union(enum) { /// Scroll the viewport of the terminal grid. pub fn scrollViewport(self: *Terminal, behavior: ScrollViewport) !void { - self.screen.scroll(switch (behavior) { + self.screens.active.scroll(switch (behavior) { .top => .{ .top = {} }, .bottom => .{ .active = {} }, .delta => |delta| .{ .delta_row = delta }, @@ -1518,23 +1508,23 @@ pub fn insertLines(self: *Terminal, count: usize) void { if (count == 0) return; // If the cursor is outside the scroll region we do nothing. - if (self.screen.cursor.y < self.scrolling_region.top or - self.screen.cursor.y > self.scrolling_region.bottom or - self.screen.cursor.x < self.scrolling_region.left or - self.screen.cursor.x > self.scrolling_region.right) return; + if (self.screens.active.cursor.y < self.scrolling_region.top or + self.screens.active.cursor.y > self.scrolling_region.bottom or + self.screens.active.cursor.x < self.scrolling_region.left or + self.screens.active.cursor.x > self.scrolling_region.right) return; if (comptime build_options.kitty_graphics) { // Scrolling dirties the images because it updates their placements pins. - self.screen.kitty_images.dirty = true; + self.screens.active.kitty_images.dirty = true; } // At the end we need to return the cursor to the row it started on. - const start_y = self.screen.cursor.y; + const start_y = self.screens.active.cursor.y; defer { - self.screen.cursorAbsolute(self.scrolling_region.left, start_y); + self.screens.active.cursorAbsolute(self.scrolling_region.left, start_y); // Always unset pending wrap - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; } // We have a slower path if we have left or right scroll margins. @@ -1542,7 +1532,7 @@ pub fn insertLines(self: *Terminal, count: usize) void { self.scrolling_region.right < self.cols - 1; // Remaining rows from our cursor to the bottom of the scroll region. - const rem = self.scrolling_region.bottom - self.screen.cursor.y + 1; + const rem = self.scrolling_region.bottom - self.screens.active.cursor.y + 1; // We can only insert lines up to our remaining lines in the scroll // region. So we take whichever is smaller. @@ -1550,8 +1540,8 @@ pub fn insertLines(self: *Terminal, count: usize) void { // Create a new tracked pin which we'll use to navigate the page list // so that if we need to adjust capacity it will be properly tracked. - var cur_p = self.screen.pages.trackPin( - self.screen.cursor.page_pin.down(rem - 1).?, + var cur_p = self.screens.active.pages.trackPin( + self.screens.active.cursor.page_pin.down(rem - 1).?, ) catch |err| { comptime assert(@TypeOf(err) == error{OutOfMemory}); @@ -1563,7 +1553,7 @@ pub fn insertLines(self: *Terminal, count: usize) void { log.err("insertLines trackPin error err={}", .{err}); @panic("insertLines trackPin OOM"); }; - defer self.screen.pages.untrackPin(cur_p); + defer self.screens.active.pages.untrackPin(cur_p); // Our current y position relative to the cursor var y: usize = rem; @@ -1611,7 +1601,7 @@ pub fn insertLines(self: *Terminal, count: usize) void { const cap = dst_p.node.data.capacity; // Adjust our page capacity to make // room for we didn't have space for - _ = self.screen.adjustCapacity( + _ = self.screens.active.adjustCapacity( dst_p.node, switch (err) { // Rehash the sets @@ -1689,7 +1679,7 @@ pub fn insertLines(self: *Terminal, count: usize) void { // Clear the cells for this row, it has been shifted. const page = &cur_p.node.data; const cells = page.getCells(cur_row); - self.screen.clearCells( + self.screens.active.clearCells( page, cur_row, cells[self.scrolling_region.left .. self.scrolling_region.right + 1], @@ -1724,22 +1714,22 @@ pub fn deleteLines(self: *Terminal, count: usize) void { if (count == 0) return; // If the cursor is outside the scroll region we do nothing. - if (self.screen.cursor.y < self.scrolling_region.top or - self.screen.cursor.y > self.scrolling_region.bottom or - self.screen.cursor.x < self.scrolling_region.left or - self.screen.cursor.x > self.scrolling_region.right) return; + if (self.screens.active.cursor.y < self.scrolling_region.top or + self.screens.active.cursor.y > self.scrolling_region.bottom or + self.screens.active.cursor.x < self.scrolling_region.left or + self.screens.active.cursor.x > self.scrolling_region.right) return; if (comptime build_options.kitty_graphics) { // Scrolling dirties the images because it updates their placements pins. - self.screen.kitty_images.dirty = true; + self.screens.active.kitty_images.dirty = true; } // At the end we need to return the cursor to the row it started on. - const start_y = self.screen.cursor.y; + const start_y = self.screens.active.cursor.y; defer { - self.screen.cursorAbsolute(self.scrolling_region.left, start_y); + self.screens.active.cursorAbsolute(self.scrolling_region.left, start_y); // Always unset pending wrap - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; } // We have a slower path if we have left or right scroll margins. @@ -1747,7 +1737,7 @@ pub fn deleteLines(self: *Terminal, count: usize) void { self.scrolling_region.right < self.cols - 1; // Remaining rows from our cursor to the bottom of the scroll region. - const rem = self.scrolling_region.bottom - self.screen.cursor.y + 1; + const rem = self.scrolling_region.bottom - self.screens.active.cursor.y + 1; // We can only insert lines up to our remaining lines in the scroll // region. So we take whichever is smaller. @@ -1755,15 +1745,15 @@ pub fn deleteLines(self: *Terminal, count: usize) void { // Create a new tracked pin which we'll use to navigate the page list // so that if we need to adjust capacity it will be properly tracked. - var cur_p = self.screen.pages.trackPin( - self.screen.cursor.page_pin.*, + var cur_p = self.screens.active.pages.trackPin( + self.screens.active.cursor.page_pin.*, ) catch |err| { // See insertLines comptime assert(@TypeOf(err) == error{OutOfMemory}); log.err("deleteLines trackPin error err={}", .{err}); @panic("deleteLines trackPin OOM"); }; - defer self.screen.pages.untrackPin(cur_p); + defer self.screens.active.pages.untrackPin(cur_p); // Our current y position relative to the cursor var y: usize = 0; @@ -1811,7 +1801,7 @@ pub fn deleteLines(self: *Terminal, count: usize) void { const cap = dst_p.node.data.capacity; // Adjust our page capacity to make // room for we didn't have space for - _ = self.screen.adjustCapacity( + _ = self.screens.active.adjustCapacity( dst_p.node, switch (err) { // Rehash the sets @@ -1884,7 +1874,7 @@ pub fn deleteLines(self: *Terminal, count: usize) void { // Clear the cells for this row, it's from out of bounds. const page = &cur_p.node.data; const cells = page.getCells(cur_row); - self.screen.clearCells( + self.screens.active.clearCells( page, cur_row, cells[self.scrolling_region.left .. self.scrolling_region.right + 1], @@ -1909,35 +1899,35 @@ pub fn insertBlanks(self: *Terminal, count: usize) void { // Unset pending wrap state without wrapping. Note: this purposely // happens BEFORE the scroll region check below, because that's what // xterm does. - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; // If our cursor is outside the margins then do nothing. We DO reset // wrap state still so this must remain below the above logic. - if (self.screen.cursor.x < self.scrolling_region.left or - self.screen.cursor.x > self.scrolling_region.right) return; + if (self.screens.active.cursor.x < self.scrolling_region.left or + self.screens.active.cursor.x > self.scrolling_region.right) return; // If our count is larger than the remaining amount, we just erase right. // We only do this if we can erase the entire line (no right margin). // if (right_limit == self.cols and - // count > right_limit - self.screen.cursor.x) + // count > right_limit - self.screens.active.cursor.x) // { // self.eraseLine(.right, false); // return; // } // left is just the cursor position but as a multi-pointer - const left: [*]Cell = @ptrCast(self.screen.cursor.page_cell); - var page = &self.screen.cursor.page_pin.node.data; + const left: [*]Cell = @ptrCast(self.screens.active.cursor.page_cell); + var page = &self.screens.active.cursor.page_pin.node.data; // If our X is a wide spacer tail then we need to erase the // previous cell too so we don't split a multi-cell character. - if (self.screen.cursor.page_cell.wide == .spacer_tail) { - assert(self.screen.cursor.x > 0); - self.screen.clearCells(page, self.screen.cursor.page_row, (left - 1)[0..2]); + if (self.screens.active.cursor.page_cell.wide == .spacer_tail) { + assert(self.screens.active.cursor.x > 0); + self.screens.active.clearCells(page, self.screens.active.cursor.page_row, (left - 1)[0..2]); } // Remaining cols from our cursor to the right margin. - const rem = self.scrolling_region.right - self.screen.cursor.x + 1; + const rem = self.scrolling_region.right - self.screens.active.cursor.x + 1; // We can only insert blanks up to our remaining cols const adjusted_count = @min(count, rem); @@ -1958,9 +1948,9 @@ pub fn insertBlanks(self: *Terminal, count: usize) void { if (end.wide == .wide) { const end_multi: [*]Cell = @ptrCast(end); assert(end_multi[1].wide == .spacer_tail); - self.screen.clearCells( + self.screens.active.clearCells( page, - self.screen.cursor.page_row, + self.screens.active.cursor.page_row, end_multi[0..2], ); } @@ -1974,10 +1964,10 @@ pub fn insertBlanks(self: *Terminal, count: usize) void { } // Insert blanks. The blanks preserve the background color. - self.screen.clearCells(page, self.screen.cursor.page_row, left[0..adjusted_count]); + self.screens.active.clearCells(page, self.screens.active.cursor.page_row, left[0..adjusted_count]); // Our row is always dirty - self.screen.cursorMarkDirty(); + self.screens.active.cursorMarkDirty(); } /// Removes amount characters from the current cursor position to the right. @@ -1993,22 +1983,22 @@ pub fn deleteChars(self: *Terminal, count_req: usize) void { // If our cursor is outside the margins then do nothing. We DO reset // wrap state still so this must remain below the above logic. - if (self.screen.cursor.x < self.scrolling_region.left or - self.screen.cursor.x > self.scrolling_region.right) return; + if (self.screens.active.cursor.x < self.scrolling_region.left or + self.screens.active.cursor.x > self.scrolling_region.right) return; // left is just the cursor position but as a multi-pointer - const left: [*]Cell = @ptrCast(self.screen.cursor.page_cell); - var page = &self.screen.cursor.page_pin.node.data; + const left: [*]Cell = @ptrCast(self.screens.active.cursor.page_cell); + var page = &self.screens.active.cursor.page_pin.node.data; // Remaining cols from our cursor to the right margin. - const rem = self.scrolling_region.right - self.screen.cursor.x + 1; + const rem = self.scrolling_region.right - self.screens.active.cursor.x + 1; // We can only insert blanks up to our remaining cols const count = @min(count_req, rem); - self.screen.splitCellBoundary(self.screen.cursor.x); - self.screen.splitCellBoundary(self.screen.cursor.x + count); - self.screen.splitCellBoundary(self.scrolling_region.right + 1); + self.screens.active.splitCellBoundary(self.screens.active.cursor.x); + self.screens.active.splitCellBoundary(self.screens.active.cursor.x + count); + self.screens.active.splitCellBoundary(self.scrolling_region.right + 1); // This is the amount of space at the right of the scroll region // that will NOT be blank, so we need to shift the correct cols right. @@ -2029,24 +2019,24 @@ pub fn deleteChars(self: *Terminal, count_req: usize) void { } // Insert blanks. The blanks preserve the background color. - self.screen.clearCells(page, self.screen.cursor.page_row, x[0 .. rem - scroll_amount]); + self.screens.active.clearCells(page, self.screens.active.cursor.page_row, x[0 .. rem - scroll_amount]); // Our row's soft-wrap is always reset. - self.screen.cursorResetWrap(); + self.screens.active.cursorResetWrap(); // Our row is always dirty - self.screen.cursorMarkDirty(); + self.screens.active.cursorMarkDirty(); } pub fn eraseChars(self: *Terminal, count_req: usize) void { const count = end: { - const remaining = self.cols - self.screen.cursor.x; + const remaining = self.cols - self.screens.active.cursor.x; var end = @min(remaining, @max(count_req, 1)); // If our last cell is a wide char then we need to also clear the // cell beyond it since we can't just split a wide char. if (end != remaining) { - const last = self.screen.cursorCellRight(end - 1); + const last = self.screens.active.cursorCellRight(end - 1); if (last.wide == .wide) end += 1; } @@ -2058,33 +2048,33 @@ pub fn eraseChars(self: *Terminal, count_req: usize) void { // TODO(qwerasd): This isn't actually correct if you take in to account // protected modes. We need to figure out how to make `clearCells` or at // least `clearUnprotectedCells` handle boundary conditions... - self.screen.splitCellBoundary(self.screen.cursor.x); - self.screen.splitCellBoundary(self.screen.cursor.x + count); + self.screens.active.splitCellBoundary(self.screens.active.cursor.x); + self.screens.active.splitCellBoundary(self.screens.active.cursor.x + count); // Reset our row's soft-wrap. - self.screen.cursorResetWrap(); + self.screens.active.cursorResetWrap(); // Mark our cursor row as dirty - self.screen.cursorMarkDirty(); + self.screens.active.cursorMarkDirty(); // Clear the cells - const cells: [*]Cell = @ptrCast(self.screen.cursor.page_cell); + const cells: [*]Cell = @ptrCast(self.screens.active.cursor.page_cell); // If we never had a protection mode, then we can assume no cells // are protected and go with the fast path. If the last protection // mode was not ISO we also always ignore protection attributes. - if (self.screen.protected_mode != .iso) { - self.screen.clearCells( - &self.screen.cursor.page_pin.node.data, - self.screen.cursor.page_row, + if (self.screens.active.protected_mode != .iso) { + self.screens.active.clearCells( + &self.screens.active.cursor.page_pin.node.data, + self.screens.active.cursor.page_row, cells[0..count], ); return; } - self.screen.clearUnprotectedCells( - &self.screen.cursor.page_pin.node.data, - self.screen.cursor.page_row, + self.screens.active.clearUnprotectedCells( + &self.screens.active.cursor.page_pin.node.data, + self.screens.active.cursor.page_row, cells[0..count], ); } @@ -2098,25 +2088,25 @@ pub fn eraseLine( // Get our start/end positions depending on mode. const start, const end = switch (mode) { .right => right: { - var x = self.screen.cursor.x; + var x = self.screens.active.cursor.x; // If our X is a wide spacer tail then we need to erase the // previous cell too so we don't split a multi-cell character. - if (x > 0 and self.screen.cursor.page_cell.wide == .spacer_tail) { + if (x > 0 and self.screens.active.cursor.page_cell.wide == .spacer_tail) { x -= 1; } // Reset our row's soft-wrap. - self.screen.cursorResetWrap(); + self.screens.active.cursorResetWrap(); break :right .{ x, self.cols }; }, .left => left: { - var x = self.screen.cursor.x; + var x = self.screens.active.cursor.x; // If our x is a wide char we need to delete the tail too. - if (self.screen.cursor.page_cell.wide == .wide) { + if (self.screens.active.cursor.page_cell.wide == .wide) { x += 1; } @@ -2135,36 +2125,36 @@ pub fn eraseLine( // All modes will clear the pending wrap state and we know we have // a valid mode at this point. - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; // We always mark our row as dirty - self.screen.cursorMarkDirty(); + self.screens.active.cursorMarkDirty(); // Start of our cells const cells: [*]Cell = cells: { - const cells: [*]Cell = @ptrCast(self.screen.cursor.page_cell); - break :cells cells - self.screen.cursor.x; + const cells: [*]Cell = @ptrCast(self.screens.active.cursor.page_cell); + break :cells cells - self.screens.active.cursor.x; }; // We respect protected attributes if explicitly requested (probably // a DECSEL sequence) or if our last protected mode was ISO even if its // not currently set. - const protected = self.screen.protected_mode == .iso or protected_req; + const protected = self.screens.active.protected_mode == .iso or protected_req; // If we're not respecting protected attributes, we can use a fast-path // to fill the entire line. if (!protected) { - self.screen.clearCells( - &self.screen.cursor.page_pin.node.data, - self.screen.cursor.page_row, + self.screens.active.clearCells( + &self.screens.active.cursor.page_pin.node.data, + self.screens.active.cursor.page_row, cells[start..end], ); return; } - self.screen.clearUnprotectedCells( - &self.screen.cursor.page_pin.node.data, - self.screen.cursor.page_row, + self.screens.active.clearUnprotectedCells( + &self.screens.active.cursor.page_pin.node.data, + self.screens.active.cursor.page_row, cells[start..end], ); } @@ -2178,23 +2168,23 @@ pub fn eraseDisplay( // We respect protected attributes if explicitly requested (probably // a DECSEL sequence) or if our last protected mode was ISO even if its // not currently set. - const protected = self.screen.protected_mode == .iso or protected_req; + const protected = self.screens.active.protected_mode == .iso or protected_req; switch (mode) { .scroll_complete => { - self.screen.scrollClear() catch |err| { + self.screens.active.scrollClear() catch |err| { log.warn("scroll clear failed, doing a normal clear err={}", .{err}); self.eraseDisplay(.complete, protected_req); return; }; // Unsets pending wrap state - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; if (comptime build_options.kitty_graphics) { // Clear all Kitty graphics state for this screen - self.screen.kitty_images.delete( - self.screen.alloc, + self.screens.active.kitty_images.delete( + self.screens.active.alloc, self, .{ .all = true }, ); @@ -2211,12 +2201,12 @@ pub fn eraseDisplay( if (self.screens.active_key == .primary) at_prompt: { // Go from the bottom of the active up and see if we're // at a prompt. - const active_br = self.screen.pages.getBottomRight( + const active_br = self.screens.active.pages.getBottomRight( .active, ) orelse break :at_prompt; var it = active_br.rowIterator( .left_up, - self.screen.pages.getTopLeft(.active), + self.screens.active.pages.getTopLeft(.active), ); while (it.next()) |p| { const row = p.rowAndCell().row; @@ -2236,26 +2226,26 @@ pub fn eraseDisplay( } } else break :at_prompt; - self.screen.scrollClear() catch { + self.screens.active.scrollClear() catch { // If we fail, we just fall back to doing a normal clear // so we don't worry about the error. }; } // All active area - self.screen.clearRows( + self.screens.active.clearRows( .{ .active = .{} }, null, protected, ); // Unsets pending wrap state - self.screen.cursor.pending_wrap = false; + self.screens.active.cursor.pending_wrap = false; if (comptime build_options.kitty_graphics) { // Clear all Kitty graphics state for this screen - self.screen.kitty_images.delete( - self.screen.alloc, + self.screens.active.kitty_images.delete( + self.screens.active.alloc, self, .{ .all = true }, ); @@ -2270,16 +2260,16 @@ pub fn eraseDisplay( self.eraseLine(.right, protected_req); // All lines below - if (self.screen.cursor.y + 1 < self.rows) { - self.screen.clearRows( - .{ .active = .{ .y = self.screen.cursor.y + 1 } }, + if (self.screens.active.cursor.y + 1 < self.rows) { + self.screens.active.clearRows( + .{ .active = .{ .y = self.screens.active.cursor.y + 1 } }, null, protected, ); } // Unsets pending wrap state. Should be done by eraseLine. - assert(!self.screen.cursor.pending_wrap); + assert(!self.screens.active.cursor.pending_wrap); }, .above => { @@ -2287,19 +2277,19 @@ pub fn eraseDisplay( self.eraseLine(.left, protected_req); // All lines above - if (self.screen.cursor.y > 0) { - self.screen.clearRows( + if (self.screens.active.cursor.y > 0) { + self.screens.active.clearRows( .{ .active = .{ .y = 0 } }, - .{ .active = .{ .y = self.screen.cursor.y - 1 } }, + .{ .active = .{ .y = self.screens.active.cursor.y - 1 } }, protected, ); } // Unsets pending wrap state - assert(!self.screen.cursor.pending_wrap); + assert(!self.screens.active.cursor.pending_wrap); }, - .scrollback => self.screen.eraseRows(.{ .history = .{} }, null), + .scrollback => self.screens.active.eraseRows(.{ .history = .{} }, null), } } @@ -2309,13 +2299,13 @@ pub fn eraseDisplay( pub fn decaln(self: *Terminal) !void { // Clear our stylistic attributes. This is the only thing that can // fail so we do it first so we can undo it. - const old_style = self.screen.cursor.style; - self.screen.cursor.style = .{ - .bg_color = self.screen.cursor.style.bg_color, - .fg_color = self.screen.cursor.style.fg_color, + const old_style = self.screens.active.cursor.style; + self.screens.active.cursor.style = .{ + .bg_color = self.screens.active.cursor.style.bg_color, + .fg_color = self.screens.active.cursor.style.fg_color, }; - errdefer self.screen.cursor.style = old_style; - try self.screen.manualStyleUpdate(); + errdefer self.screens.active.cursor.style = old_style; + try self.screens.active.manualStyleUpdate(); // Reset margins, also sets cursor to top-left self.scrolling_region = .{ @@ -2333,7 +2323,7 @@ pub fn decaln(self: *Terminal) !void { // Use clearRows instead of eraseDisplay because we must NOT respect // protected attributes here. - self.screen.clearRows( + self.screens.active.clearRows( .{ .active = .{} }, null, false, @@ -2341,24 +2331,24 @@ pub fn decaln(self: *Terminal) !void { // Fill with Es by moving the cursor but reset it after. while (true) { - const page = &self.screen.cursor.page_pin.node.data; - const row = self.screen.cursor.page_row; + const page = &self.screens.active.cursor.page_pin.node.data; + const row = self.screens.active.cursor.page_row; const cells_multi: [*]Cell = row.cells.ptr(page.memory); const cells = cells_multi[0..page.size.cols]; @memset(cells, .{ .content_tag = .codepoint, .content = .{ .codepoint = 'E' }, - .style_id = self.screen.cursor.style_id, + .style_id = self.screens.active.cursor.style_id, // DECALN does not respect protected state. Verified with xterm. .protected = false, }); // If we have a ref-counted style, increase - if (self.screen.cursor.style_id != style.default_id) { + if (self.screens.active.cursor.style_id != style.default_id) { page.styles.useMultiple( page.memory, - self.screen.cursor.style_id, + self.screens.active.cursor.style_id, @intCast(cells.len), ); row.styled = true; @@ -2367,9 +2357,9 @@ pub fn decaln(self: *Terminal) !void { // We messed with the page so assert its integrity here. page.assertIntegrity(); - self.screen.cursorMarkDirty(); - if (self.screen.cursor.y == self.rows - 1) break; - self.screen.cursorDown(1); + self.screens.active.cursorMarkDirty(); + if (self.screens.active.cursor.y == self.rows - 1) break; + self.screens.active.cursorDown(1); } // Reset the cursor to the top-left @@ -2393,7 +2383,7 @@ pub fn kittyGraphics( /// Set a style attribute. pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void { - try self.screen.setAttribute(attr); + try self.screens.active.setAttribute(attr); } /// Print the active attributes as a string. This is used to respond to DECRQSS @@ -2408,7 +2398,7 @@ pub fn printAttributes(self: *Terminal, buf: []u8) ![]const u8 { // The SGR response always starts with a 0. See https://vt100.net/docs/vt510-rm/DECRPSS try writer.writeByte('0'); - const pen = self.screen.cursor.style; + const pen = self.screens.active.cursor.style; var attrs: [8]u8 = @splat(0); var i: usize = 0; @@ -2650,7 +2640,6 @@ pub fn switchScreen(self: *Terminal, key: ScreenSet.Key) !?*Screen { // Finalize the switch self.screens.switchTo(key); - self.screen = new; return old; } @@ -2699,7 +2688,7 @@ pub fn switchScreenMode( // cursor is already copied. The cursor is copied regardless // of destination screen. .@"47", .@"1047" => if (old_) |old| { - self.screen.cursorCopy(old.cursor, .{ + self.screens.active.cursorCopy(old.cursor, .{ .hyperlink = false, }) catch |err| { log.warn( @@ -2719,7 +2708,7 @@ pub fn switchScreenMode( // cursor from the primary screen (if we weren't already // on it). if (old_) |old| { - self.screen.cursorCopy(old.cursor, .{ + self.screens.active.cursorCopy(old.cursor, .{ .hyperlink = false, }) catch |err| { log.warn( @@ -2766,12 +2755,12 @@ pub const SwitchScreenMode = enum { /// /// The caller must free the string. pub fn plainString(self: *Terminal, alloc: Allocator) ![]const u8 { - return try self.screen.dumpStringAlloc(alloc, .{ .viewport = .{} }); + return try self.screens.active.dumpStringAlloc(alloc, .{ .viewport = .{} }); } /// Same as plainString, but respects row wrap state when building the string. pub fn plainStringUnwrapped(self: *Terminal, alloc: Allocator) ![]const u8 { - return try self.screen.dumpStringAllocUnwrapped(alloc, .{ .viewport = .{} }); + return try self.screens.active.dumpStringAllocUnwrapped(alloc, .{ .viewport = .{} }); } /// Full reset. @@ -2786,7 +2775,6 @@ pub fn fullReset(self: *Terminal) void { self.screens.active.alloc, .alternate, ); - self.screen = self.screens.active; // Reset our screens self.screens.active.reset(); @@ -2811,12 +2799,12 @@ pub fn fullReset(self: *Terminal) void { /// Returns true if the point is dirty, used for testing. fn isDirty(t: *const Terminal, pt: point.Point) bool { - return t.screen.pages.getCell(pt).?.isDirty(); + return t.screens.active.pages.getCell(pt).?.isDirty(); } /// Clear all dirty bits. Testing only. fn clearDirty(t: *Terminal) void { - t.screen.pages.clearDirty(); + t.screens.active.pages.clearDirty(); } test "Terminal: input with no control characters" { @@ -2826,8 +2814,8 @@ test "Terminal: input with no control characters" { // Basic grid writing for ("hello") |c| try t.print(c); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 5), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 5), t.screens.active.cursor.x); { const str = try t.plainString(alloc); defer alloc.free(str); @@ -2846,9 +2834,9 @@ test "Terminal: input with basic wraparound" { // Basic grid writing for ("helloworldabc12") |c| try t.print(c); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 4), t.screen.cursor.x); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 4), t.screens.active.cursor.x); + try testing.expect(t.screens.active.cursor.pending_wrap); { const str = try t.plainString(alloc); defer alloc.free(str); @@ -2878,8 +2866,8 @@ test "Terminal: input that forces scroll" { // Basic grid writing for ("abcdef") |c| try t.print(c); - try testing.expectEqual(@as(usize, 4), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 4), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); { const str = try t.plainString(alloc); defer alloc.free(str); @@ -2913,19 +2901,19 @@ test "Terminal: input glitch text" { // Get our initial grapheme capacity. const grapheme_cap = cap: { - const page = t.screen.pages.pages.first.?; + const page = t.screens.active.pages.pages.first.?; break :cap page.data.capacity.grapheme_bytes; }; // Print glitch text until our capacity changes while (true) { - const page = t.screen.pages.pages.first.?; + const page = t.screens.active.pages.pages.first.?; if (page.data.capacity.grapheme_bytes != grapheme_cap) break; try t.printString(glitch); } // We're testing to make sure that grapheme capacity gets increased. - const page = t.screen.pages.pages.first.?; + const page = t.screens.active.pages.pages.first.?; try testing.expect(page.data.capacity.grapheme_bytes > grapheme_cap); } @@ -2937,8 +2925,8 @@ test "Terminal: zero-width character at start" { // just ignore it. try t.print(0x200D); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); // Should not be dirty since we changed nothing. try testing.expect(!t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); @@ -2959,17 +2947,17 @@ test "Terminal: print wide char" { defer t.deinit(testing.allocator); try t.print(0x1F600); // Smiley face - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x1F600), cell.content.codepoint); try testing.expectEqual(Cell.Wide.wide, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); } @@ -2983,22 +2971,22 @@ test "Terminal: print wide char at edge creates spacer head" { t.setCursorPos(1, 10); try t.print(0x1F600); // Smiley face - try testing.expectEqual(@as(usize, 1), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 9, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 9, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(Cell.Wide.spacer_head, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 1 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 1 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x1F600), cell.content.codepoint); try testing.expectEqual(Cell.Wide.wide, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 1 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 1 } }).?; const cell = list_cell.cell; try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); } @@ -3027,12 +3015,12 @@ test "Terminal: print wide char in single-width terminal" { defer t.deinit(testing.allocator); try t.print(0x1F600); // Smiley face - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); + try testing.expect(t.screens.active.cursor.pending_wrap); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); @@ -3049,17 +3037,17 @@ test "Terminal: print over wide char at 0,0" { t.setCursorPos(0, 0); try t.print('A'); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'A'), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); @@ -3078,13 +3066,13 @@ test "Terminal: print over wide spacer tail" { try t.print('X'); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'X'), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); @@ -3107,7 +3095,7 @@ test "Terminal: print over wide char with bold" { try t.print(0x1F600); // Smiley face // verify we have styles in our style map { - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); } @@ -3118,7 +3106,7 @@ test "Terminal: print over wide char with bold" { // verify our style is gone { - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); } @@ -3137,7 +3125,7 @@ test "Terminal: print over wide char with bg color" { try t.print(0x1F600); // Smiley face // verify we have styles in our style map { - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); } @@ -3148,7 +3136,7 @@ test "Terminal: print over wide char with bg color" { // verify our style is gone { - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); } @@ -3168,13 +3156,13 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try t.print(0x1F467); // We should have 6 cells taken up - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 6), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 6), t.screens.active.cursor.x); // Assert various properties about our screen to verify // we have all expected cells. { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x1F468), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3183,7 +3171,7 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try testing.expectEqual(@as(usize, 1), cps.len); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); @@ -3191,7 +3179,7 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try testing.expect(list_cell.node.data.lookupGrapheme(cell) == null); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x1F469), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3200,7 +3188,7 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try testing.expectEqual(@as(usize, 1), cps.len); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 3, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 3, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); @@ -3208,7 +3196,7 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try testing.expect(list_cell.node.data.lookupGrapheme(cell) == null); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x1F467), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); @@ -3216,7 +3204,7 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try testing.expect(list_cell.node.data.lookupGrapheme(cell) == null); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 5, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 5, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); @@ -3244,7 +3232,7 @@ test "Terminal: VS16 doesn't make character with 2027 disabled" { } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3278,20 +3266,20 @@ test "Terminal: print invalid VS16 non-grapheme" { try t.print(0xFE0F); // We should have 2 cells taken up. It is one character but "wide". - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x); // Assert various properties about our screen to verify // we have all expected cells. { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'x'), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); } @@ -3328,8 +3316,8 @@ test "Terminal: print multicodepoint grapheme, mode 2027" { try t.print(0x1F467); // We should have 2 cells taken up. It is one character but "wide". - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); // Row should be dirty try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); @@ -3337,7 +3325,7 @@ test "Terminal: print multicodepoint grapheme, mode 2027" { // Assert various properties about our screen to verify // we have all expected cells. { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x1F468), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3346,7 +3334,7 @@ test "Terminal: print multicodepoint grapheme, mode 2027" { try testing.expectEqual(@as(usize, 4), cps.len); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); @@ -3367,15 +3355,15 @@ test "Terminal: keypad sequence VS15" { // VS15 should combine with the base character into a single grapheme cluster, // taking 1 cell (narrow character). - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x); // Row should be dirty try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); // The base emoji should be in cell 0 with the skin tone as a grapheme { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x23), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3396,15 +3384,15 @@ test "Terminal: keypad sequence VS16" { // VS16 should combine with the base character into a single grapheme cluster, // taking 2 cells (wide character). - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); // Row should be dirty try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); // The base emoji should be in cell 0 with the skin tone as a grapheme { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x23), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3425,15 +3413,15 @@ test "Terminal: Fitzpatrick skin tone next valid base" { // The skin tone should combine with the base emoji into a single grapheme cluster, // taking 2 cells (wide character). - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); // Row should be dirty try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); // The base emoji should be in cell 0 with the skin tone as a grapheme { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x1F44B), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3455,8 +3443,8 @@ test "Terminal: Fitzpatrick skin tone next to non-base" { // We should have 4 cells taken up. Importantly, the skin tone // should not join with the quotes. - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 4), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 4), t.screens.active.cursor.x); // Row should be dirty try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); @@ -3464,21 +3452,21 @@ test "Terminal: Fitzpatrick skin tone next to non-base" { // Assert various properties about our screen to verify // we have all expected cells. { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x22), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x1F3FF), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 3, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 3, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x22), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); @@ -3511,8 +3499,8 @@ test "Terminal: multicodepoint grapheme marks dirty on every codepoint" { try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); // We should have 2 cells taken up. It is one character but "wide". - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); } test "Terminal: VS15 to make narrow character" { @@ -3527,16 +3515,16 @@ test "Terminal: VS15 to make narrow character" { t.clearDirty(); // We should have 2 cells taken up. It is one character but "wide". - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); try t.print(0xFE0E); // VS15 to make narrow try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); t.clearDirty(); // VS15 should send us back a cell since our char is no longer wide. - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x); { const str = try t.plainString(testing.allocator); @@ -3545,7 +3533,7 @@ test "Terminal: VS15 to make narrow character" { } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x2614), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3570,8 +3558,8 @@ test "Terminal: VS15 on already narrow emoji" { t.clearDirty(); // Character takes up one cell - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x); { const str = try t.plainString(testing.allocator); @@ -3580,7 +3568,7 @@ test "Terminal: VS15 on already narrow emoji" { } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x26C8), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3602,18 +3590,18 @@ test "Terminal: VS15 to make narrow character with pending wrap" { t.clearDirty(); // We only move one because we're in a pending wrap state. - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x); + try testing.expect(t.screens.active.cursor.pending_wrap); try t.print(0xFE0E); // VS15 to make narrow try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); t.clearDirty(); // VS15 should clear the pending wrap state - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x); + try testing.expect(!t.screens.active.cursor.pending_wrap); { const str = try t.plainString(testing.allocator); @@ -3622,7 +3610,7 @@ test "Terminal: VS15 to make narrow character with pending wrap" { } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x2614), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3653,7 +3641,7 @@ test "Terminal: VS16 to make wide character with mode 2027" { } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3684,7 +3672,7 @@ test "Terminal: VS16 repeated with mode 2027" { } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3693,7 +3681,7 @@ test "Terminal: VS16 repeated with mode 2027" { try testing.expectEqual(@as(usize, 1), cps.len); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); @@ -3715,20 +3703,20 @@ test "Terminal: print invalid VS16 grapheme" { try t.print(0xFE0F); // We should have 2 cells taken up. It is one character but "wide". - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x); // Assert various properties about our screen to verify // we have all expected cells. { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'x'), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); @@ -3748,13 +3736,13 @@ test "Terminal: print invalid VS16 with second char" { try t.print('y'); // We should have 2 cells taken up. It is one character but "wide". - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); // Assert various properties about our screen to verify // we have all expected cells. { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'x'), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); @@ -3762,7 +3750,7 @@ test "Terminal: print invalid VS16 with second char" { } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'y'), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); @@ -3793,7 +3781,7 @@ test "Terminal: overwrite grapheme should clear grapheme data" { } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'A'), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); @@ -3817,11 +3805,11 @@ test "Terminal: overwrite multicodepoint grapheme clears grapheme data" { try t.print(0x1F467); // We should have 2 cells taken up. It is one character but "wide". - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); // We should have one cell with graphemes - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.graphemeCount()); // Move back and overwrite wide @@ -3830,8 +3818,8 @@ test "Terminal: overwrite multicodepoint grapheme clears grapheme data" { try t.print('X'); try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x); try testing.expectEqual(@as(usize, 0), page.graphemeCount()); { @@ -3857,11 +3845,11 @@ test "Terminal: overwrite multicodepoint grapheme tail clears grapheme data" { try t.print(0x1F467); // We should have 2 cells taken up. It is one character but "wide". - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); // We should have one cell with graphemes - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.graphemeCount()); // Move back and overwrite wide @@ -3874,8 +3862,8 @@ test "Terminal: overwrite multicodepoint grapheme tail clears grapheme data" { try testing.expectEqualStrings(" X", str); } - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); try testing.expectEqual(@as(usize, 0), page.graphemeCount()); } @@ -3899,7 +3887,7 @@ test "Terminal: print writes to bottom if scrolled" { } // Scroll to the top - t.screen.scroll(.{ .top = {} }); + t.screens.active.scroll(.{ .top = {} }); { const str = try t.plainString(testing.allocator); defer testing.allocator.free(str); @@ -3908,7 +3896,7 @@ test "Terminal: print writes to bottom if scrolled" { // Type try t.print('A'); - t.screen.scroll(.{ .active = {} }); + t.screens.active.scroll(.{ .active = {} }); { const str = try t.plainString(testing.allocator); defer testing.allocator.free(str); @@ -3916,8 +3904,8 @@ test "Terminal: print writes to bottom if scrolled" { } try testing.expect(t.isDirty(.{ .active = .{ - .x = t.screen.cursor.x, - .y = t.screen.cursor.y, + .x = t.screens.active.cursor.x, + .y = t.screens.active.cursor.y, } })); } @@ -4024,11 +4012,11 @@ test "Terminal: print kitty unicode placeholder" { defer t.deinit(testing.allocator); try t.print(kitty.graphics.unicode.placeholder); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.x); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, kitty.graphics.unicode.placeholder), cell.content.codepoint); try testing.expect(list_cell.row.kitty_virtual_placeholder); @@ -4043,8 +4031,8 @@ test "Terminal: soft wrap" { // Basic grid writing for ("hello") |c| try t.print(c); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); { const str = try t.plainString(testing.allocator); defer testing.allocator.free(str); @@ -4063,11 +4051,11 @@ test "Terminal: soft wrap with semantic prompt" { for ("hello") |c| try t.print(c); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; try testing.expectEqual(Row.SemanticPrompt.prompt, list_cell.row.semantic_prompt); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 1 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 1 } }).?; try testing.expectEqual(Row.SemanticPrompt.prompt, list_cell.row.semantic_prompt); } } @@ -4083,8 +4071,8 @@ test "Terminal: disabled wraparound with wide char and one space" { try t.printString("AAAA"); t.clearDirty(); try t.print(0x1F6A8); // Police car light - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 4), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 4), t.screens.active.cursor.x); { const str = try t.plainString(testing.allocator); @@ -4094,7 +4082,7 @@ test "Terminal: disabled wraparound with wide char and one space" { // Make sure we printed nothing { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); @@ -4115,8 +4103,8 @@ test "Terminal: disabled wraparound with wide char and no space" { try t.printString("AAAAA"); t.clearDirty(); try t.print(0x1F6A8); // Police car light - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 4), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 4), t.screens.active.cursor.x); { const str = try t.plainString(testing.allocator); @@ -4125,7 +4113,7 @@ test "Terminal: disabled wraparound with wide char and no space" { } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'A'), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); @@ -4148,8 +4136,8 @@ test "Terminal: disabled wraparound with wide grapheme and half space" { try t.print(0x2764); // Heart t.clearDirty(); try t.print(0xFE0F); // VS16 to make wide - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 4), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 4), t.screens.active.cursor.x); { const str = try t.plainString(testing.allocator); @@ -4158,7 +4146,7 @@ test "Terminal: disabled wraparound with wide grapheme and half space" { } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, '❤'), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); @@ -4185,7 +4173,7 @@ test "Terminal: print right margin wrap" { } { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; const row = list_cell.row; try testing.expect(!row.wrap); } @@ -4265,15 +4253,15 @@ test "Terminal: print wide char at right margin does not create spacer head" { t.setLeftAndRightMargin(3, 5); t.setCursorPos(1, 5); try t.print(0x1F600); // Smiley face - try testing.expectEqual(@as(usize, 1), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 4), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 4), t.screens.active.cursor.x); // Both rows dirty because the cursor moved try testing.expect(t.isDirty(.{ .screen = .{ .x = 4, .y = 0 } })); try testing.expect(t.isDirty(.{ .screen = .{ .x = 4, .y = 1 } })); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); @@ -4282,13 +4270,13 @@ test "Terminal: print wide char at right margin does not create spacer head" { try testing.expect(!row.wrap); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 2, .y = 1 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 2, .y = 1 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x1F600), cell.content.codepoint); try testing.expectEqual(Cell.Wide.wide, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 3, .y = 1 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 3, .y = 1 } }).?; const cell = list_cell.cell; try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); } @@ -4299,12 +4287,12 @@ test "Terminal: print with hyperlink" { defer t.deinit(testing.allocator); // Setup our hyperlink and print - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.printString("123456"); // Verify all our cells have a hyperlink for (0..6) |x| { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = @intCast(x), .y = 0, } }).?; @@ -4324,14 +4312,14 @@ test "Terminal: print over cell with same hyperlink" { defer t.deinit(testing.allocator); // Setup our hyperlink and print - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.printString("123456"); t.setCursorPos(1, 1); try t.printString("123456"); // Verify all our cells have a hyperlink for (0..6) |x| { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = @intCast(x), .y = 0, } }).?; @@ -4351,14 +4339,14 @@ test "Terminal: print and end hyperlink" { defer t.deinit(testing.allocator); // Setup our hyperlink and print - 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(); try t.printString("456"); // Verify all our cells have a hyperlink for (0..3) |x| { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = @intCast(x), .y = 0, } }).?; @@ -4370,7 +4358,7 @@ test "Terminal: print and end hyperlink" { try testing.expectEqual(@as(hyperlink.Id, 1), id); } for (3..6) |x| { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = @intCast(x), .y = 0, } }).?; @@ -4388,14 +4376,14 @@ test "Terminal: print and change hyperlink" { defer t.deinit(testing.allocator); // Setup our hyperlink and print - try t.screen.startHyperlink("http://one.example.com", null); + try t.screens.active.startHyperlink("http://one.example.com", null); try t.printString("123"); - try t.screen.startHyperlink("http://two.example.com", null); + try t.screens.active.startHyperlink("http://two.example.com", null); try t.printString("456"); // Verify all our cells have a hyperlink for (0..3) |x| { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = @intCast(x), .y = 0, } }).?; @@ -4405,7 +4393,7 @@ test "Terminal: print and change hyperlink" { try testing.expectEqual(@as(hyperlink.Id, 1), id); } for (3..6) |x| { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = @intCast(x), .y = 0, } }).?; @@ -4423,15 +4411,15 @@ test "Terminal: overwrite hyperlink" { defer t.deinit(testing.allocator); // Setup our hyperlink and print - try t.screen.startHyperlink("http://one.example.com", null); + try t.screens.active.startHyperlink("http://one.example.com", null); try t.printString("123"); t.setCursorPos(1, 1); - t.screen.endHyperlink(); + t.screens.active.endHyperlink(); try t.printString("456"); // Verify all our cells have a hyperlink for (0..3) |x| { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = @intCast(x), .y = 0, } }).?; @@ -4466,8 +4454,8 @@ test "Terminal: linefeed and carriage return" { try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 1 } })); for ("world") |c| try t.print(c); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 5), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 5), t.screens.active.cursor.x); { const str = try t.plainString(testing.allocator); defer testing.allocator.free(str); @@ -4481,12 +4469,12 @@ test "Terminal: linefeed unsets pending wrap" { // Basic grid writing for ("hello") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap == true); + try testing.expect(t.screens.active.cursor.pending_wrap == true); t.clearDirty(); try t.linefeed(); try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 1 } })); - try testing.expect(t.screen.cursor.pending_wrap == false); + try testing.expect(t.screens.active.cursor.pending_wrap == false); } test "Terminal: linefeed mode automatic carriage return" { @@ -4511,9 +4499,9 @@ test "Terminal: carriage return unsets pending wrap" { // Basic grid writing for ("hello") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap == true); + try testing.expect(t.screens.active.cursor.pending_wrap == true); t.carriageReturn(); - try testing.expect(t.screen.cursor.pending_wrap == false); + try testing.expect(t.screens.active.cursor.pending_wrap == false); } test "Terminal: carriage return origin mode moves to left margin" { @@ -4521,30 +4509,30 @@ test "Terminal: carriage return origin mode moves to left margin" { defer t.deinit(testing.allocator); t.modes.set(.origin, true); - t.screen.cursor.x = 0; + t.screens.active.cursor.x = 0; t.scrolling_region.left = 2; t.carriageReturn(); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); } test "Terminal: carriage return left of left margin moves to zero" { var t = try init(testing.allocator, .{ .cols = 5, .rows = 80 }); defer t.deinit(testing.allocator); - t.screen.cursor.x = 1; + t.screens.active.cursor.x = 1; t.scrolling_region.left = 2; t.carriageReturn(); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); } test "Terminal: carriage return right of left margin moves to left margin" { var t = try init(testing.allocator, .{ .cols = 5, .rows = 80 }); defer t.deinit(testing.allocator); - t.screen.cursor.x = 3; + t.screens.active.cursor.x = 3; t.scrolling_region.left = 2; t.carriageReturn(); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x); } test "Terminal: backspace" { @@ -4555,8 +4543,8 @@ test "Terminal: backspace" { for ("hello") |c| try t.print(c); t.backspace(); try t.print('y'); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 5), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 5), t.screens.active.cursor.x); { const str = try t.plainString(testing.allocator); defer testing.allocator.free(str); @@ -4572,17 +4560,17 @@ test "Terminal: horizontal tabs" { // HT try t.print('1'); try t.horizontalTab(); - try testing.expectEqual(@as(usize, 8), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 8), t.screens.active.cursor.x); // HT try t.horizontalTab(); - try testing.expectEqual(@as(usize, 16), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 16), t.screens.active.cursor.x); // HT at the end try t.horizontalTab(); - try testing.expectEqual(@as(usize, 19), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 19), t.screens.active.cursor.x); try t.horizontalTab(); - try testing.expectEqual(@as(usize, 19), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 19), t.screens.active.cursor.x); } test "Terminal: horizontal tabs starting on tabstop" { @@ -4590,9 +4578,9 @@ test "Terminal: horizontal tabs starting on tabstop" { var t = try init(alloc, .{ .cols = 20, .rows = 5 }); defer t.deinit(alloc); - t.setCursorPos(t.screen.cursor.y, 9); + t.setCursorPos(t.screens.active.cursor.y, 9); try t.print('X'); - t.setCursorPos(t.screen.cursor.y, 9); + t.setCursorPos(t.screens.active.cursor.y, 9); try t.horizontalTab(); try t.print('A'); @@ -4610,7 +4598,7 @@ test "Terminal: horizontal tabs with right margin" { t.scrolling_region.left = 2; t.scrolling_region.right = 5; - t.setCursorPos(t.screen.cursor.y, 1); + t.setCursorPos(t.screens.active.cursor.y, 1); try t.print('X'); try t.horizontalTab(); try t.print('A'); @@ -4628,21 +4616,21 @@ test "Terminal: horizontal tabs back" { defer t.deinit(alloc); // Edge of screen - t.setCursorPos(t.screen.cursor.y, 20); + t.setCursorPos(t.screens.active.cursor.y, 20); // HT try t.horizontalTabBack(); - try testing.expectEqual(@as(usize, 16), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 16), t.screens.active.cursor.x); // HT try t.horizontalTabBack(); - try testing.expectEqual(@as(usize, 8), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 8), t.screens.active.cursor.x); // HT try t.horizontalTabBack(); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); try t.horizontalTabBack(); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); } test "Terminal: horizontal tabs back starting on tabstop" { @@ -4650,9 +4638,9 @@ test "Terminal: horizontal tabs back starting on tabstop" { var t = try init(alloc, .{ .cols = 20, .rows = 5 }); defer t.deinit(alloc); - t.setCursorPos(t.screen.cursor.y, 9); + t.setCursorPos(t.screens.active.cursor.y, 9); try t.print('X'); - t.setCursorPos(t.screen.cursor.y, 9); + t.setCursorPos(t.screens.active.cursor.y, 9); try t.horizontalTabBack(); try t.print('A'); @@ -4709,9 +4697,9 @@ test "Terminal: cursorPos resets wrap" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.setCursorPos(1, 1); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('X'); { @@ -4799,52 +4787,52 @@ test "Terminal: setCursorPos (original test)" { var t = try init(testing.allocator, .{ .cols = 80, .rows = 80 }); defer t.deinit(testing.allocator); - 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); // Setting it to 0 should keep it zero (1 based) t.setCursorPos(0, 0); - 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); // Should clamp to size t.setCursorPos(81, 81); - try testing.expectEqual(@as(usize, 79), t.screen.cursor.x); - try testing.expectEqual(@as(usize, 79), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 79), t.screens.active.cursor.x); + try testing.expectEqual(@as(usize, 79), t.screens.active.cursor.y); // Should reset pending wrap t.setCursorPos(0, 80); try t.print('c'); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.setCursorPos(0, 80); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); // Origin mode t.modes.set(.origin, true); // No change without a scroll region t.setCursorPos(81, 81); - try testing.expectEqual(@as(usize, 79), t.screen.cursor.x); - try testing.expectEqual(@as(usize, 79), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 79), t.screens.active.cursor.x); + try testing.expectEqual(@as(usize, 79), t.screens.active.cursor.y); // Set the scroll region t.setTopAndBottomMargin(10, t.rows); t.setCursorPos(0, 0); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); - try testing.expectEqual(@as(usize, 9), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); + try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.y); t.setCursorPos(1, 1); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); - try testing.expectEqual(@as(usize, 9), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); + try testing.expectEqual(@as(usize, 9), t.screens.active.cursor.y); t.setCursorPos(100, 0); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); - try testing.expectEqual(@as(usize, 79), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); + try testing.expectEqual(@as(usize, 79), t.screens.active.cursor.y); t.setTopAndBottomMargin(10, 11); t.setCursorPos(2, 0); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); - try testing.expectEqual(@as(usize, 10), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); + try testing.expectEqual(@as(usize, 10), t.screens.active.cursor.y); } test "Terminal: setTopAndBottomMargin simple" { @@ -5175,7 +5163,7 @@ test "Terminal: insertLines colors with bg color" { } for (0..t.cols) |x| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = @intCast(x), .y = 1, } }).?; @@ -5206,7 +5194,7 @@ test "Terminal: insertLines handles style refs" { try t.setAttribute(.{ .unset = {} }); // verify we have styles in our style map - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); t.setCursorPos(2, 2); @@ -5410,9 +5398,9 @@ test "Terminal: insertLines resets pending wrap" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.insertLines(1); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('B'); { @@ -5442,7 +5430,7 @@ test "Terminal: insertLines resets wrap" { } { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 2 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 2 } }).?; const row = list_cell.row; try testing.expect(!row.wrap); } @@ -5525,11 +5513,11 @@ test "Terminal: scrollUp simple" { try t.printString("GHI"); t.setCursorPos(2, 2); - const cursor = t.screen.cursor; + const cursor = t.screens.active.cursor; t.clearDirty(); t.scrollUp(1); - try testing.expectEqual(cursor.x, t.screen.cursor.x); - try testing.expectEqual(cursor.y, t.screen.cursor.y); + try testing.expectEqual(cursor.x, t.screens.active.cursor.x); + try testing.expectEqual(cursor.y, t.screens.active.cursor.y); try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 0 } })); try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 1 } })); @@ -5550,9 +5538,9 @@ test "Terminal: scrollUp moves hyperlink" { try t.printString("ABC"); t.carriageReturn(); try t.linefeed(); - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.printString("DEF"); - t.screen.endHyperlink(); + t.screens.active.endHyperlink(); t.carriageReturn(); try t.linefeed(); try t.printString("GHI"); @@ -5566,7 +5554,7 @@ test "Terminal: scrollUp moves hyperlink" { } for (0..3) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 0, } }).?; @@ -5580,7 +5568,7 @@ test "Terminal: scrollUp moves hyperlink" { try testing.expectEqual(1, page.hyperlink_set.count()); } for (0..3) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 1, } }).?; @@ -5598,9 +5586,9 @@ test "Terminal: scrollUp clears hyperlink" { var t = try init(alloc, .{ .rows = 5, .cols = 5 }); defer t.deinit(alloc); - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.printString("ABC"); - t.screen.endHyperlink(); + t.screens.active.endHyperlink(); t.carriageReturn(); try t.linefeed(); try t.printString("DEF"); @@ -5617,7 +5605,7 @@ test "Terminal: scrollUp clears hyperlink" { } for (0..3) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 0, } }).?; @@ -5676,11 +5664,11 @@ test "Terminal: scrollUp left/right scroll region" { t.scrolling_region.right = 3; t.setCursorPos(2, 2); - const cursor = t.screen.cursor; + const cursor = t.screens.active.cursor; t.clearDirty(); t.scrollUp(1); - try testing.expectEqual(cursor.x, t.screen.cursor.x); - try testing.expectEqual(cursor.y, t.screen.cursor.y); + try testing.expectEqual(cursor.x, t.screens.active.cursor.x); + try testing.expectEqual(cursor.y, t.screens.active.cursor.y); try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 0 } })); try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 1 } })); @@ -5701,9 +5689,9 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { try t.printString("ABC123"); t.carriageReturn(); try t.linefeed(); - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.printString("DEF456"); - t.screen.endHyperlink(); + t.screens.active.endHyperlink(); t.carriageReturn(); try t.linefeed(); try t.printString("GHI789"); @@ -5721,7 +5709,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { // First row gets some hyperlinks { for (0..1) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 0, } }).?; @@ -5731,7 +5719,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { try testing.expect(id == null); } for (1..4) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 0, } }).?; @@ -5745,7 +5733,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { try testing.expectEqual(1, page.hyperlink_set.count()); } for (4..6) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 0, } }).?; @@ -5759,7 +5747,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { // Second row preserves hyperlink where we didn't scroll { for (0..1) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 1, } }).?; @@ -5773,7 +5761,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { try testing.expectEqual(1, page.hyperlink_set.count()); } for (1..4) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 1, } }).?; @@ -5783,7 +5771,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { try testing.expect(id == null); } for (4..6) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 1, } }).?; @@ -5887,11 +5875,11 @@ test "Terminal: scrollDown simple" { try t.printString("GHI"); t.setCursorPos(2, 2); - const cursor = t.screen.cursor; + const cursor = t.screens.active.cursor; t.clearDirty(); t.scrollDown(1); - try testing.expectEqual(cursor.x, t.screen.cursor.x); - try testing.expectEqual(cursor.y, t.screen.cursor.y); + try testing.expectEqual(cursor.x, t.screens.active.cursor.x); + try testing.expectEqual(cursor.y, t.screens.active.cursor.y); for (0..5) |y| try testing.expect(t.isDirty(.{ .active = .{ .x = 0, @@ -5910,9 +5898,9 @@ test "Terminal: scrollDown hyperlink moves" { var t = try init(alloc, .{ .rows = 5, .cols = 5 }); defer t.deinit(alloc); - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.printString("ABC"); - t.screen.endHyperlink(); + t.screens.active.endHyperlink(); t.carriageReturn(); try t.linefeed(); try t.printString("DEF"); @@ -5929,7 +5917,7 @@ test "Terminal: scrollDown hyperlink moves" { } for (0..3) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 1, } }).?; @@ -5943,7 +5931,7 @@ test "Terminal: scrollDown hyperlink moves" { try testing.expectEqual(1, page.hyperlink_set.count()); } for (0..3) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 0, } }).?; @@ -5971,11 +5959,11 @@ test "Terminal: scrollDown outside of scroll region" { t.setTopAndBottomMargin(3, 4); t.setCursorPos(2, 2); - const cursor = t.screen.cursor; + const cursor = t.screens.active.cursor; t.clearDirty(); t.scrollDown(1); - try testing.expectEqual(cursor.x, t.screen.cursor.x); - try testing.expectEqual(cursor.y, t.screen.cursor.y); + try testing.expectEqual(cursor.x, t.screens.active.cursor.x); + try testing.expectEqual(cursor.y, t.screens.active.cursor.y); try testing.expect(!t.isDirty(.{ .active = .{ .x = 0, .y = 0 } })); @@ -6007,11 +5995,11 @@ test "Terminal: scrollDown left/right scroll region" { t.scrolling_region.right = 3; t.setCursorPos(2, 2); - const cursor = t.screen.cursor; + const cursor = t.screens.active.cursor; t.clearDirty(); t.scrollDown(1); - try testing.expectEqual(cursor.x, t.screen.cursor.x); - try testing.expectEqual(cursor.y, t.screen.cursor.y); + try testing.expectEqual(cursor.x, t.screens.active.cursor.x); + try testing.expectEqual(cursor.y, t.screens.active.cursor.y); for (0..4) |y| try testing.expect(t.isDirty(.{ .active = .{ .x = 0, @@ -6030,9 +6018,9 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { var t = try init(alloc, .{ .cols = 10, .rows = 10 }); defer t.deinit(alloc); - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.printString("ABC123"); - t.screen.endHyperlink(); + t.screens.active.endHyperlink(); t.carriageReturn(); try t.linefeed(); try t.printString("DEF456"); @@ -6053,7 +6041,7 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { // First row preserves hyperlink where we didn't scroll { for (0..1) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 0, } }).?; @@ -6067,7 +6055,7 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { try testing.expectEqual(1, page.hyperlink_set.count()); } for (1..4) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 0, } }).?; @@ -6077,7 +6065,7 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { try testing.expect(id == null); } for (4..6) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 0, } }).?; @@ -6095,7 +6083,7 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { // Second row gets some hyperlinks { for (0..1) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 1, } }).?; @@ -6105,7 +6093,7 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { try testing.expect(id == null); } for (1..4) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 1, } }).?; @@ -6119,7 +6107,7 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { try testing.expectEqual(1, page.hyperlink_set.count()); } for (4..6) |x| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = @intCast(x), .y = 1, } }).?; @@ -6147,11 +6135,11 @@ test "Terminal: scrollDown outside of left/right scroll region" { t.scrolling_region.right = 3; t.setCursorPos(1, 1); - const cursor = t.screen.cursor; + const cursor = t.screens.active.cursor; t.clearDirty(); t.scrollDown(1); - try testing.expectEqual(cursor.x, t.screen.cursor.x); - try testing.expectEqual(cursor.y, t.screen.cursor.y); + try testing.expectEqual(cursor.x, t.screens.active.cursor.x); + try testing.expectEqual(cursor.y, t.screens.active.cursor.y); for (0..4) |y| try testing.expect(t.isDirty(.{ .active = .{ .x = 0, @@ -6266,9 +6254,9 @@ test "Terminal: eraseChars resets pending wrap" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.eraseChars(1); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('X'); { @@ -6285,7 +6273,7 @@ test "Terminal: eraseChars resets wrap" { for ("ABCDE123") |c| try t.print(c); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; const row = list_cell.row; try testing.expect(row.wrap); } @@ -6294,7 +6282,7 @@ test "Terminal: eraseChars resets wrap" { t.eraseChars(1); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; const row = list_cell.row; try testing.expect(!row.wrap); } @@ -6327,7 +6315,7 @@ test "Terminal: eraseChars preserves background sgr" { defer testing.allocator.free(str); try testing.expectEqualStrings(" C", str); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; try testing.expect(list_cell.cell.content_tag == .bg_color_rgb); try testing.expectEqual(Cell.RGB{ .r = 0xFF, @@ -6336,7 +6324,7 @@ test "Terminal: eraseChars preserves background sgr" { }, list_cell.cell.content.color_rgb); } { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 1, .y = 0 } }).?; try testing.expect(list_cell.cell.content_tag == .bg_color_rgb); try testing.expectEqual(Cell.RGB{ .r = 0xFF, @@ -6359,7 +6347,7 @@ test "Terminal: eraseChars handles refcounted styles" { try t.print('C'); // verify we have styles in our style map - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); t.setCursorPos(1, 1); @@ -6436,7 +6424,7 @@ test "Terminal: eraseChars wide char boundary conditions" { t.setCursorPos(1, 2); t.eraseChars(3); - t.screen.cursor.page_pin.node.data.assertIntegrity(); + t.screens.active.cursor.page_pin.node.data.assertIntegrity(); { const str = try t.plainString(alloc); @@ -6466,7 +6454,7 @@ test "Terminal: eraseChars wide char splits proper cell boundaries" { t.setCursorPos(1, 6); // At: て t.eraseChars(4); // Delete: て下 - t.screen.cursor.page_pin.node.data.assertIntegrity(); + t.screens.active.cursor.page_pin.node.data.assertIntegrity(); { const str = try t.plainString(alloc); @@ -6493,7 +6481,7 @@ test "Terminal: eraseChars wide char wrap boundary conditions" { t.setCursorPos(2, 2); t.eraseChars(3); - t.screen.cursor.page_pin.node.data.assertIntegrity(); + t.screens.active.cursor.page_pin.node.data.assertIntegrity(); { const str = try t.plainString(alloc); @@ -6774,9 +6762,9 @@ test "Terminal: index scrolling with hyperlink" { defer t.deinit(alloc); t.setCursorPos(5, 1); - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.print('A'); - t.screen.endHyperlink(); + t.screens.active.endHyperlink(); t.cursorLeft(1); // undo moving right from 'A' try t.index(); try t.print('B'); @@ -6788,7 +6776,7 @@ test "Terminal: index scrolling with hyperlink" { } { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = 0, .y = 3, } }).?; @@ -6800,7 +6788,7 @@ test "Terminal: index scrolling with hyperlink" { try testing.expectEqual(@as(hyperlink.Id, 1), id); } { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = 0, .y = 4, } }).?; @@ -6818,10 +6806,10 @@ test "Terminal: index outside of scrolling region" { var t = try init(alloc, .{ .cols = 2, .rows = 5 }); defer t.deinit(alloc); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); t.setTopAndBottomMargin(2, 5); try t.index(); - try testing.expectEqual(@as(usize, 1), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y); } test "Terminal: index from the bottom outside of scroll region" { @@ -6904,7 +6892,7 @@ test "Terminal: index bottom of primary screen background sgr" { defer testing.allocator.free(str); try testing.expectEqualStrings("\n\n\nA", str); for (0..5) |x| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = @intCast(x), .y = 4, } }).?; @@ -6948,9 +6936,9 @@ test "Terminal: index bottom of scroll region with hyperlinks" { try t.print('A'); try t.index(); t.carriageReturn(); - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.print('B'); - t.screen.endHyperlink(); + t.screens.active.endHyperlink(); try t.index(); t.carriageReturn(); try t.print('C'); @@ -6962,7 +6950,7 @@ test "Terminal: index bottom of scroll region with hyperlinks" { } { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = 0, .y = 0, } }).?; @@ -6974,7 +6962,7 @@ test "Terminal: index bottom of scroll region with hyperlinks" { try testing.expectEqual(@as(hyperlink.Id, 1), id); } { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = 0, .y = 1, } }).?; @@ -6994,9 +6982,9 @@ test "Terminal: index bottom of scroll region clear hyperlinks" { t.setTopAndBottomMargin(2, 3); t.setCursorPos(2, 1); - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.print('A'); - t.screen.endHyperlink(); + t.screens.active.endHyperlink(); try t.index(); t.carriageReturn(); try t.print('B'); @@ -7011,7 +6999,7 @@ test "Terminal: index bottom of scroll region clear hyperlinks" { } for (1..3) |y| { - const list_cell = t.screen.pages.getCell(.{ .viewport = .{ + const list_cell = t.screens.active.pages.getCell(.{ .viewport = .{ .x = 0, .y = @intCast(y), } }).?; @@ -7050,7 +7038,7 @@ test "Terminal: index bottom of scroll region with background SGR" { } for (0..t.cols) |x| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = @intCast(x), .y = 2, } }).?; @@ -7139,8 +7127,8 @@ test "Terminal: index inside left/right margin" { try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 1 } })); try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 2 } })); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); { const str = try t.plainString(testing.allocator); @@ -7163,12 +7151,12 @@ test "Terminal: index bottom of scroll region creates scrollback" { try t.print('Y'); { - const str = try t.screen.dumpStringAlloc(alloc, .{ .viewport = .{} }); + const str = try t.screens.active.dumpStringAlloc(alloc, .{ .viewport = .{} }); defer testing.allocator.free(str); try testing.expectEqualStrings("2\n3\nY\nX", str); } { - const str = try t.screen.dumpStringAlloc(alloc, .{ .screen = .{} }); + const str = try t.screens.active.dumpStringAlloc(alloc, .{ .screen = .{} }); defer testing.allocator.free(str); try testing.expectEqualStrings("1\n2\n3\nY\nX", str); } @@ -7213,17 +7201,17 @@ test "Terminal: index bottom of scroll region blank line preserves SGR" { try t.index(); { - const str = try t.screen.dumpStringAlloc(alloc, .{ .viewport = .{} }); + const str = try t.screens.active.dumpStringAlloc(alloc, .{ .viewport = .{} }); defer testing.allocator.free(str); try testing.expectEqualStrings("2\n3\n\nX", str); } { - const str = try t.screen.dumpStringAlloc(alloc, .{ .screen = .{} }); + const str = try t.screens.active.dumpStringAlloc(alloc, .{ .screen = .{} }); defer testing.allocator.free(str); try testing.expectEqualStrings("1\n2\n3\n\nX", str); } for (0..t.cols) |x| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = @intCast(x), .y = 2, } }).?; @@ -7296,9 +7284,9 @@ test "Terminal: cursorUp resets wrap" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.cursorUp(1); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('X'); { @@ -7332,9 +7320,9 @@ test "Terminal: cursorLeft unsets pending wrap state" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.cursorLeft(1); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('X'); { @@ -7350,9 +7338,9 @@ test "Terminal: cursorLeft unsets pending wrap state with longer jump" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.cursorLeft(3); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('X'); { @@ -7371,9 +7359,9 @@ test "Terminal: cursorLeft reverse wrap with pending wrap state" { t.modes.set(.reverse_wrap, true); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.cursorLeft(1); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('X'); { @@ -7392,9 +7380,9 @@ test "Terminal: cursorLeft reverse wrap extended with pending wrap state" { t.modes.set(.reverse_wrap_extended, true); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.cursorLeft(1); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('X'); { @@ -7415,7 +7403,7 @@ test "Terminal: cursorLeft reverse wrap" { for ("ABCDE1") |c| try t.print(c); t.cursorLeft(2); try t.print('X'); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); { const str = try t.plainString(testing.allocator); @@ -7543,8 +7531,8 @@ test "Terminal: cursorLeft extended reverse wrap above top scroll region" { t.setCursorPos(2, 1); t.cursorLeft(1000); - 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 "Terminal: cursorLeft reverse wrap on first row" { @@ -7559,8 +7547,8 @@ test "Terminal: cursorLeft reverse wrap on first row" { t.setCursorPos(1, 2); t.cursorLeft(1000); - 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 "Terminal: cursorDown basic" { @@ -7620,9 +7608,9 @@ test "Terminal: cursorDown resets wrap" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.cursorDown(1); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('X'); { @@ -7638,9 +7626,9 @@ test "Terminal: cursorRight resets wrap" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.cursorRight(1); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('X'); { @@ -7755,7 +7743,7 @@ test "Terminal: deleteLines colors with bg color" { } for (0..t.cols) |x| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = @intCast(x), .y = 4, } }).?; @@ -7793,8 +7781,8 @@ test "Terminal: deleteLines (legacy)" { try t.linefeed(); // We should be - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); - try testing.expectEqual(@as(usize, 2), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); + try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.y); { const str = try t.plainString(testing.allocator); @@ -7836,8 +7824,8 @@ test "Terminal: deleteLines with scroll region" { try t.linefeed(); // We should be - // try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); - // try testing.expectEqual(@as(usize, 2), t.screen.cursor.y); + // try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); + // try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.y); { const str = try t.plainString(testing.allocator); @@ -7879,8 +7867,8 @@ test "Terminal: deleteLines with scroll region, large count" { try t.linefeed(); // We should be - // try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); - // try testing.expectEqual(@as(usize, 2), t.screen.cursor.y); + // try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); + // try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.y); { const str = try t.plainString(testing.allocator); @@ -7930,9 +7918,9 @@ test "Terminal: deleteLines resets pending wrap" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.deleteLines(1); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('B'); { @@ -7964,7 +7952,7 @@ test "Terminal: deleteLines resets wrap" { } for (0..t.rows) |y| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = @intCast(y), } }).?; @@ -8323,7 +8311,7 @@ test "Terminal: default style is empty" { try t.print('A'); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'A'), cell.content.codepoint); try testing.expectEqual(@as(style.Id, 0), cell.style_id); @@ -8339,12 +8327,12 @@ test "Terminal: bold style" { try t.print('A'); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'A'), cell.content.codepoint); try testing.expect(cell.style_id != 0); - const page = &t.screen.cursor.page_pin.node.data; - try testing.expect(page.styles.refCount(page.memory, t.screen.cursor.style_id) > 1); + const page = &t.screens.active.cursor.page_pin.node.data; + try testing.expect(page.styles.refCount(page.memory, t.screens.active.cursor.style_id) > 1); } } @@ -8360,14 +8348,14 @@ test "Terminal: garbage collect overwritten" { try t.print('B'); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'B'), cell.content.codepoint); try testing.expect(cell.style_id == 0); } // verify we have no styles in our style map - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); } @@ -8382,14 +8370,14 @@ test "Terminal: do not garbage collect old styles in use" { try t.print('B'); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'B'), cell.content.codepoint); try testing.expect(cell.style_id == 0); } // verify we have no styles in our style map - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); } @@ -8404,7 +8392,7 @@ test "Terminal: print with style marks the row as styled" { try t.print('B'); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; try testing.expect(list_cell.row.styled); } } @@ -8421,8 +8409,8 @@ test "Terminal: DECALN" { try t.print('B'); try t.decaln(); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); for (0..t.rows) |y| try testing.expect(t.isDirty(.{ .active = .{ .x = 0, @@ -8473,7 +8461,7 @@ test "Terminal: decaln preserves color" { } { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; try testing.expect(list_cell.cell.content_tag == .bg_color_rgb); try testing.expectEqual(Cell.RGB{ .r = 0xFF, @@ -8502,10 +8490,10 @@ test "Terminal: DECALN resets graphemes with protected mode" { try t.decaln(); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); - try testing.expect(t.screen.cursor.protected); - try testing.expect(t.screen.protected_mode == .iso); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); + try testing.expect(t.screens.active.cursor.protected); + try testing.expect(t.screens.active.protected_mode == .iso); for (0..t.rows) |y| try testing.expect(t.isDirty(.{ .active = .{ .x = 0, @@ -8628,7 +8616,7 @@ test "Terminal: insertBlanks preserves background sgr" { try testing.expectEqualStrings(" ABC", str); } { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; try testing.expect(list_cell.cell.content_tag == .bg_color_rgb); try testing.expectEqual(Cell.RGB{ .r = 0xFF, @@ -8708,11 +8696,11 @@ test "Terminal: insertBlanks outside left/right scroll region" { for ("ABC") |c| try t.print(c); t.scrolling_region.left = 2; t.scrolling_region.right = 4; - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.clearDirty(); t.insertBlanks(2); try testing.expect(!t.isDirty(.{ .active = .{ .x = 0, .y = 0 } })); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('X'); { @@ -8761,7 +8749,7 @@ test "Terminal: insertBlanks deleting graphemes" { try t.print(0x1F467); // We should have one cell with graphemes - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.graphemeCount()); t.setCursorPos(1, 1); @@ -8797,7 +8785,7 @@ test "Terminal: insertBlanks shift graphemes" { try t.print(0x1F467); // We should have one cell with graphemes - const page = &t.screen.cursor.page_pin.node.data; + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.graphemeCount()); t.setCursorPos(1, 1); @@ -8844,7 +8832,7 @@ test "Terminal: insertBlanks shifts hyperlinks" { var t = try init(alloc, .{ .cols = 10, .rows = 2 }); defer t.deinit(alloc); - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.printString("ABC"); t.setCursorPos(1, 1); t.insertBlanks(2); @@ -8857,7 +8845,7 @@ test "Terminal: insertBlanks shifts hyperlinks" { // Verify all our cells have a hyperlink for (2..5) |x| { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = @intCast(x), .y = 0, } }).?; @@ -8869,7 +8857,7 @@ test "Terminal: insertBlanks shifts hyperlinks" { try testing.expectEqual(@as(hyperlink.Id, 1), id); } for (0..2) |x| { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = @intCast(x), .y = 0, } }).?; @@ -8885,7 +8873,7 @@ test "Terminal: insertBlanks pushes hyperlink off end completely" { var t = try init(alloc, .{ .cols = 3, .rows = 2 }); defer t.deinit(alloc); - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); try t.printString("ABC"); t.setCursorPos(1, 1); t.insertBlanks(3); @@ -8897,7 +8885,7 @@ test "Terminal: insertBlanks pushes hyperlink off end completely" { } for (0..3) |x| { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = @intCast(x), .y = 0, } }).?; @@ -9112,9 +9100,9 @@ test "Terminal: deleteChars resets pending wrap" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.deleteChars(1); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('X'); { @@ -9131,7 +9119,7 @@ test "Terminal: deleteChars resets wrap" { for ("ABCDE123") |c| try t.print(c); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; const row = list_cell.row; try testing.expect(row.wrap); } @@ -9139,7 +9127,7 @@ test "Terminal: deleteChars resets wrap" { t.deleteChars(1); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; const row = list_cell.row; try testing.expect(!row.wrap); } @@ -9192,7 +9180,7 @@ test "Terminal: deleteChars preserves background sgr" { try testing.expectEqualStrings("AB23", str); } for (t.cols - 2..t.cols) |x| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = @intCast(x), .y = 0, } }).?; @@ -9213,11 +9201,11 @@ test "Terminal: deleteChars outside scroll region" { try t.printString("ABC123"); t.scrolling_region.left = 2; t.scrolling_region.right = 4; - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.clearDirty(); t.deleteChars(2); try testing.expect(!t.isDirty(.{ .active = .{ .x = 0, .y = 0 } })); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); { const str = try t.plainString(testing.allocator); @@ -9273,13 +9261,13 @@ test "Terminal: deleteChars split wide character from wide" { t.deleteChars(1); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, '1'), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); @@ -9296,13 +9284,13 @@ test "Terminal: deleteChars split wide character from end" { t.deleteChars(1); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0x6A4B), cell.content.codepoint); try testing.expectEqual(Cell.Wide.wide, cell.wide); } { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); @@ -9316,7 +9304,7 @@ test "Terminal: deleteChars with a spacer head at the end" { try t.printString("0123橋123"); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; const row = list_cell.row; const cell = list_cell.cell; try testing.expectEqual(Cell.Wide.spacer_head, cell.wide); @@ -9327,7 +9315,7 @@ test "Terminal: deleteChars with a spacer head at the end" { t.deleteChars(1); { - const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 3, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 3, .y = 0 } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expectEqual(Cell.Wide.narrow, cell.wide); @@ -9407,7 +9395,7 @@ test "Terminal: deleteChars wide char boundary conditions" { t.setCursorPos(1, 2); t.deleteChars(3); - t.screen.cursor.page_pin.node.data.assertIntegrity(); + t.screens.active.cursor.page_pin.node.data.assertIntegrity(); { const str = try t.plainString(alloc); @@ -9459,7 +9447,7 @@ test "Terminal: deleteChars wide char wrap boundary conditions" { t.setCursorPos(2, 2); t.deleteChars(3); - t.screen.cursor.page_pin.node.data.assertIntegrity(); + t.screens.active.cursor.page_pin.node.data.assertIntegrity(); { const str = try t.plainString(alloc); @@ -9498,7 +9486,7 @@ test "Terminal: deleteChars wide char across right margin" { t.setCursorPos(1, 2); t.deleteChars(1); - t.screen.cursor.page_pin.node.data.assertIntegrity(); + t.screens.active.cursor.page_pin.node.data.assertIntegrity(); // NOTE: This behavior is slightly inconsistent with xterm. xterm // _visually_ splits the wide character (half the wide character shows @@ -9521,15 +9509,15 @@ test "Terminal: saveCursor" { defer t.deinit(alloc); try t.setAttribute(.{ .bold = {} }); - t.screen.charset.gr = .G3; + t.screens.active.charset.gr = .G3; t.modes.set(.origin, true); t.saveCursor(); - t.screen.charset.gr = .G0; + t.screens.active.charset.gr = .G0; try t.setAttribute(.{ .unset = {} }); t.modes.set(.origin, false); try t.restoreCursor(); - try testing.expect(t.screen.cursor.style.flags.bold); - try testing.expect(t.screen.charset.gr == .G3); + try testing.expect(t.screens.active.cursor.style.flags.bold); + try testing.expect(t.screens.active.charset.gr == .G3); try testing.expect(t.modes.get(.origin)); } @@ -9617,13 +9605,13 @@ test "Terminal: saveCursor protected pen" { defer t.deinit(alloc); t.setProtectedMode(.iso); - try testing.expect(t.screen.cursor.protected); + try testing.expect(t.screens.active.cursor.protected); t.setCursorPos(1, 10); t.saveCursor(); t.setProtectedMode(.off); - try testing.expect(!t.screen.cursor.protected); + try testing.expect(!t.screens.active.cursor.protected); try t.restoreCursor(); - try testing.expect(t.screen.cursor.protected); + try testing.expect(t.screens.active.cursor.protected); } test "Terminal: saveCursor doesn't modify hyperlink state" { @@ -9631,12 +9619,12 @@ test "Terminal: saveCursor doesn't modify hyperlink state" { var t = try init(alloc, .{ .cols = 3, .rows = 3 }); defer t.deinit(alloc); - try t.screen.startHyperlink("http://example.com", null); - const id = t.screen.cursor.hyperlink_id; + try t.screens.active.startHyperlink("http://example.com", null); + const id = t.screens.active.cursor.hyperlink_id; t.saveCursor(); - try testing.expectEqual(id, t.screen.cursor.hyperlink_id); + try testing.expectEqual(id, t.screens.active.cursor.hyperlink_id); try t.restoreCursor(); - try testing.expectEqual(id, t.screen.cursor.hyperlink_id); + try testing.expectEqual(id, t.screens.active.cursor.hyperlink_id); } test "Terminal: setProtectedMode" { @@ -9644,15 +9632,15 @@ test "Terminal: setProtectedMode" { var t = try init(alloc, .{ .cols = 3, .rows = 3 }); defer t.deinit(alloc); - try testing.expect(!t.screen.cursor.protected); + try testing.expect(!t.screens.active.cursor.protected); t.setProtectedMode(.off); - try testing.expect(!t.screen.cursor.protected); + try testing.expect(!t.screens.active.cursor.protected); t.setProtectedMode(.iso); - try testing.expect(t.screen.cursor.protected); + try testing.expect(t.screens.active.cursor.protected); t.setProtectedMode(.dec); - try testing.expect(t.screen.cursor.protected); + try testing.expect(t.screens.active.cursor.protected); t.setProtectedMode(.off); - try testing.expect(!t.screen.cursor.protected); + try testing.expect(!t.screens.active.cursor.protected); } test "Terminal: eraseLine simple erase right" { @@ -9679,9 +9667,9 @@ test "Terminal: eraseLine resets pending wrap" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.eraseLine(.right, false); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('B'); { @@ -9698,7 +9686,7 @@ test "Terminal: eraseLine resets wrap" { for ("ABCDE123") |c| try t.print(c); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; try testing.expect(list_cell.row.wrap); } @@ -9706,7 +9694,7 @@ test "Terminal: eraseLine resets wrap" { t.eraseLine(.right, false); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; try testing.expect(!list_cell.row.wrap); } try t.print('X'); @@ -9737,7 +9725,7 @@ test "Terminal: eraseLine right preserves background sgr" { defer testing.allocator.free(str); try testing.expectEqualStrings("A", str); for (1..5) |x| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = @intCast(x), .y = 0, } }).?; @@ -9836,10 +9824,10 @@ test "Terminal: eraseLine right protected requested" { defer t.deinit(alloc); for ("12345678") |c| try t.print(c); - t.setCursorPos(t.screen.cursor.y + 1, 6); + t.setCursorPos(t.screens.active.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorPos(t.screen.cursor.y + 1, 4); + t.setCursorPos(t.screens.active.cursor.y + 1, 4); t.clearDirty(); t.eraseLine(.right, true); try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 0 } })); @@ -9875,11 +9863,11 @@ test "Terminal: eraseLine left resets wrap" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.clearDirty(); t.eraseLine(.left, false); try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 0 } })); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); try t.print('B'); { @@ -9908,7 +9896,7 @@ test "Terminal: eraseLine left preserves background sgr" { defer testing.allocator.free(str); try testing.expectEqualStrings(" CDE", str); for (0..2) |x| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = @intCast(x), .y = 0, } }).?; @@ -10007,10 +9995,10 @@ test "Terminal: eraseLine left protected requested" { defer t.deinit(alloc); for ("123456789") |c| try t.print(c); - t.setCursorPos(t.screen.cursor.y + 1, 6); + t.setCursorPos(t.screens.active.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorPos(t.screen.cursor.y + 1, 8); + t.setCursorPos(t.screens.active.cursor.y + 1, 8); t.clearDirty(); t.eraseLine(.left, true); try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 0 } })); @@ -10041,7 +10029,7 @@ test "Terminal: eraseLine complete preserves background sgr" { defer testing.allocator.free(str); try testing.expectEqualStrings("", str); for (0..5) |x| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = @intCast(x), .y = 0, } }).?; @@ -10120,10 +10108,10 @@ test "Terminal: eraseLine complete protected requested" { defer t.deinit(alloc); for ("123456789") |c| try t.print(c); - t.setCursorPos(t.screen.cursor.y + 1, 6); + t.setCursorPos(t.screens.active.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorPos(t.screen.cursor.y + 1, 8); + t.setCursorPos(t.screens.active.cursor.y + 1, 8); t.clearDirty(); t.eraseLine(.complete, true); try testing.expect(t.isDirty(.{ .active = .{ .x = 0, .y = 0 } })); @@ -10145,7 +10133,7 @@ test "Terminal: tabClear single" { try testing.expect(!t.isDirty(.{ .active = .{ .x = 0, .y = 0 } })); t.setCursorPos(1, 1); try t.horizontalTab(); - try testing.expectEqual(@as(usize, 16), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 16), t.screens.active.cursor.x); } test "Terminal: tabClear all" { @@ -10157,7 +10145,7 @@ test "Terminal: tabClear all" { try testing.expect(!t.isDirty(.{ .active = .{ .x = 0, .y = 0 } })); t.setCursorPos(1, 1); try t.horizontalTab(); - try testing.expectEqual(@as(usize, 29), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 29), t.screens.active.cursor.x); } test "Terminal: printRepeat simple" { @@ -10312,7 +10300,7 @@ test "Terminal: eraseDisplay erase below preserves SGR bg" { defer testing.allocator.free(str); try testing.expectEqualStrings("ABC\nD", str); for (1..5) |x| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = @intCast(x), .y = 1, } }).?; @@ -10495,7 +10483,7 @@ test "Terminal: eraseDisplay erase above preserves SGR bg" { defer testing.allocator.free(str); try testing.expectEqualStrings("\n F\nGHI", str); for (0..2) |x| { - const list_cell = t.screen.pages.getCell(.{ .active = .{ + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = @intCast(x), .y = 1, } }).?; @@ -10634,10 +10622,10 @@ test "Terminal: eraseDisplay protected complete" { t.carriageReturn(); try t.linefeed(); for ("123456789") |c| try t.print(c); - t.setCursorPos(t.screen.cursor.y + 1, 6); + t.setCursorPos(t.screens.active.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorPos(t.screen.cursor.y + 1, 4); + t.setCursorPos(t.screens.active.cursor.y + 1, 4); t.clearDirty(); t.eraseDisplay(.complete, true); @@ -10662,10 +10650,10 @@ test "Terminal: eraseDisplay protected below" { t.carriageReturn(); try t.linefeed(); for ("123456789") |c| try t.print(c); - t.setCursorPos(t.screen.cursor.y + 1, 6); + t.setCursorPos(t.screens.active.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorPos(t.screen.cursor.y + 1, 4); + t.setCursorPos(t.screens.active.cursor.y + 1, 4); t.eraseDisplay(.below, true); { @@ -10701,10 +10689,10 @@ test "Terminal: eraseDisplay protected above" { t.carriageReturn(); try t.linefeed(); for ("123456789") |c| try t.print(c); - t.setCursorPos(t.screen.cursor.y + 1, 6); + t.setCursorPos(t.screens.active.cursor.y + 1, 6); t.setProtectedMode(.dec); try t.print('X'); - t.setCursorPos(t.screen.cursor.y + 1, 8); + t.setCursorPos(t.screens.active.cursor.y + 1, 8); t.eraseDisplay(.above, true); { @@ -10722,13 +10710,13 @@ test "Terminal: eraseDisplay complete preserves cursor" { // Set our cursur try t.setAttribute(.{ .bold = {} }); try t.printString("AAAA"); - try testing.expect(t.screen.cursor.style_id != style.default_id); + try testing.expect(t.screens.active.cursor.style_id != style.default_id); // Erasing the display may detect that our style is no longer in use // and prune our style, which we don't want because its still our // active cursor. t.eraseDisplay(.complete, false); - try testing.expect(t.screen.cursor.style_id != style.default_id); + try testing.expect(t.screens.active.cursor.style_id != style.default_id); } test "Terminal: cursorIsAtPrompt" { @@ -10786,24 +10774,24 @@ test "Terminal: fullReset with a non-empty pen" { t.fullReset(); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ - .x = t.screen.cursor.x, - .y = t.screen.cursor.y, + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ + .x = t.screens.active.cursor.x, + .y = t.screens.active.cursor.y, } }).?; const cell = list_cell.cell; try testing.expect(cell.style_id == 0); } - try testing.expectEqual(@as(style.Id, 0), t.screen.cursor.style_id); + try testing.expectEqual(@as(style.Id, 0), t.screens.active.cursor.style_id); } test "Terminal: fullReset hyperlink" { var t = try init(testing.allocator, .{ .cols = 80, .rows = 80 }); defer t.deinit(testing.allocator); - try t.screen.startHyperlink("http://example.com", null); + try t.screens.active.startHyperlink("http://example.com", null); t.fullReset(); - try testing.expectEqual(0, t.screen.cursor.hyperlink_id); + try testing.expectEqual(0, t.screens.active.cursor.hyperlink_id); } test "Terminal: fullReset with a non-empty saved cursor" { @@ -10816,15 +10804,15 @@ test "Terminal: fullReset with a non-empty saved cursor" { t.fullReset(); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ - .x = t.screen.cursor.x, - .y = t.screen.cursor.y, + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ + .x = t.screens.active.cursor.x, + .y = t.screens.active.cursor.y, } }).?; const cell = list_cell.cell; try testing.expect(cell.style_id == 0); } - try testing.expectEqual(@as(style.Id, 0), t.screen.cursor.style_id); + try testing.expectEqual(@as(style.Id, 0), t.screens.active.cursor.style_id); } test "Terminal: fullReset origin mode" { @@ -10836,8 +10824,8 @@ test "Terminal: fullReset origin mode" { t.fullReset(); // Origin mode should be reset and the cursor should be moved - try testing.expectEqual(@as(usize, 0), t.screen.cursor.y); - try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); try testing.expect(!t.modes.get(.origin)); } @@ -10856,7 +10844,7 @@ test "Terminal: fullReset clears alt screen kitty keyboard state" { defer t.deinit(testing.allocator); try t.switchScreenMode(.@"1049", true); - t.screen.kitty_keyboard.push(.{ + t.screens.active.kitty_keyboard.push(.{ .disambiguate = true, .report_events = false, .report_alternates = true, @@ -10886,9 +10874,9 @@ test "Terminal: fullReset tracked pins" { defer t.deinit(testing.allocator); // Create a tracked pin - const p = try t.screen.pages.trackPin(t.screen.cursor.page_pin.*); + const p = try t.screens.active.pages.trackPin(t.screens.active.cursor.page_pin.*); t.fullReset(); - try testing.expect(t.screen.pages.pinIsValid(p.*)); + try testing.expect(t.screens.active.pages.pinIsValid(p.*)); } // https://github.com/mitchellh/ghostty/issues/272 @@ -11014,9 +11002,9 @@ test "Terminal: resize with reflow and saved cursor" { try t.printString("1A2B"); t.setCursorPos(2, 2); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ - .x = t.screen.cursor.x, - .y = t.screen.cursor.y, + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ + .x = t.screens.active.cursor.x, + .y = t.screens.active.cursor.y, } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u32, 'B'), cell.content.codepoint); @@ -11040,9 +11028,9 @@ test "Terminal: resize with reflow and saved cursor" { // Verify our cursor is still in the same place { - const list_cell = t.screen.pages.getCell(.{ .active = .{ - .x = t.screen.cursor.x, - .y = t.screen.cursor.y, + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ + .x = t.screens.active.cursor.x, + .y = t.screens.active.cursor.y, } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u32, 'B'), cell.content.codepoint); @@ -11055,9 +11043,9 @@ test "Terminal: resize with reflow and saved cursor pending wrap" { defer t.deinit(alloc); try t.printString("1A2B"); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ - .x = t.screen.cursor.x, - .y = t.screen.cursor.y, + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ + .x = t.screens.active.cursor.x, + .y = t.screens.active.cursor.y, } }).?; const cell = list_cell.cell; try testing.expectEqual(@as(u32, 'B'), cell.content.codepoint); @@ -11117,13 +11105,13 @@ test "Terminal: DECCOLM resets pending wrap" { defer t.deinit(alloc); for ("ABCDE") |c| try t.print(c); - try testing.expect(t.screen.cursor.pending_wrap); + try testing.expect(t.screens.active.cursor.pending_wrap); t.modes.set(.enable_mode_3, true); try t.deccolm(alloc, .@"80_cols"); try testing.expectEqual(@as(usize, 80), t.cols); try testing.expectEqual(@as(usize, 5), t.rows); - try testing.expect(!t.screen.cursor.pending_wrap); + try testing.expect(!t.screens.active.cursor.pending_wrap); } test "Terminal: DECCOLM preserves SGR bg" { @@ -11140,7 +11128,7 @@ test "Terminal: DECCOLM preserves SGR bg" { try t.deccolm(alloc, .@"80_cols"); { - const list_cell = t.screen.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; + const list_cell = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 0 } }).?; try testing.expect(list_cell.cell.content_tag == .bg_color_rgb); try testing.expectEqual(Cell.RGB{ .r = 0xFF, @@ -11234,10 +11222,10 @@ test "Terminal: mode 47 copies cursor both directions" { // Verify that our style is set { - try testing.expect(t.screen.cursor.style_id != style.default_id); - const page = &t.screen.cursor.page_pin.node.data; + try testing.expect(t.screens.active.cursor.style_id != style.default_id); + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); - try testing.expect(page.styles.refCount(page.memory, t.screen.cursor.style_id) > 0); + try testing.expect(page.styles.refCount(page.memory, t.screens.active.cursor.style_id) > 0); } // Set a new style @@ -11249,10 +11237,10 @@ test "Terminal: mode 47 copies cursor both directions" { // Verify that our style is still set { - try testing.expect(t.screen.cursor.style_id != style.default_id); - const page = &t.screen.cursor.page_pin.node.data; + try testing.expect(t.screens.active.cursor.style_id != style.default_id); + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); - try testing.expect(page.styles.refCount(page.memory, t.screen.cursor.style_id) > 0); + try testing.expect(page.styles.refCount(page.memory, t.screens.active.cursor.style_id) > 0); } } @@ -11321,10 +11309,10 @@ test "Terminal: mode 1047 copies cursor both directions" { // Verify that our style is set { - try testing.expect(t.screen.cursor.style_id != style.default_id); - const page = &t.screen.cursor.page_pin.node.data; + try testing.expect(t.screens.active.cursor.style_id != style.default_id); + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); - try testing.expect(page.styles.refCount(page.memory, t.screen.cursor.style_id) > 0); + try testing.expect(page.styles.refCount(page.memory, t.screens.active.cursor.style_id) > 0); } // Set a new style @@ -11336,10 +11324,10 @@ test "Terminal: mode 1047 copies cursor both directions" { // Verify that our style is still set { - try testing.expect(t.screen.cursor.style_id != style.default_id); - const page = &t.screen.cursor.page_pin.node.data; + try testing.expect(t.screens.active.cursor.style_id != style.default_id); + const page = &t.screens.active.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); - try testing.expect(page.styles.refCount(page.memory, t.screen.cursor.style_id) > 0); + try testing.expect(page.styles.refCount(page.memory, t.screens.active.cursor.style_id) > 0); } } diff --git a/src/terminal/formatter.zig b/src/terminal/formatter.zig index 0a742ccb1..35fd71665 100644 --- a/src/terminal/formatter.zig +++ b/src/terminal/formatter.zig @@ -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("&\"'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 diff --git a/src/terminal/kitty/graphics_exec.zig b/src/terminal/kitty/graphics_exec.zig index 9cf0bda0c..1559c0cec 100644 --- a/src/terminal/kitty/graphics_exec.zig +++ b/src/terminal/kitty/graphics_exec.zig @@ -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); } diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig index 34b9a6e85..cfa654ae8 100644 --- a/src/terminal/kitty/graphics_storage.zig +++ b/src/terminal/kitty/graphics_storage.zig @@ -232,7 +232,7 @@ pub const ImageStorage = struct { // Deinit the placement and remove it const image_id = entry.key_ptr.image_id; - entry.value_ptr.deinit(t.screen); + entry.value_ptr.deinit(t.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" { diff --git a/src/terminal/kitty/graphics_unicode.zig b/src/terminal/kitty/graphics_unicode.zig index 491c3e110..b2a90296c 100644 --- a/src/terminal/kitty/graphics_unicode.zig +++ b/src/terminal/kitty/graphics_unicode.zig @@ -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, diff --git a/src/terminal/search/active.zig b/src/terminal/search/active.zig index d05417747..2ace939e7 100644 --- a/src/terminal/search/active.zig +++ b/src/terminal/search/active.zig @@ -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); } diff --git a/src/terminal/search/pagelist.zig b/src/terminal/search/pagelist.zig index b1ad88e81..8b6b57949 100644 --- a/src/terminal/search/pagelist.zig +++ b/src/terminal/search/pagelist.zig @@ -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(); diff --git a/src/terminal/search/screen.zig b/src/terminal/search/screen.zig index 674f08b8c..721caeca9 100644 --- a/src/terminal/search/screen.zig +++ b/src/terminal/search/screen.zig @@ -391,7 +391,7 @@ test "simple search" { defer s.deinit(); try s.nextSlice("Fizz\r\nBuzz\r\nFizz\r\nBang"); - var search: ScreenSearch = try .init(alloc, t.screen, "Fizz"); + var search: ScreenSearch = try .init(alloc, t.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()).?); } } diff --git a/src/terminal/stream_readonly.zig b/src/terminal/stream_readonly.zig index d34e4c84c..3b088e2b7 100644 --- a/src/terminal/stream_readonly.zig +++ b/src/terminal/stream_readonly.zig @@ -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; } } @@ -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); @@ -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)); diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index 6371722b5..9bcbd38ca 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -257,7 +257,7 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { } // 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; @@ -579,17 +579,17 @@ pub fn clearScreen(self: *Termio, td: *ThreadData, history: bool) !void { 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 } }, ); } @@ -599,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 }, ); @@ -633,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(); diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 4b40ff3cf..fd94f77bc 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -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; } } @@ -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(.{ From 4ba00dbe89267619bf170c7ecc06a0b180386050 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Nov 2025 15:44:57 -0800 Subject: [PATCH 4/5] fix harfbuzz --- src/font/shaper/harfbuzz.zig | 40 ++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/font/shaper/harfbuzz.zig b/src/font/shaper/harfbuzz.zig index 634afd0de..83de69cfe 100644 --- a/src/font/shaper/harfbuzz.zig +++ b/src/font/shaper/harfbuzz.zig @@ -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; @@ -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]); @@ -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("👍🏼"); @@ -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(">"); From 2452026ff34c8633ff4849c8496bb574a5510af1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Nov 2025 15:52:59 -0800 Subject: [PATCH 5/5] terminal: kitty limits only if kitty graphics being built --- src/terminal/Terminal.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index f67cb119c..472b390d1 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -2614,7 +2614,10 @@ pub fn switchScreen(self: *Terminal, key: ScreenSet.Key) !?*Screen { // Inherit our Kitty image storage limit from the primary // screen if we have to initialize. - .kitty_image_storage_limit = primary.kitty_images.total_limit, + .kitty_image_storage_limit = if (comptime build_options.kitty_graphics) + primary.kitty_images.total_limit + else + 0, }, ); };