From d672bedec725f7167507f35cfadfbd1146280c6e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 22 Jul 2022 13:35:11 -0700 Subject: [PATCH] alternate screen buffer (mode 1049) now supported --- src/Window.zig | 15 +++++++++ src/terminal/Terminal.zig | 68 ++++++++++++++++++++++++++++++++++++++- src/terminal/ansi.zig | 3 ++ src/terminal/stream.zig | 10 +++--- 4 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index ff2ea4a6b..caa280e98 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -775,6 +775,21 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { self.terminal.modes.autowrap = @boolToInt(enabled); }, + .alt_screen_save_cursor_clear_enter => { + const opts: terminal.Terminal.AlternateScreenOptions = .{ + .cursor_save = true, + .clear_on_enter = true, + }; + + if (enabled) + self.terminal.alternateScreen(opts) + else + self.terminal.primaryScreen(opts); + + // Schedule a render since we changed screens + try self.render_timer.schedule(); + }, + .bracketed_paste => self.bracketed_paste = true, .enable_mode_3 => self.terminal.modes.enable_mode_3 = @boolToInt(enabled), diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index ac5343c81..dacaf1890 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -22,8 +22,18 @@ const log = std.log.scoped(.terminal); /// Default tabstop interval const TABSTOP_INTERVAL = 8; -/// Screen is the current screen state. +/// Screen type is an enum that tracks whether a screen is primary or alternate. +const ScreenType = enum { + primary, + alternate, +}; + +/// 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, /// Where the tabstops are. tabstops: Tabstops, @@ -60,8 +70,11 @@ pub fn init(alloc: Allocator, cols: usize, rows: usize) !Terminal { return Terminal{ .cols = cols, .rows = rows, + .active_screen = .primary, // TODO: configurable scrollback .screen = try Screen.init(alloc, rows, cols, 1000), + // No scrollback for the alternate screen + .secondary_screen = try Screen.init(alloc, rows, cols, 0), .tabstops = try Tabstops.init(alloc, cols, TABSTOP_INTERVAL), .scrolling_region = .{ .top = 0, @@ -73,9 +86,62 @@ pub fn init(alloc: Allocator, cols: usize, rows: usize) !Terminal { pub fn deinit(self: *Terminal, alloc: Allocator) void { self.tabstops.deinit(alloc); self.screen.deinit(alloc); + self.secondary_screen.deinit(alloc); self.* = undefined; } +/// 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 { + // 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; + + if (options.clear_on_enter) { + self.eraseDisplay(.complete); + } +} + +/// Switch back to the primary screen (reset alternate screen mode). +pub fn primaryScreen(self: *Terminal, options: AlternateScreenOptions) void { + // 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); + + // Switch the screens + const old = self.screen; + self.screen = self.secondary_screen; + self.secondary_screen = old; + self.active_screen = .primary; + + // Restore the cursor from the primary screen + if (options.cursor_save) self.restoreCursor(); +} + /// Resize the underlying terminal. pub fn resize(self: *Terminal, alloc: Allocator, cols: usize, rows: usize) !void { // TODO: test, wrapping, etc. diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index db60ad76b..64f8eabe0 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -63,6 +63,9 @@ pub const Mode = enum(u16) { /// mode ?3 is set or unset. enable_mode_3 = 40, + /// Alternate screen mode with save cursor and clear on enter. + alt_screen_save_cursor_clear_enter = 1049, + /// Bracket clipboard paste contents in delimiter sequences. /// /// When pasting from the (e.g. system) clipboard add "ESC [ 2 0 0 ~" diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 9c75ea55b..7f456fc98 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -48,11 +48,11 @@ pub fn Stream(comptime Handler: type) type { //log.debug("char: {x}", .{c}); const actions = self.parser.next(c); for (actions) |action_opt| { - if (action_opt) |action| { - if (action != .print) { - log.info("action: {}", .{action}); - } - } + // if (action_opt) |action| { + // if (action != .print) { + // log.info("action: {}", .{action}); + // } + // } switch (action_opt orelse continue) { .print => |p| if (@hasDecl(T, "print")) try self.handler.print(p), .execute => |code| try self.execute(code),