From 7961355ba1c0cd521372496c507a31a51b41ddf2 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Wed, 24 Nov 2021 12:17:41 +0530 Subject: [PATCH] Change cursor shape on mode change Fixes #323. Due to terminal limitations we can only change the shape of the primary cursor. --- book/src/configuration.md | 42 +++++++++++++++++++++++++++++++++++-- helix-term/src/ui/editor.rs | 12 ++++++++--- helix-view/src/editor.rs | 42 +++++++++++++++++++++++++++++++------ helix-view/src/graphics.rs | 32 ++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 11 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 2ed48d51f..a40a89591 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -5,9 +5,26 @@ To override global configuration parameters, create a `config.toml` file located * Linux and Mac: `~/.config/helix/config.toml` * Windows: `%AppData%\helix\config.toml` +Example config: + +```toml +theme = "onedark" + +[editor] +line-number = "relative" +mouse = false + +[editor.cursor-shape] +normal = "underline" +insert = "block" + +[editor.file-picker] +hidden = false +``` + ## Editor -`[editor]` section of the config. +### `[editor]` Section | Key | Description | Default | |--|--|---------| @@ -24,7 +41,28 @@ To override global configuration parameters, create a `config.toml` file located | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` | | `auto-info` | Whether to display infoboxes | `true` | -`[editor.filepicker]` section of the config. Sets options for file picker and global search. All but the last key listed in the default file-picker configuration below are IgnoreOptions: whether hidden files and files listed within ignore files are ignored by (not visible in) the helix file picker and global search. There is also one other key, `max-depth` available, which is not defined by default. +### `[editor.cursor-shape]` Section + +Defines the shape of cursor in each mode. Note that due to limitations +of the terminal environment, only the primary cursor can change shape. + +| Key | Description | Default | +| --- | ----------- | -------- | +| `normal` | Cursor shape in [normal mode][normal mode] | `block` | +| `insert` | Cursor shape in [insert mode][insert mode] | `bar` | +| `select` | Cursor shape in [select mode][select mode] | `underline` | + +[normal mode]: ./keymap.md#normal-mode +[insert mode]: ./keymap.md#insert-mode +[select mode]: ./keymap.md#select--extend-mode + +### `[editor.filepicker]` Section + +Sets options for file picker and global search. All but the last key listed in +the default file-picker configuration below are IgnoreOptions: whether hidden +files and files listed within ignore files are ignored by (not visible in) the +helix file picker and global search. There is also one other key, `max-depth` +available, which is not defined by default. | Key | Description | Default | |--|--|---------| diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 27d33d225..8ad54dbdd 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -250,7 +250,9 @@ impl EditorView { // Special-case: cursor at end of the rope. if range.head == range.anchor && range.head == text.len_chars() { - spans.push((cursor_scope, range.head..range.head + 1)); + if i != primary_idx { + spans.push((cursor_scope, range.head..range.head + 1)); + } continue; } @@ -259,11 +261,15 @@ impl EditorView { // Standard case. let cursor_start = prev_grapheme_boundary(text, range.head); spans.push((selection_scope, range.anchor..cursor_start)); - spans.push((cursor_scope, cursor_start..range.head)); + if i != primary_idx { + spans.push((cursor_scope, cursor_start..range.head)); + } } else { // Reverse case. let cursor_end = next_grapheme_boundary(text, range.head); - spans.push((cursor_scope, range.head..cursor_end)); + if i != primary_idx { + spans.push((cursor_scope, range.head..cursor_end)); + } spans.push((selection_scope, cursor_end..range.anchor)); } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 1ce33760e..9c77f2705 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,6 +1,6 @@ use crate::{ clipboard::{get_clipboard_provider, ClipboardProvider}, - document::SCRATCH_BUFFER_NAME, + document::{Mode, SCRATCH_BUFFER_NAME}, graphics::{CursorKind, Rect}, theme::{self, Theme}, tree::{self, Tree}, @@ -9,7 +9,7 @@ use crate::{ use futures_util::future; use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, io::stdin, path::{Path, PathBuf}, pin::Pin, @@ -22,7 +22,7 @@ use anyhow::Error; pub use helix_core::diagnostic::Severity; pub use helix_core::register::Registers; -use helix_core::syntax; +use helix_core::{hashmap, syntax}; use helix_core::{Position, Selection}; use serde::Deserialize; @@ -103,6 +103,30 @@ pub struct Config { /// Whether to display infoboxes. Defaults to true. pub auto_info: bool, pub file_picker: FilePickerConfig, + /// Shape for cursor in each mode + pub cursor_shape: CursorShapeConfig, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(transparent)] +pub struct CursorShapeConfig(HashMap); + +impl std::ops::Deref for CursorShapeConfig { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Default for CursorShapeConfig { + fn default() -> Self { + Self(hashmap!( + Mode::Insert => CursorKind::Bar, + Mode::Normal => CursorKind::Block, + Mode::Select => CursorKind::Underline, + )) + } } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -110,7 +134,6 @@ pub struct Config { pub enum LineNumber { /// Show absolute line number Absolute, - /// Show relative line number to the primary cursor Relative, } @@ -135,6 +158,7 @@ impl Default for Config { completion_trigger_len: 2, auto_info: true, file_picker: FilePickerConfig::default(), + cursor_shape: CursorShapeConfig::default(), } } } @@ -594,9 +618,15 @@ impl Editor { let inner = view.inner_area(); pos.col += inner.x as usize; pos.row += inner.y as usize; - (Some(pos), CursorKind::Hidden) + let cursorkind = self + .config + .cursor_shape + .get(&doc.mode()) + .copied() + .unwrap_or_default(); + (Some(pos), cursorkind) } else { - (None, CursorKind::Hidden) + (None, CursorKind::default()) } } diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index 0bfca04aa..c9dd21e30 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -1,4 +1,6 @@ +use anyhow::{anyhow, Error}; use bitflags::bitflags; +use serde::de::{self, Deserialize, Deserializer}; use std::{ cmp::{max, min}, str::FromStr, @@ -17,6 +19,36 @@ pub enum CursorKind { Hidden, } +impl Default for CursorKind { + fn default() -> Self { + Self::Block + } +} + +impl FromStr for CursorKind { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "bar" => Ok(Self::Bar), + "block" => Ok(Self::Block), + "underline" => Ok(Self::Underline), + _ => Err(anyhow!("Invalid cursor '{}'", s)), + } + } +} + +// toml deserializer doesn't seem to recognize string as enum +impl<'de> Deserialize<'de> for CursorKind { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse().map_err(de::Error::custom) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Margin { pub vertical: u16,