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 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%0A1p2Q7tBUvLYEP|1Nx?^`t z3<_QCTW}1?o%=3q)!)17dQ!%ppp$TePYo-n-fdsutk0{0rNv|@%-Oy1=z|hEgoxYD z@p?QnRDLUHY^$@s`=}V31t>+uOsZ28&G*Ol&Mr;K@eBvI_GbG2C3imOmCTE?FhV z7NuvwEp0Hywf7b$d9-f8GYE6`!Y|n)F;&CCF>6D{1xCROR9}Xc9IwI5jnr2uTr{Y@cIF|IZ2-uFwqdze}RX?Xk*G2Zm7CCaWX@o zB=E4Md-tDL;du9mK0G1< zDHGW4A-9wD&N#s0500*1pr)Hr4ruoN(1rM~2dq+SbwS`WXyI^%z}z-O37~)_dC%CzI26u~t)^=IV!iyOqT4xocd_@BhJybD3hHckOL&9L!tO1ZAid@5e47xjo1KnR8&A6?R4{S2Q5Dm&-=P|K3*?a>k28%>3XVkmPi*WdF|I YLnmT$G>Ol2AmHb=_MPjmHEkdL7Xjg`WdHyG delta 6743 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 a$qXhZLw}$kI_on8{OIc%Un$bDfAD`B)tMFm