on master help sign in
Change cursor shape on mode change

Fixes #323. Due to terminal limitations we can only change the shape of the primary cursor.

commit7961355ba1c0cd521372496c507a31a51b41ddf2 authorGokul Soumya <gokulps15@gmail.com> dateWed 24 Nov 2021, 12:17 (5 years ago) parent21143e8
4 files changed+117−11
book/src/configuration.md+40−2 helix-term/src/ui/editor.rs+9−3 helix-view/src/editor.rs+36−6 helix-view/src/graphics.rs+32
book/src/configuration.md+40−2
@@ -5,9 +5,26 @@ To override global configuration parameters, create a `config.toml` file located
55* Linux and Mac: `~/.config/helix/config.toml`
66* Windows: `%AppData%\helix\config.toml`
77
8+Example config:
9+
10+```toml
11+theme = "onedark"
12+
13+[editor]
14+line-number = "relative"
15+mouse = false
16+
17+[editor.cursor-shape]
18+normal = "underline"
19+insert = "block"
20+
21+[editor.file-picker]
22+hidden = false
23+```
24+
825## Editor
926
10−`[editor]` section of the config.
27+### `[editor]` Section
1128
1229| Key | Description | Default |
1330|--|--|---------|
@@ -24,7 +41,28 @@ To override global configuration parameters, create a `config.toml` file located
2441| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
2542| `auto-info` | Whether to display infoboxes | `true` |
2643
27−`[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.
44+### `[editor.cursor-shape]` Section
45+
46+Defines the shape of cursor in each mode. Note that due to limitations
47+of the terminal environment, only the primary cursor can change shape.
48+
49+| Key | Description | Default |
50+| --- | ----------- | -------- |
51+| `normal` | Cursor shape in [normal mode][normal mode] | `block` |
52+| `insert` | Cursor shape in [insert mode][insert mode] | `bar` |
53+| `select` | Cursor shape in [select mode][select mode] | `underline` |
54+
55+[normal mode]: ./keymap.md#normal-mode
56+[insert mode]: ./keymap.md#insert-mode
57+[select mode]: ./keymap.md#select--extend-mode
58+
59+### `[editor.filepicker]` Section
60+
61+Sets options for file picker and global search. All but the last key listed in
62+the default file-picker configuration below are IgnoreOptions: whether hidden
63+files and files listed within ignore files are ignored by (not visible in) the
64+helix file picker and global search. There is also one other key, `max-depth`
65+available, which is not defined by default.
2866
2967| Key | Description | Default |
3068|--|--|---------|
helix-term/src/ui/editor.rs+9−3
@@ -250,7 +250,9 @@ impl EditorView {
250250
251251 // Special-case: cursor at end of the rope.
252252 if range.head == range.anchor && range.head == text.len_chars() {
253− spans.push((cursor_scope, range.head..range.head + 1));
253+ if i != primary_idx {
254+ spans.push((cursor_scope, range.head..range.head + 1));
255+ }
254256 continue;
255257 }
256258
@@ -259,11 +261,15 @@ impl EditorView {
259261 // Standard case.
260262 let cursor_start = prev_grapheme_boundary(text, range.head);
261263 spans.push((selection_scope, range.anchor..cursor_start));
262− spans.push((cursor_scope, cursor_start..range.head));
264+ if i != primary_idx {
265+ spans.push((cursor_scope, cursor_start..range.head));
266+ }
263267 } else {
264268 // Reverse case.
265269 let cursor_end = next_grapheme_boundary(text, range.head);
266− spans.push((cursor_scope, range.head..cursor_end));
270+ if i != primary_idx {
271+ spans.push((cursor_scope, range.head..cursor_end));
272+ }
267273 spans.push((selection_scope, cursor_end..range.anchor));
268274 }
269275 }
helix-view/src/editor.rs+36−6
@@ -1,6 +1,6 @@
11use crate::{
22 clipboard::{get_clipboard_provider, ClipboardProvider},
3− document::SCRATCH_BUFFER_NAME,
3+ document::{Mode, SCRATCH_BUFFER_NAME},
44 graphics::{CursorKind, Rect},
55 theme::{self, Theme},
66 tree::{self, Tree},
@@ -9,7 +9,7 @@ use crate::{
99
1010use futures_util::future;
1111use std::{
12− collections::BTreeMap,
12+ collections::{BTreeMap, HashMap},
1313 io::stdin,
1414 path::{Path, PathBuf},
1515 pin::Pin,
@@ -22,7 +22,7 @@ use anyhow::Error;
2222
2323pub use helix_core::diagnostic::Severity;
2424pub use helix_core::register::Registers;
25−use helix_core::syntax;
25+use helix_core::{hashmap, syntax};
2626use helix_core::{Position, Selection};
2727
2828use serde::Deserialize;
@@ -103,6 +103,30 @@ pub struct Config {
103103 /// Whether to display infoboxes. Defaults to true.
104104 pub auto_info: bool,
105105 pub file_picker: FilePickerConfig,
106+ /// Shape for cursor in each mode
107+ pub cursor_shape: CursorShapeConfig,
108+}
109+
110+#[derive(Debug, Clone, PartialEq, Deserialize)]
111+#[serde(transparent)]
112+pub struct CursorShapeConfig(HashMap<Mode, CursorKind>);
113+
114+impl std::ops::Deref for CursorShapeConfig {
115+ type Target = HashMap<Mode, CursorKind>;
116+
117+ fn deref(&self) -> &Self::Target {
118+ &self.0
119+ }
120+}
121+
122+impl Default for CursorShapeConfig {
123+ fn default() -> Self {
124+ Self(hashmap!(
125+ Mode::Insert => CursorKind::Bar,
126+ Mode::Normal => CursorKind::Block,
127+ Mode::Select => CursorKind::Underline,
128+ ))
129+ }
106130}
107131
108132#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
@@ -110,7 +134,6 @@ pub struct Config {
110134pub enum LineNumber {
111135 /// Show absolute line number
112136 Absolute,
113−
114137 /// Show relative line number to the primary cursor
115138 Relative,
116139}
@@ -135,6 +158,7 @@ impl Default for Config {
135158 completion_trigger_len: 2,
136159 auto_info: true,
137160 file_picker: FilePickerConfig::default(),
161+ cursor_shape: CursorShapeConfig::default(),
138162 }
139163 }
140164}
@@ -594,9 +618,15 @@ impl Editor {
594618 let inner = view.inner_area();
595619 pos.col += inner.x as usize;
596620 pos.row += inner.y as usize;
597− (Some(pos), CursorKind::Hidden)
621+ let cursorkind = self
622+ .config
623+ .cursor_shape
624+ .get(&doc.mode())
625+ .copied()
626+ .unwrap_or_default();
627+ (Some(pos), cursorkind)
598628 } else {
599− (None, CursorKind::Hidden)
629+ (None, CursorKind::default())
600630 }
601631 }
602632
helix-view/src/graphics.rs+32
@@ -1,4 +1,6 @@
1+use anyhow::{anyhow, Error};
12use bitflags::bitflags;
3+use serde::de::{self, Deserialize, Deserializer};
24use std::{
35 cmp::{max, min},
46 str::FromStr,
@@ -17,6 +19,36 @@ pub enum CursorKind {
1719 Hidden,
1820}
1921
22+impl Default for CursorKind {
23+ fn default() -> Self {
24+ Self::Block
25+ }
26+}
27+
28+impl FromStr for CursorKind {
29+ type Err = Error;
30+
31+ fn from_str(s: &str) -> Result<Self, Self::Err> {
32+ match s {
33+ "bar" => Ok(Self::Bar),
34+ "block" => Ok(Self::Block),
35+ "underline" => Ok(Self::Underline),
36+ _ => Err(anyhow!("Invalid cursor '{}'", s)),
37+ }
38+ }
39+}
40+
41+// toml deserializer doesn't seem to recognize string as enum
42+impl<'de> Deserialize<'de> for CursorKind {
43+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
44+ where
45+ D: Deserializer<'de>,
46+ {
47+ let s = String::deserialize(deserializer)?;
48+ s.parse().map_err(de::Error::custom)
49+ }
50+}
51+
2052#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2153pub struct Margin {
2254 pub vertical: u16,