diff --git a/src/Window.zig b/src/Window.zig index ff9ae92e1..1fecffbff 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -59,9 +59,8 @@ terminal: terminal.Terminal, /// The stream parser. terminal_stream: terminal.Stream(*Window), -/// Timer that blinks the cursor. -cursor_timer: libuv.Timer, -cursor_style: terminal.CursorStyle, +/// Cursor state. +terminal_cursor: Cursor, /// Render at least 60fps. render_timer: RenderTimer, @@ -76,10 +75,6 @@ write_req_pool: SegmentedPool(libuv.WriteReq.T, WRITE_REQ_PREALLOC) = .{}, /// The pool of available buffers for writing to the pty. write_buf_pool: SegmentedPool([64]u8, WRITE_REQ_PREALLOC) = .{}, -/// Set this to true whenver an event occurs that we may want to wake up -/// the event loop. Only set this from the main thread. -wakeup: bool = false, - /// The app configuration config: *const Config, @@ -98,6 +93,42 @@ bracketed_paste: bool = false, /// like such as "control-v" will write a "v" even if they're intercepted. ignore_char: bool = false, +/// Information related to the current cursor for the window. +// +// QUESTION(mitchellh): should this be attached to the Screen instead? +// I'm not sure if the cursor settings stick to the screen, i.e. if you +// change to an alternate screen if those are preserved. Need to check this. +const Cursor = struct { + /// Timer for cursor blinking. + timer: libuv.Timer, + + /// Current cursor style. This can be set by escape sequences. To get + /// the default style, the config has to be referenced. + style: terminal.CursorStyle = .default, + + /// Whether the cursor is visible at all. This should not be used for + /// "blink" settings, see "blink" for that. This is used to turn the + /// cursor ON or OFF. + visible: bool = true, + + /// Whether the cursor is currently blinking. If it is blinking, then + /// the cursor will not be rendered. + blink: bool = false, + + /// Start (or restart) the timer. This is idempotent. + pub fn startTimer(self: Cursor) !void { + try self.timer.start( + cursorTimerCallback, + 0, + self.timer.getRepeat(), + ); + } + + pub fn stopTimer(self: Cursor) !void { + try self.timer.stop(); + } +}; + /// Create a new window. This allocates and returns a pointer because we /// need a stable pointer for user data callbacks. Therefore, a stack-only /// initialization is not currently possible. @@ -231,8 +262,10 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo .command = cmd, .terminal = term, .terminal_stream = .{ .handler = self }, - .cursor_timer = timer, - .cursor_style = .blinking_block, + .terminal_cursor = .{ + .timer = timer, + .style = .blinking_block, + }, .render_timer = try RenderTimer.init(loop, self, 16, 64), .pty_stream = stream, .config = config, @@ -265,7 +298,7 @@ pub fn destroy(self: *Window) void { self.grid.deinit(); self.window.destroy(); - self.cursor_timer.close((struct { + self.terminal_cursor.timer.close((struct { fn callback(t: *libuv.Timer) void { const alloc = t.loop().getData(Allocator).?.*; t.deinit(alloc); @@ -318,25 +351,6 @@ fn queueWrite(self: *Window, data: []const u8) !void { } } -/// Updates te style of the cursor. -fn updateCursorStyle(self: *Window, style: Grid.CursorStyle, blink: bool) !void { - self.grid.cursor_style = style; - self.grid.cursor_visible = !blink; - - if (blink) { - try self.cursor_timer.start( - cursorTimerCallback, - 0, - self.cursor_timer.getRepeat(), - ); - } else { - try self.cursor_timer.stop(); - } - - // Always schedule a render when we change cursors - try self.render_timer.schedule(); -} - fn sizeCallback(window: glfw.Window, width: i32, height: i32) void { const tracy = trace(@src()); defer tracy.end(); @@ -483,21 +497,17 @@ fn focusCallback(window: glfw.Window, focused: bool) void { if (win.focused == focused) return; // We have to schedule a render because no matter what we're changing - // the cursor. + // the cursor. If we're focused its reappearing, if we're not then + // its changing to hollow and not blinking. win.render_timer.schedule() catch unreachable; // Set our focused state on the window. win.focused = focused; - if (focused) { - win.wakeup = true; - win.updateCursorStyle( - Grid.CursorStyle.fromTerminal(win.cursor_style) orelse .box, - win.cursor_style.blinking(), - ) catch unreachable; - } else { - win.updateCursorStyle(.box_hollow, false) catch unreachable; - } + if (focused) + win.terminal_cursor.startTimer() catch unreachable + else + win.terminal_cursor.stopTimer() catch unreachable; } fn refreshCallback(window: glfw.Window) void { @@ -536,7 +546,13 @@ fn cursorTimerCallback(t: *libuv.Timer) void { defer tracy.end(); const win = t.getData(Window) orelse return; - win.grid.cursor_visible = !win.grid.cursor_visible; + + // If the cursor is currently invisible, then we do nothing. Ideally + // in this state the timer would be cancelled but no big deal. + if (!win.terminal_cursor.visible) return; + + // Swap blink state and schedule a render + win.terminal_cursor.blink = !win.terminal_cursor.blink; win.render_timer.schedule() catch unreachable; } @@ -570,11 +586,11 @@ fn ttyRead(t: *libuv.Tty, n: isize, buf: []const u8) void { return; }; - // Whenever a character is typed, we ensure the cursor is visible - // and we restart the cursor timer. - win.grid.cursor_visible = true; - if (win.cursor_timer.isActive() catch false) { - _ = win.cursor_timer.again() catch null; + // Whenever a character is typed, we ensure the cursor is in the + // non-blink state so it is rendered if visible. + win.terminal_cursor.blink = false; + if (win.terminal_cursor.timer.isActive() catch false) { + _ = win.terminal_cursor.timer.again() catch null; } // Schedule a render @@ -607,6 +623,10 @@ fn renderTimerCallback(t: *libuv.Timer) void { const win = t.getData(Window).?; + // Setup our cursor settings + win.grid.cursor_visible = win.terminal_cursor.visible and !win.terminal_cursor.blink; + win.grid.cursor_style = Grid.CursorStyle.fromTerminal(win.terminal_cursor.style) orelse .box; + // Calculate foreground and background colors const bg = win.grid.background; const fg = win.grid.foreground; @@ -788,6 +808,10 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { self.terminal.modes.autowrap = @boolToInt(enabled); }, + .cursor_visible => { + self.terminal_cursor.visible = enabled; + }, + .alt_screen_save_cursor_clear_enter => { const opts: terminal.Terminal.AlternateScreenOptions = .{ .cursor_save = true, @@ -887,19 +911,7 @@ pub fn setCursorStyle( self: *Window, style: terminal.CursorStyle, ) !void { - // Get the style that we use in the renderer - const grid_style = Grid.CursorStyle.fromTerminal(style) orelse { - log.warn("unimplemented cursor style: {}", .{style}); - return; - }; - - // Set our style - self.cursor_style = style; - - // If we're currently focused, we update our style, since our unfocused - // cursor is manually managed. If we're not focused, we ignore it because - // it'll be updated the next time the window comes into focus. - if (self.focused) try self.updateCursorStyle(grid_style, style.blinking()); + self.terminal_cursor.style = style; } pub fn decaln(self: *Window) !void { diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index 3186f322d..817f3b02e 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -58,6 +58,9 @@ pub const Mode = enum(u16) { /// Enable or disable automatic line wrapping. autowrap = 7, + /// Set whether the cursor is visible or not. + cursor_visible = 25, + /// Enables or disables mode ?3. If disabled, the terminal will resize /// to the size of the window. If enabled, this will take effect when /// mode ?3 is set or unset.