From 533416c5a96d3201dfd5e639a6a2d8fc9798a6fb Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Mon, 30 Sep 2024 12:38:57 +0200 Subject: [PATCH] terminal: Make CursorShape configurable (#18530) This builds on top of @Yevgen's #15840 and combines it with the settings names introduced in #17572. Closes #4731. Release Notes: - Added a setting for the terminal's default cursor shape. The setting is `{"terminal": {"cursor_shape": "block"}}``. Possible values: `block`, `bar`, `hollow`, `underline`. Demo: https://github.com/user-attachments/assets/96ed28c2-c222-436b-80cb-7cd63eeb47dd --- assets/settings/default.json | 12 +++++++ crates/project/src/terminals.rs | 1 + crates/terminal/src/terminal.rs | 13 +++++-- crates/terminal/src/terminal_settings.rs | 43 +++++++++++++++++++++++ crates/terminal_view/src/terminal_view.rs | 23 ++++++++++-- 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 2a11a85d4e..ba95c2cfcd 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -671,6 +671,18 @@ // 3. Always blink the cursor, ignoring the terminal mode // "blinking": "on", "blinking": "terminal_controlled", + // Default cursor shape for the terminal. + // 1. A block that surrounds the following character + // "block" + // 2. A vertical bar + // "bar" + // 3. An underline that runs along the following character + // "underscore" + // 4. A box drawn around the following character + // "hollow" + // + // Default: not set, defaults to "block" + "cursor_shape": null, // Set whether Alternate Scroll mode (code: ?1007) is active by default. // Alternate Scroll mode converts mouse scroll events into up / down key // presses when in the alternate screen (e.g. when running applications diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 136842d158..ababb3261b 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -216,6 +216,7 @@ impl Project { shell, env, Some(settings.blinking), + settings.cursor_shape.unwrap_or_default(), settings.alternate_scroll, settings.max_scroll_history_lines, window, diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 8f8982f02c..f9767b07d1 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -18,7 +18,9 @@ use alacritty_terminal::{ Config, RenderableCursor, TermMode, }, tty::{self}, - vte::ansi::{ClearMode, Handler, NamedPrivateMode, PrivateMode}, + vte::ansi::{ + ClearMode, CursorStyle as AlacCursorStyle, Handler, NamedPrivateMode, PrivateMode, + }, Term, }; use anyhow::{bail, Result}; @@ -40,7 +42,7 @@ use serde::{Deserialize, Serialize}; use settings::Settings; use smol::channel::{Receiver, Sender}; use task::{HideStrategy, Shell, TaskId}; -use terminal_settings::{AlternateScroll, TerminalBlink, TerminalSettings}; +use terminal_settings::{AlternateScroll, CursorShape, TerminalBlink, TerminalSettings}; use theme::{ActiveTheme, Theme}; use util::truncate_and_trailoff; @@ -314,6 +316,7 @@ impl TerminalBuilder { shell: Shell, mut env: HashMap, blink_settings: Option, + cursor_shape: CursorShape, alternate_scroll: AlternateScroll, max_scroll_history_lines: Option, window: AnyWindowHandle, @@ -353,6 +356,7 @@ impl TerminalBuilder { // Setup Alacritty's env, which modifies the current process's environment alacritty_terminal::tty::setup_env(); + let default_cursor_style = AlacCursorStyle::from(cursor_shape); let scrolling_history = if task.is_some() { // Tasks like `cargo build --all` may produce a lot of output, ergo allow maximum scrolling. // After the task finishes, we do not allow appending to that terminal, so small tasks output should not @@ -365,6 +369,7 @@ impl TerminalBuilder { }; let config = Config { scrolling_history, + default_cursor_style, ..Config::default() }; @@ -951,6 +956,10 @@ impl Terminal { &self.last_content } + pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape) { + self.term.lock().set_cursor_style(Some(cursor_shape.into())); + } + pub fn total_lines(&self) -> usize { let term = self.term.clone(); let terminal = term.lock_unfair(); diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 4051caf864..956cde19d9 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -1,3 +1,6 @@ +use alacritty_terminal::vte::ansi::{ + CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle, +}; use collections::HashMap; use gpui::{ px, AbsoluteLength, AppContext, FontFallbacks, FontFeatures, FontWeight, Pixels, SharedString, @@ -32,6 +35,7 @@ pub struct TerminalSettings { pub font_weight: Option, pub line_height: TerminalLineHeight, pub env: HashMap, + pub cursor_shape: Option, pub blinking: TerminalBlink, pub alternate_scroll: AlternateScroll, pub option_as_meta: bool, @@ -129,6 +133,11 @@ pub struct TerminalSettingsContent { /// /// Default: {} pub env: Option>, + /// Default cursor shape for the terminal. + /// Can be "bar", "block", "underscore", or "hollow". + /// + /// Default: None + pub cursor_shape: Option, /// Sets the cursor blinking behavior in the terminal. /// /// Default: terminal_controlled @@ -282,3 +291,37 @@ pub struct ToolbarContent { /// Default: true pub title: Option, } + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CursorShape { + /// Cursor is a block like `█`. + #[default] + Block, + /// Cursor is an underscore like `_`. + Underline, + /// Cursor is a vertical bar like `⎸`. + Bar, + /// Cursor is a hollow box like `▯`. + Hollow, +} + +impl From for AlacCursorShape { + fn from(value: CursorShape) -> Self { + match value { + CursorShape::Block => AlacCursorShape::Block, + CursorShape::Underline => AlacCursorShape::Underline, + CursorShape::Bar => AlacCursorShape::Beam, + CursorShape::Hollow => AlacCursorShape::HollowBlock, + } + } +} + +impl From for AlacCursorStyle { + fn from(value: CursorShape) -> Self { + AlacCursorStyle { + shape: value.into(), + blinking: false, + } + } +} diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index e0b92035d1..f7b38e3f5c 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -19,7 +19,7 @@ use terminal::{ index::Point, term::{search::RegexSearch, TermMode}, }, - terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory}, + terminal_settings::{CursorShape, TerminalBlink, TerminalSettings, WorkingDirectory}, Clear, Copy, Event, MaybeNavigationTarget, Paste, ScrollLineDown, ScrollLineUp, ScrollPageDown, ScrollPageUp, ScrollToBottom, ScrollToTop, ShowCharacterPalette, TaskStatus, Terminal, TerminalSize, @@ -102,6 +102,7 @@ pub struct TerminalView { //Currently using iTerm bell, show bell emoji in tab until input is received has_bell: bool, context_menu: Option<(View, gpui::Point, Subscription)>, + cursor_shape: CursorShape, blink_state: bool, blinking_on: bool, blinking_paused: bool, @@ -171,6 +172,9 @@ impl TerminalView { let focus_out = cx.on_focus_out(&focus_handle, |terminal_view, _event, cx| { terminal_view.focus_out(cx); }); + let cursor_shape = TerminalSettings::get_global(cx) + .cursor_shape + .unwrap_or_default(); Self { terminal, @@ -178,6 +182,7 @@ impl TerminalView { has_bell: false, focus_handle, context_menu: None, + cursor_shape, blink_state: true, blinking_on: false, blinking_paused: false, @@ -255,6 +260,16 @@ impl TerminalView { fn settings_changed(&mut self, cx: &mut ViewContext) { let settings = TerminalSettings::get_global(cx); self.show_title = settings.toolbar.title; + + let new_cursor_shape = settings.cursor_shape.unwrap_or_default(); + let old_cursor_shape = self.cursor_shape; + if old_cursor_shape != new_cursor_shape { + self.cursor_shape = new_cursor_shape; + self.terminal.update(cx, |term, _| { + term.set_cursor_shape(self.cursor_shape); + }); + } + cx.notify(); } @@ -903,7 +918,10 @@ impl TerminalView { } fn focus_in(&mut self, cx: &mut ViewContext) { - self.terminal.read(cx).focus_in(); + self.terminal.update(cx, |terminal, _| { + terminal.set_cursor_shape(self.cursor_shape); + terminal.focus_in(); + }); self.blink_cursors(self.blink_epoch, cx); cx.invalidate_character_coordinates(); cx.notify(); @@ -912,6 +930,7 @@ impl TerminalView { fn focus_out(&mut self, cx: &mut ViewContext) { self.terminal.update(cx, |terminal, _| { terminal.focus_out(); + terminal.set_cursor_shape(CursorShape::Hollow); }); cx.notify(); }