alternate screen buffer (mode 1049) now supported

This commit is contained in:
Mitchell Hashimoto
2022-07-22 13:35:11 -07:00
parent 5564bd7213
commit d672bedec7
4 changed files with 90 additions and 6 deletions

View File

@ -775,6 +775,21 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void {
self.terminal.modes.autowrap = @boolToInt(enabled); 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, .bracketed_paste => self.bracketed_paste = true,
.enable_mode_3 => self.terminal.modes.enable_mode_3 = @boolToInt(enabled), .enable_mode_3 => self.terminal.modes.enable_mode_3 = @boolToInt(enabled),

View File

@ -22,8 +22,18 @@ const log = std.log.scoped(.terminal);
/// Default tabstop interval /// Default tabstop interval
const TABSTOP_INTERVAL = 8; 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, screen: Screen,
secondary_screen: Screen,
/// Where the tabstops are. /// Where the tabstops are.
tabstops: Tabstops, tabstops: Tabstops,
@ -60,8 +70,11 @@ pub fn init(alloc: Allocator, cols: usize, rows: usize) !Terminal {
return Terminal{ return Terminal{
.cols = cols, .cols = cols,
.rows = rows, .rows = rows,
.active_screen = .primary,
// TODO: configurable scrollback // TODO: configurable scrollback
.screen = try Screen.init(alloc, rows, cols, 1000), .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), .tabstops = try Tabstops.init(alloc, cols, TABSTOP_INTERVAL),
.scrolling_region = .{ .scrolling_region = .{
.top = 0, .top = 0,
@ -73,9 +86,62 @@ pub fn init(alloc: Allocator, cols: usize, rows: usize) !Terminal {
pub fn deinit(self: *Terminal, alloc: Allocator) void { pub fn deinit(self: *Terminal, alloc: Allocator) void {
self.tabstops.deinit(alloc); self.tabstops.deinit(alloc);
self.screen.deinit(alloc); self.screen.deinit(alloc);
self.secondary_screen.deinit(alloc);
self.* = undefined; 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. /// Resize the underlying terminal.
pub fn resize(self: *Terminal, alloc: Allocator, cols: usize, rows: usize) !void { pub fn resize(self: *Terminal, alloc: Allocator, cols: usize, rows: usize) !void {
// TODO: test, wrapping, etc. // TODO: test, wrapping, etc.

View File

@ -63,6 +63,9 @@ pub const Mode = enum(u16) {
/// mode ?3 is set or unset. /// mode ?3 is set or unset.
enable_mode_3 = 40, 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. /// Bracket clipboard paste contents in delimiter sequences.
/// ///
/// When pasting from the (e.g. system) clipboard add "ESC [ 2 0 0 ~" /// When pasting from the (e.g. system) clipboard add "ESC [ 2 0 0 ~"

View File

@ -48,11 +48,11 @@ pub fn Stream(comptime Handler: type) type {
//log.debug("char: {x}", .{c}); //log.debug("char: {x}", .{c});
const actions = self.parser.next(c); const actions = self.parser.next(c);
for (actions) |action_opt| { for (actions) |action_opt| {
if (action_opt) |action| { // if (action_opt) |action| {
if (action != .print) { // if (action != .print) {
log.info("action: {}", .{action}); // log.info("action: {}", .{action});
} // }
} // }
switch (action_opt orelse continue) { switch (action_opt orelse continue) {
.print => |p| if (@hasDecl(T, "print")) try self.handler.print(p), .print => |p| if (@hasDecl(T, "print")) try self.handler.print(p),
.execute => |code| try self.execute(code), .execute => |code| try self.execute(code),