diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 8de914a3e..a2cf510b1 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -193,6 +193,10 @@ pub const Options = struct { cols: size.CellCountInt, rows: size.CellCountInt, max_scrollback: usize = 10_000, + + /// The default mode state. When the terminal gets a reset, it + /// will revert back to this state. + default_modes: modes.ModePacked = .{}, }; /// Initialize a new terminal. @@ -216,6 +220,10 @@ pub fn init( .right = cols - 1, }, .pwd = std.ArrayList(u8).init(alloc), + .modes = .{ + .values = opts.default_modes, + .default = opts.default_modes, + }, }; } @@ -2680,7 +2688,7 @@ fn resetCommonState(self: *Terminal) void { self.screen.endHyperlink(); self.screen.charset = .{}; - self.modes = .{}; + self.modes.reset(); self.flags = .{}; self.tabstops.reset(TABSTOP_INTERVAL); self.screen.kitty_keyboard = .{}; @@ -10555,6 +10563,18 @@ test "Terminal: fullReset clears alt screen kitty keyboard state" { try testing.expectEqual(0, t.secondary_screen.kitty_keyboard.current().int()); } +test "Terminal: fullReset default modes" { + var t = try init(testing.allocator, .{ + .cols = 10, + .rows = 10, + .default_modes = .{ .grapheme_cluster = true }, + }); + defer t.deinit(testing.allocator); + try testing.expect(t.modes.get(.grapheme_cluster)); + t.fullReset(); + try testing.expect(t.modes.get(.grapheme_cluster)); +} + // https://github.com/mitchellh/ghostty/issues/272 // This is also tested in depth in screen resize tests but I want to keep // this test around to ensure we don't regress at multiple layers. diff --git a/src/terminal/main.zig b/src/terminal/main.zig index d295ea1ba..3fc7d2600 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -47,6 +47,7 @@ pub const CursorStyle = Screen.CursorStyle; pub const CursorStyleReq = ansi.CursorStyle; pub const DeviceAttributeReq = ansi.DeviceAttributeReq; pub const Mode = modes.Mode; +pub const ModePacked = modes.ModePacked; pub const ModifyKeyFormat = ansi.ModifyKeyFormat; pub const ProtectedMode = ansi.ProtectedMode; pub const StatusLineType = ansi.StatusLineType; diff --git a/src/terminal/modes.zig b/src/terminal/modes.zig index c4dbb1cd6..89d352e4a 100644 --- a/src/terminal/modes.zig +++ b/src/terminal/modes.zig @@ -21,6 +21,17 @@ pub const ModeState = struct { /// a real-world issue but we need to be aware of a DoS vector. saved: ModePacked = .{}, + /// The default values for the modes. This is used to reset + /// the modes to their default values during reset. + default: ModePacked = .{}, + + /// Reset the modes to their default values. This also clears the + /// saved state. + pub fn reset(self: *ModeState) void { + self.values = self.default; + self.saved = .{}; + } + /// Set a mode to a value. pub fn set(self: *ModeState, mode: Mode, value: bool) void { switch (mode) { diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index e7b391419..1ebe84541 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -127,6 +127,23 @@ pub const DerivedConfig = struct { /// This will also start the child process if the termio is configured /// to run a child process. pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { + // The default terminal modes based on our config. + const default_modes: terminal.ModePacked = modes: { + var modes: terminal.ModePacked = .{}; + + // Setup our initial grapheme cluster support if enabled. We use a + // switch to ensure we get a compiler error if more cases are added. + switch (opts.full_config.@"grapheme-width-method") { + .unicode => modes.grapheme_cluster = true, + .legacy => {}, + } + + // Set default cursor blink settings + modes.cursor_blinking = opts.config.cursor_blink orelse true; + + break :modes modes; + }; + // Create our terminal var term = try terminal.Terminal.init(alloc, opts: { const grid_size = opts.size.grid(); @@ -134,19 +151,13 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { .cols = grid_size.columns, .rows = grid_size.rows, .max_scrollback = opts.full_config.@"scrollback-limit", + .default_modes = default_modes, }; }); errdefer term.deinit(alloc); term.default_palette = opts.config.palette; term.color_palette.colors = opts.config.palette; - // Setup our initial grapheme cluster support if enabled. We use a - // switch to ensure we get a compiler error if more cases are added. - switch (opts.full_config.@"grapheme-width-method") { - .unicode => term.modes.set(.grapheme_cluster, true), - .legacy => {}, - } - // Set the image size limits try term.screen.kitty_images.setLimit( alloc, @@ -159,12 +170,6 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { opts.config.image_storage_limit, ); - // Set default cursor blink settings - term.modes.set( - .cursor_blinking, - opts.config.cursor_blink orelse true, - ); - // Set our default cursor style term.screen.cursor.cursor_style = opts.config.cursor_style;