diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 17f2b6af9..d88207892 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -4939,6 +4939,7 @@ test "Terminal: cursorIsAtPrompt" { try testing.expect(t.cursorIsAtPrompt()); } +// X test "Terminal: cursorIsAtPrompt alternate screen" { const alloc = testing.allocator; var t = try init(alloc, 3, 2); @@ -5491,6 +5492,7 @@ test "Terminal: saveCursor" { try testing.expect(t.modes.get(.origin)); } +// X test "Terminal: saveCursor with screen change" { const alloc = testing.allocator; var t = try init(alloc, 3, 3); diff --git a/src/terminal/new/Screen.zig b/src/terminal/new/Screen.zig index 9c9cf76c3..02f3983a4 100644 --- a/src/terminal/new/Screen.zig +++ b/src/terminal/new/Screen.zig @@ -8,6 +8,7 @@ const charsets = @import("../charsets.zig"); const kitty = @import("../kitty.zig"); const sgr = @import("../sgr.zig"); const unicode = @import("../../unicode/main.zig"); +const Selection = @import("../Selection.zig"); const PageList = @import("PageList.zig"); const pagepkg = @import("page.zig"); const point = @import("point.zig"); @@ -30,6 +31,9 @@ cursor: Cursor, /// The saved cursor saved_cursor: ?SavedCursor = null, +/// The selection for this screen (if any). +selection: ?Selection = null, + /// The charset state charset: CharsetState = .{}, diff --git a/src/terminal/new/Terminal.zig b/src/terminal/new/Terminal.zig index 0771fd5f4..5aeabd7c1 100644 --- a/src/terminal/new/Terminal.zig +++ b/src/terminal/new/Terminal.zig @@ -1950,6 +1950,98 @@ pub fn printAttributes(self: *Terminal, buf: []u8) ![]const u8 { return stream.getWritten(); } +/// Options for switching to the alternate screen. +pub const AlternateScreenOptions = struct { + cursor_save: bool = false, + clear_on_enter: bool = false, + clear_on_exit: bool = false, +}; + +/// Switch to the alternate screen buffer. +/// +/// The alternate screen buffer: +/// * has its own grid +/// * has its own cursor state (included saved cursor) +/// * does not support scrollback +/// +pub fn alternateScreen( + self: *Terminal, + options: AlternateScreenOptions, +) void { + //log.info("alt screen active={} options={} cursor={}", .{ self.active_screen, options, self.screen.cursor }); + + // TODO: test + // TODO(mitchellh): what happens if we enter alternate screen multiple times? + // for now, we ignore... + if (self.active_screen == .alternate) return; + + // If we requested cursor save, we save the cursor in the primary screen + if (options.cursor_save) self.saveCursor(); + + // Switch the screens + const old = self.screen; + self.screen = self.secondary_screen; + self.secondary_screen = old; + self.active_screen = .alternate; + + // Bring our charset state with us + self.screen.charset = old.charset; + + // Clear our selection + self.screen.selection = null; + + // Mark kitty images as dirty so they redraw + self.screen.kitty_images.dirty = true; + + // Bring our pen with us + self.screen.cursor = old.cursor; + self.screen.cursor.style_id = 0; + self.screen.cursor.style_ref = null; + self.screen.cursorAbsolute(old.cursor.x, old.cursor.y); + + if (options.clear_on_enter) { + self.eraseDisplay(.complete, false); + } + + // Update any style ref after we erase the display so we definitely have space + self.screen.manualStyleUpdate() catch |err| { + log.warn("style update failed entering alt screen err={}", .{err}); + }; +} + +/// Switch back to the primary screen (reset alternate screen mode). +pub fn primaryScreen( + self: *Terminal, + options: AlternateScreenOptions, +) void { + //log.info("primary screen active={} options={}", .{ self.active_screen, options }); + + // TODO: test + // TODO(mitchellh): what happens if we enter alternate screen multiple times? + if (self.active_screen == .primary) return; + + if (options.clear_on_exit) self.eraseDisplay(.complete, false); + + // Switch the screens + const old = self.screen; + self.screen = self.secondary_screen; + self.secondary_screen = old; + self.active_screen = .primary; + + // Clear our selection + self.screen.selection = null; + + // Mark kitty images as dirty so they redraw + self.screen.kitty_images.dirty = true; + + // Restore the cursor from the primary screen. This should not + // fail because we should not have to allocate memory since swapping + // screens does not create new cursors. + if (options.cursor_save) self.restoreCursor() catch |err| { + log.warn("restore cursor on primary screen failed err={}", .{err}); + }; +} + /// Return the current string value of the terminal. Newlines are /// encoded as "\n". This omits any formatting such as fg/bg. /// @@ -6143,6 +6235,36 @@ test "Terminal: saveCursor" { try testing.expect(t.modes.get(.origin)); } +test "Terminal: saveCursor with screen change" { + const alloc = testing.allocator; + var t = try init(alloc, 3, 3); + defer t.deinit(alloc); + + try t.setAttribute(.{ .bold = {} }); + t.screen.cursor.x = 2; + t.screen.charset.gr = .G3; + t.modes.set(.origin, true); + t.alternateScreen(.{ + .cursor_save = true, + .clear_on_enter = true, + }); + // make sure our cursor and charset have come with us + try testing.expect(t.screen.cursor.style.flags.bold); + try testing.expect(t.screen.cursor.x == 2); + try testing.expect(t.screen.charset.gr == .G3); + try testing.expect(t.modes.get(.origin)); + t.screen.charset.gr = .G0; + try t.setAttribute(.{ .reset_bold = {} }); + t.modes.set(.origin, false); + t.primaryScreen(.{ + .cursor_save = true, + .clear_on_enter = true, + }); + try testing.expect(t.screen.cursor.style.flags.bold); + try testing.expect(t.screen.charset.gr == .G3); + try testing.expect(t.modes.get(.origin)); +} + test "Terminal: saveCursor position" { const alloc = testing.allocator; var t = try init(alloc, 10, 5); @@ -7251,3 +7373,19 @@ test "Terminal: cursorIsAtPrompt" { t.markSemanticPrompt(.prompt); try testing.expect(t.cursorIsAtPrompt()); } + +test "Terminal: cursorIsAtPrompt alternate screen" { + const alloc = testing.allocator; + var t = try init(alloc, 3, 2); + defer t.deinit(alloc); + + try testing.expect(!t.cursorIsAtPrompt()); + t.markSemanticPrompt(.prompt); + try testing.expect(t.cursorIsAtPrompt()); + + // Secondary screen is never a prompt + t.alternateScreen(.{}); + try testing.expect(!t.cursorIsAtPrompt()); + t.markSemanticPrompt(.prompt); + try testing.expect(!t.cursorIsAtPrompt()); +}