diff --git a/book/src/editor.md b/book/src/editor.md index 872fa8a62623..9cc2de28f9e5 100644 --- a/book/src/editor.md +++ b/book/src/editor.md @@ -184,7 +184,7 @@ The following statusline elements can be configured: ### `[editor.cursor-shape]` Section Defines the shape of cursor in each mode. -Valid values for these options are `block`, `bar`, `underline`, or `hidden`. +Valid values for these options are `block`, `terminal-block`, `bar`, `underline`, or `hidden`. > 💡 Due to limitations of the terminal environment, only the primary cursor can > change shape. diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index ea969f85005f..573875c89ae4 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -11,7 +11,7 @@ use helix_view::{ align_view, document::{DocumentOpenError, DocumentSavedEventResult}, editor::{ConfigEvent, EditorEvent}, - graphics::Rect, + graphics::{CursorKind, Modifier, Rect}, theme, tree::Layout, Align, Editor, @@ -288,6 +288,22 @@ impl Application { self.editor.cursor_cache.reset(); let pos = pos.map(|pos| (pos.col as u16, pos.row as u16)); + + // Hide the manually drawn cursor under the terminal block cursor + if kind == CursorKind::TerminalBlock { + if let Some((x, y)) = pos { + if let Some(cell) = surface.get_mut(x, y) { + if cell.modifier.contains(Modifier::REVERSED) { + // Themes with reversed cursor colors: remove the reverse modifier + cell.modifier.remove(Modifier::REVERSED); + } else { + // Themes with static cursor colors: swap foreground and background + std::mem::swap(&mut cell.fg, &mut cell.bg); + } + } + } + } + self.terminal.draw(pos, kind).unwrap(); } @@ -1271,11 +1287,6 @@ impl Application { } fn restore_term(&mut self) -> std::io::Result<()> { - use helix_view::graphics::CursorKind; - self.terminal - .backend_mut() - .show_cursor(CursorKind::Block) - .ok(); self.terminal.restore() } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 3bd66f9413ff..dad733757c89 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -541,7 +541,8 @@ impl EditorView { let primary_idx = selection.primary_index(); let cursorkind = cursor_shape_config.from_mode(mode); - let cursor_is_block = cursorkind == CursorKind::Block; + let cursor_is_block = + cursorkind == CursorKind::Block || cursorkind == CursorKind::TerminalBlock; let selection_scope = theme .find_highlight_exact("ui.selection") @@ -1735,18 +1736,7 @@ impl Component for EditorView { } fn cursor(&self, _area: Rect, editor: &Editor) -> (Option, CursorKind) { - match editor.cursor() { - // all block cursors are drawn manually - (pos, CursorKind::Block) => { - if self.terminal_focused { - (pos, CursorKind::Hidden) - } else { - // use terminal cursor when terminal loses focus - (pos, CursorKind::Underline) - } - } - cursor => cursor, - } + editor.cursor() } } diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index a5e8c7354808..2137c35db43a 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -599,6 +599,44 @@ impl Prompt { |_| prompt_color, ); } + + // Draw the block cursor + let cursor_kind = cx.editor.config().cursor_shape.from_mode(Mode::Insert); + let cursor_is_block = + cursor_kind == CursorKind::Block || cursor_kind == CursorKind::TerminalBlock; + if cursor_is_block { + let pos = self.cursor_position(area); + if let Some(cell) = surface.get_mut(pos.col as u16, pos.row as u16) { + let cursor_style = cx.editor.theme.get("ui.cursor.primary"); + cell.set_style(cursor_style); + } + } + } + + fn cursor_position(&self, area: Rect) -> Position { + let area = area + .clip_left(self.prompt.len() as u16) + .clip_right(if self.prompt.is_empty() { 2 } else { 0 }); + + let mut col = area.left() as usize + self.line[self.anchor..self.cursor].width(); + + // ensure the cursor does not go beyond elipses + if self.truncate_end + && self.line[self.anchor..self.cursor].width() >= self.line_area.width as usize + { + col -= 1; + } + + if self.truncate_start && self.cursor == self.anchor { + col += self.line[self.cursor..] + .graphemes(true) + .next() + .map_or(0, |g| g.width()); + } + + let row = area.y as usize + area.height as usize - 1; + + Position::new(row, col) } } @@ -770,30 +808,8 @@ impl Component for Prompt { } fn cursor(&self, area: Rect, editor: &Editor) -> (Option, CursorKind) { - let area = area - .clip_left(self.prompt.len() as u16) - .clip_right(if self.prompt.is_empty() { 2 } else { 0 }); - - let mut col = area.left() as usize + self.line[self.anchor..self.cursor].width(); - - // ensure the cursor does not go beyond elipses - if self.truncate_end - && self.line[self.anchor..self.cursor].width() >= self.line_area.width as usize - { - col -= 1; - } - - if self.truncate_start && self.cursor == self.anchor { - col += self.line[self.cursor..] - .graphemes(true) - .next() - .map_or(0, |g| g.width()); - } - - let line = area.height as usize - 1; - ( - Some(Position::new(area.y as usize + line, col)), + Some(self.cursor_position(area)), editor.config().cursor_shape.from_mode(Mode::Insert), ) } diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs index 78626b9121be..920ce742a8b0 100644 --- a/helix-tui/src/backend/crossterm.rs +++ b/helix-tui/src/backend/crossterm.rs @@ -296,7 +296,8 @@ where fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> { let shape = match kind { - CursorKind::Block => SetCursorStyle::SteadyBlock, + CursorKind::Block => unreachable!(), + CursorKind::TerminalBlock => SetCursorStyle::SteadyBlock, CursorKind::Bar => SetCursorStyle::SteadyBar, CursorKind::Underline => SetCursorStyle::SteadyUnderScore, CursorKind::Hidden => unreachable!(), diff --git a/helix-tui/src/backend/termina.rs b/helix-tui/src/backend/termina.rs index 7c39cc96c008..c040253635c4 100644 --- a/helix-tui/src/backend/termina.rs +++ b/helix-tui/src/backend/termina.rs @@ -560,7 +560,8 @@ impl Backend for TerminaBackend { fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> { let style = match kind { - CursorKind::Block => CursorStyle::SteadyBlock, + CursorKind::Block => unreachable!(), + CursorKind::TerminalBlock => CursorStyle::SteadyBlock, CursorKind::Bar => CursorStyle::SteadyBar, CursorKind::Underline => CursorStyle::SteadyUnderline, CursorKind::Hidden => unreachable!(), diff --git a/helix-tui/src/terminal.rs b/helix-tui/src/terminal.rs index 20287c18979e..586cf89a697b 100644 --- a/helix-tui/src/terminal.rs +++ b/helix-tui/src/terminal.rs @@ -112,7 +112,7 @@ where Buffer::empty(options.viewport.area), ], current: 0, - cursor_kind: CursorKind::Block, + cursor_kind: CursorKind::default(), viewport: options.viewport, force_clear: false, }) @@ -210,7 +210,7 @@ where } match cursor_kind { - CursorKind::Hidden => self.hide_cursor()?, + CursorKind::Hidden | CursorKind::Block => self.hide_cursor()?, kind => self.show_cursor(kind)?, } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index adced1884f6a..84a0abb7ff35 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -856,7 +856,7 @@ impl std::ops::Deref for CursorShapeConfig { impl Default for CursorShapeConfig { fn default() -> Self { - Self([CursorKind::Block; 3]) + Self([CursorKind::default(); 3]) } } diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index e4fa3e38a5bd..79bd0ca81d56 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -48,13 +48,15 @@ const fn byte_from_hex(mut h: [u8; 2]) -> Option { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] -#[serde(rename_all = "lowercase")] +#[serde(rename_all = "kebab-case")] /// UNSTABLE #[derive(Default)] pub enum CursorKind { /// █ #[default] Block, + /// █ + TerminalBlock, /// | Bar, /// _