on master help sign in
Change cursor shape on mode change
7961355Gokul Soumya
book/src/configuration.md+40−2
@@ -5,9 +5,26 @@ To override global configuration parameters, create a `config.toml` file located
5* Linux and Mac: `~/.config/helix/config.toml`
5* Linux and Mac: `~/.config/helix/config.toml`
6* Windows: `%AppData%\helix\config.toml`
6* Windows: `%AppData%\helix\config.toml`
7
7
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+
8## Editor
25## Editor
9
26
10−`[editor]` section of the config.
27+### `[editor]` Section
11
28
12| Key | Description | Default |
29| Key | Description | Default |
13|--|--|---------|
30|--|--|---------|
@@ -24,7 +41,28 @@ To override global configuration parameters, create a `config.toml` file located
24| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
41| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
25| `auto-info` | Whether to display infoboxes | `true` |
42| `auto-info` | Whether to display infoboxes | `true` |
26
43
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.
28
66
29| Key | Description | Default |
67| Key | Description | Default |
30|--|--|---------|
68|--|--|---------|
helix-term/src/ui/editor.rs+9−3
@@ -250,7 +250,9 @@ impl EditorView {
250
250
251 // Special-case: cursor at end of the rope.
251 // Special-case: cursor at end of the rope.
252 if range.head == range.anchor && range.head == text.len_chars() {
252 if range.head == range.anchor && range.head == text.len_chars() {
253+ if i != primary_idx {
253− spans.push((cursor_scope, range.head..range.head + 1));
254+ spans.push((cursor_scope, range.head..range.head + 1));
255+ }
254 continue;
256 continue;
255 }
257 }
256
258
@@ -259,11 +261,15 @@ impl EditorView {
259 // Standard case.
261 // Standard case.
260 let cursor_start = prev_grapheme_boundary(text, range.head);
262 let cursor_start = prev_grapheme_boundary(text, range.head);
261 spans.push((selection_scope, range.anchor..cursor_start));
263 spans.push((selection_scope, range.anchor..cursor_start));
264+ if i != primary_idx {
262− spans.push((cursor_scope, cursor_start..range.head));
265+ spans.push((cursor_scope, cursor_start..range.head));
266+ }
263 } else {
267 } else {
264 // Reverse case.
268 // Reverse case.
265 let cursor_end = next_grapheme_boundary(text, range.head);
269 let cursor_end = next_grapheme_boundary(text, range.head);
270+ if i != primary_idx {
266− spans.push((cursor_scope, range.head..cursor_end));
271+ spans.push((cursor_scope, range.head..cursor_end));
272+ }
267 spans.push((selection_scope, cursor_end..range.anchor));
273 spans.push((selection_scope, cursor_end..range.anchor));
268 }
274 }
269 }
275 }
helix-view/src/editor.rs+36−6
@@ -1,6 +1,6 @@
1use crate::{
1use crate::{
2 clipboard::{get_clipboard_provider, ClipboardProvider},
2 clipboard::{get_clipboard_provider, ClipboardProvider},
3− document::SCRATCH_BUFFER_NAME,
3+ document::{Mode, SCRATCH_BUFFER_NAME},
4 graphics::{CursorKind, Rect},
4 graphics::{CursorKind, Rect},
5 theme::{self, Theme},
5 theme::{self, Theme},
6 tree::{self, Tree},
6 tree::{self, Tree},
@@ -9,7 +9,7 @@ use crate::{
9
9
10use futures_util::future;
10use futures_util::future;
11use std::{
11use std::{
12− collections::BTreeMap,
12+ collections::{BTreeMap, HashMap},
13 io::stdin,
13 io::stdin,
14 path::{Path, PathBuf},
14 path::{Path, PathBuf},
15 pin::Pin,
15 pin::Pin,
@@ -22,7 +22,7 @@ use anyhow::Error;
22
22
23pub use helix_core::diagnostic::Severity;
23pub use helix_core::diagnostic::Severity;
24pub use helix_core::register::Registers;
24pub use helix_core::register::Registers;
25−use helix_core::syntax;
25+use helix_core::{hashmap, syntax};
26use helix_core::{Position, Selection};
26use helix_core::{Position, Selection};
27
27
28use serde::Deserialize;
28use serde::Deserialize;
@@ -103,6 +103,30 @@ pub struct Config {
103 /// Whether to display infoboxes. Defaults to true.
103 /// Whether to display infoboxes. Defaults to true.
104 pub auto_info: bool,
104 pub auto_info: bool,
105 pub file_picker: FilePickerConfig,
105 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+ }
106}
130}
107
131
108#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
132#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
@@ -110,7 +134,6 @@ pub struct Config {
110pub enum LineNumber {
134pub enum LineNumber {
111 /// Show absolute line number
135 /// Show absolute line number
112 Absolute,
136 Absolute,
113−
114 /// Show relative line number to the primary cursor
137 /// Show relative line number to the primary cursor
115 Relative,
138 Relative,
116}
139}
@@ -135,6 +158,7 @@ impl Default for Config {
135 completion_trigger_len: 2,
158 completion_trigger_len: 2,
136 auto_info: true,
159 auto_info: true,
137 file_picker: FilePickerConfig::default(),
160 file_picker: FilePickerConfig::default(),
161+ cursor_shape: CursorShapeConfig::default(),
138 }
162 }
139 }
163 }
140}
164}
@@ -594,9 +618,15 @@ impl Editor {
594 let inner = view.inner_area();
618 let inner = view.inner_area();
595 pos.col += inner.x as usize;
619 pos.col += inner.x as usize;
596 pos.row += inner.y as usize;
620 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)
598 } else {
628 } else {
599− (None, CursorKind::Hidden)
629+ (None, CursorKind::default())
600 }
630 }
601 }
631 }
602
632
helix-view/src/graphics.rs+32
@@ -1,4 +1,6 @@
1+use anyhow::{anyhow, Error};
1use bitflags::bitflags;
2use bitflags::bitflags;
3+use serde::de::{self, Deserialize, Deserializer};
2use std::{
4use std::{
3 cmp::{max, min},
5 cmp::{max, min},
4 str::FromStr,
6 str::FromStr,
@@ -17,6 +19,36 @@ pub enum CursorKind {
17 Hidden,
19 Hidden,
18}
20}
19
21
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+
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub struct Margin {
53pub struct Margin {
22 pub vertical: u16,
54 pub vertical: u16,