From 5c8c9d8e3ce5715e3cc58705ac51e2979b88ef25 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 24 Jul 2022 16:06:04 -0700 Subject: [PATCH] support DECCOLM This gets vttest page 1 and page 2 now FULL passing. We now crash on page 3. This is a lingering bug in our grid code though and we need to find it anyways so we'll keep the crash in. --- src/Window.zig | 25 ++++++------- src/terminal/Terminal.zig | 54 +++++++++++++++++++++++++-- src/terminal/stream.zig | 6 +-- test/cases/vttest/1_1.sh.ghostty.png | Bin 10457 -> 10462 bytes test/cases/vttest/1_2.sh.ghostty.png | Bin 10693 -> 10703 bytes 5 files changed, 65 insertions(+), 20 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index b190b75de..8a34a4053 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -809,23 +809,20 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { .bracketed_paste => self.bracketed_paste = true, - .enable_mode_3 => self.terminal.modes.enable_mode_3 = @boolToInt(enabled), - .@"132_column" => mode3: { - // TODO: test this + .enable_mode_3 => { + // Disable deccolm + self.terminal.setDeccolmSupported(enabled); - // Do nothing if "enable mode 3" is not set. - if (self.terminal.modes.enable_mode_3 == 0) break :mode3; - - // Set it - self.terminal.modes.@"132_column" = @boolToInt(enabled); - - // TODO: do not clear screen flag mode - self.terminal.eraseDisplay(.complete); - self.terminal.setCursorPos(1, 1); - - // TODO: left/right margins + // Force resize back to the window size + self.terminal.resize(self.alloc, self.grid.size.columns, self.grid.size.rows) catch |err| + log.err("error updating terminal size: {}", .{err}); }, + .@"132_column" => try self.terminal.deccolm( + self.alloc, + if (enabled) .@"132_cols" else .@"80_cols", + ), + else => if (enabled) log.warn("unimplemented mode: {}", .{mode}), } } diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index d9615ce17..4ce1511fd 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -52,8 +52,8 @@ modes: packed struct { origin: u1 = 0, // 6 autowrap: u1 = 1, // 7 - @"132_column": u1 = 0, // 3, - enable_mode_3: u1 = 0, // 40 + deccolm: u1 = 0, // 3, + deccolm_supported: u1 = 0, // 40 } = .{}, /// Scrolling region is the area of the screen designated where scrolling @@ -142,10 +142,58 @@ pub fn primaryScreen(self: *Terminal, options: AlternateScreenOptions) void { if (options.cursor_save) self.restoreCursor(); } +/// The modes for DECCOLM. +pub const DeccolmMode = enum(u1) { + @"80_cols" = 0, + @"132_cols" = 1, +}; + +/// DECCOLM changes the terminal width between 80 and 132 columns. This +/// function call will do NOTHING unless `setDeccolmSupported` has been +/// called with "true". +/// +/// This breaks the expectation around modern terminals that they resize +/// with the window. This will fix the grid at either 80 or 132 columns. +/// The rows will continue to be variable. +pub fn deccolm(self: *Terminal, alloc: Allocator, mode: DeccolmMode) !void { + // TODO: test + + // We need to support this. This corresponds to xterm's private mode 40 + // bit. If the mode "?40" is set, then "?3" (DECCOLM) is supported. This + // doesn't exactly match VT100 semantics but modern terminals no longer + // blindly accept mode 3 since its so weird in modern practice. + if (self.modes.deccolm_supported == 0) return; + + // Enable it + self.modes.deccolm = @enumToInt(mode); + + // Resize -- we can set cols to 0 because deccolm will force it + try self.resize(alloc, 0, self.rows); + + // TODO: do not clear screen flag mode + self.eraseDisplay(.complete); + self.setCursorPos(1, 1); + + // TODO: left/right margins +} + +/// Allows or disallows deccolm. +pub fn setDeccolmSupported(self: *Terminal, v: bool) void { + self.modes.deccolm_supported = @boolToInt(v); +} + /// Resize the underlying terminal. -pub fn resize(self: *Terminal, alloc: Allocator, cols: usize, rows: usize) !void { +pub fn resize(self: *Terminal, alloc: Allocator, cols_req: usize, rows: usize) !void { // TODO: test, wrapping, etc. + // If we have deccolm supported then we are fixed at either 80 or 132 + // columns depending on if mode 3 is set or not. + // TODO: test + const cols: usize = if (self.modes.deccolm_supported == 1) + @as(usize, if (self.modes.deccolm == 1) 132 else 80) + else + cols_req; + // Resize our tabstops // TODO: use resize, but it doesn't set new tabstops if (self.cols != cols) { diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 9c2b8cd55..2b6a357f4 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -48,9 +48,9 @@ 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| { - // log.info("action: {}", .{action}); - // } + if (action_opt) |action| { + 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), diff --git a/test/cases/vttest/1_1.sh.ghostty.png b/test/cases/vttest/1_1.sh.ghostty.png index 128fcbfd096f0477ff7c81c777afc2dd50dcd58c..77bd0764e22afa5f882851aaac7f85b211be499c 100644 GIT binary patch delta 235 zcmcZ^crS26F*9S><`QNn*%(9jm}3`~Tx<fFG&5dc$yZ_7d{OZQ+hhTyAo;xvj160b6&PCn)PH~d@0!DziJc4# O3=E#GelF{r5}E)^T~*`& delta 232 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 NrGNX{D_#4){tE%&t7QNH delta 6743 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 Uip}`~0)aqA24*)(^c^1m9~;$~761SM