@@ -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 input::KeyEvent,
66 theme::{self, Theme},
@@ -10,7 +10,7 @@ use crate::{
1010
1111use futures_util::future;
1212use std::{
13− collections::BTreeMap,
13+ collections::{BTreeMap, HashMap},
1414 io::stdin,
1515 num::NonZeroUsize,
1616 path::{Path, PathBuf},
@@ -27,7 +27,7 @@ pub use helix_core::register::Registers;
2727use helix_core::syntax;
2828use helix_core::{Position, Selection};
2929
30−use serde::{Deserialize, Serialize};
30+use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize};
3131
3232fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
3333where
@@ -105,16 +105,71 @@ pub struct Config {
105105 /// Whether to display infoboxes. Defaults to true.
106106 pub auto_info: bool,
107107 pub file_picker: FilePickerConfig,
108+ /// Shape for cursor in each mode
109+ pub cursor_shape: CursorShapeConfig,
108110 /// Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. Defaults to `false`.
109111 pub true_color: bool,
110112}
111113
114+// Cursor shape is read and used on every rendered frame and so needs
115+// to be fast. Therefore we avoid a hashmap and use an enum indexed array.
116+#[derive(Debug, Clone, PartialEq)]
117+pub struct CursorShapeConfig([CursorKind; 3]);
118+
119+impl CursorShapeConfig {
120+ pub fn from_mode(&self, mode: Mode) -> CursorKind {
121+ self.get(mode as usize).copied().unwrap_or_default()
122+ }
123+}
124+
125+impl<'de> Deserialize<'de> for CursorShapeConfig {
126+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
127+ where
128+ D: Deserializer<'de>,
129+ {
130+ let m = HashMap::<Mode, CursorKind>::deserialize(deserializer)?;
131+ let into_cursor = |mode: Mode| m.get(&mode).copied().unwrap_or_default();
132+ Ok(CursorShapeConfig([
133+ into_cursor(Mode::Normal),
134+ into_cursor(Mode::Select),
135+ into_cursor(Mode::Insert),
136+ ]))
137+ }
138+}
139+
140+impl Serialize for CursorShapeConfig {
141+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
142+ where
143+ S: serde::Serializer,
144+ {
145+ let mut map = serializer.serialize_map(Some(self.len()))?;
146+ let modes = [Mode::Normal, Mode::Select, Mode::Insert];
147+ for mode in modes {
148+ map.serialize_entry(&mode, &self.from_mode(mode))?;
149+ }
150+ map.end()
151+ }
152+}
153+
154+impl std::ops::Deref for CursorShapeConfig {
155+ type Target = [CursorKind; 3];
156+
157+ fn deref(&self) -> &Self::Target {
158+ &self.0
159+ }
160+}
161+
162+impl Default for CursorShapeConfig {
163+ fn default() -> Self {
164+ Self([CursorKind::Block; 3])
165+ }
166+}
167+
112168#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
113169#[serde(rename_all = "kebab-case")]
114170pub enum LineNumber {
115171 /// Show absolute line number
116172 Absolute,
117−
118173 /// Show relative line number to the primary cursor
119174 Relative,
120175}
@@ -151,6 +206,7 @@ impl Default for Config {
151206 completion_trigger_len: 2,
152207 auto_info: true,
153208 file_picker: FilePickerConfig::default(),
209+ cursor_shape: CursorShapeConfig::default(),
154210 true_color: false,
155211 }
156212 }
@@ -614,9 +670,10 @@ impl Editor {
614670 let inner = view.inner_area();
615671 pos.col += inner.x as usize;
616672 pos.row += inner.y as usize;
617− (Some(pos), CursorKind::Hidden)
673+ let cursorkind = self.config.cursor_shape.from_mode(doc.mode());
674+ (Some(pos), cursorkind)
618675 } else {
619− (None, CursorKind::Hidden)
676+ (None, CursorKind::default())
620677 }
621678 }
622679