1//! Contains the [`VimModeSetting`] and [`HelixModeSetting`] used to enable/disable Vim and Helix modes.
2//!
3//! This is in its own crate as we want other crates to be able to enable or
4//! disable Vim/Helix modes without having to depend on the `vim` crate in its
5//! entirety.
6
7use anyhow::Result;
8use gpui::App;
9use schemars::{JsonSchema, Schema, json_schema};
10
11use serde::de::Error;
12use serde::{Deserialize, Deserializer, Serialize, Serializer};
13use settings::{Settings, SettingsSources};
14use std::borrow::Cow;
15use std::fmt::Display;
16
17/// Initializes the `vim_mode_setting` crate.
18pub fn init(cx: &mut App) {
19 EditorModeSetting::register(cx);
20}
21
22/// Whether or not to enable Vim mode.
23///
24/// Default: `EditMode::Default`
25pub struct EditorModeSetting(pub EditorMode);
26
27#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
28pub enum EditorMode {
29 #[default]
30 Default,
31 Vim(ModalMode),
32 Helix(ModalMode),
33}
34
35impl JsonSchema for EditorMode {
36 fn schema_name() -> Cow<'static, str> {
37 "EditorMode".into()
38 }
39
40 fn json_schema(_gen: &mut schemars::SchemaGenerator) -> Schema {
41 json_schema!({
42 "oneOf": [
43 {
44 "const": "default",
45 "description": "Standard editing mode"
46 },
47 {
48 "const": "vim",
49 "description": "Vim normal mode"
50 },
51 {
52 "const": "vim_normal",
53 "description": "Vim normal mode"
54 },
55 {
56 "const": "vim_insert",
57 "description": "Vim insert mode"
58 },
59 {
60 "const": "vim_replace",
61 "description": "Vim replace mode"
62 },
63 {
64 "const": "vim_visual",
65 "description": "Vim visual mode"
66 },
67 {
68 "const": "vim_visual_line",
69 "description": "Vim visual line mode"
70 },
71 {
72 "const": "vim_visual_block",
73 "description": "Vim visual block mode"
74 },
75 {
76 "const": "helix_experimental",
77 "description": "Helix mode (experimental)"
78 }
79 ],
80 "description": "Editor mode"
81 })
82 }
83}
84
85impl<'de> Deserialize<'de> for EditorMode {
86 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
87 where
88 D: Deserializer<'de>,
89 {
90 let s = String::deserialize(deserializer)?;
91 match s.as_str() {
92 "default" => Ok(EditorMode::Default),
93 "vim" => Ok(EditorMode::Vim(ModalMode::Normal)),
94 "vim_normal" => Ok(EditorMode::Vim(ModalMode::Normal)),
95 "vim_insert" => Ok(EditorMode::Vim(ModalMode::Insert)),
96 "vim_replace" => Ok(EditorMode::Vim(ModalMode::Replace)),
97 "vim_visual" => Ok(EditorMode::Vim(ModalMode::Visual)),
98 "vim_visual_line" => Ok(EditorMode::Vim(ModalMode::VisualLine)),
99 "vim_visual_block" => Ok(EditorMode::Vim(ModalMode::VisualBlock)),
100 "helix_experimental" => Ok(EditorMode::Helix(ModalMode::HelixNormal)),
101 _ => Err(D::Error::custom(format!("Unknown editor mode: {}", s))),
102 }
103 }
104}
105
106impl Serialize for EditorMode {
107 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108 where
109 S: Serializer,
110 {
111 let s = match self {
112 EditorMode::Default => "default",
113 EditorMode::Vim(ModalMode::Normal) => "vim",
114 EditorMode::Vim(ModalMode::Insert) => "vim_insert",
115 EditorMode::Vim(ModalMode::Replace) => "vim_replace",
116 EditorMode::Vim(ModalMode::Visual) => "vim_visual",
117 EditorMode::Vim(ModalMode::VisualLine) => "vim_visual_line",
118 EditorMode::Vim(ModalMode::VisualBlock) => "vim_visual_block",
119 EditorMode::Helix(ModalMode::HelixNormal) => "helix_experimental",
120 _ => return Err(serde::ser::Error::custom("unsupported editor mode variant")),
121 };
122 serializer.serialize_str(s)
123 }
124}
125
126#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
127pub enum ModalMode {
128 Normal,
129 Insert,
130 Replace,
131 Visual,
132 VisualLine,
133 VisualBlock,
134 HelixNormal,
135}
136
137impl Display for ModalMode {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 match self {
140 ModalMode::Normal => write!(f, "NORMAL"),
141 ModalMode::Insert => write!(f, "INSERT"),
142 ModalMode::Replace => write!(f, "REPLACE"),
143 ModalMode::Visual => write!(f, "VISUAL"),
144 ModalMode::VisualLine => write!(f, "VISUAL LINE"),
145 ModalMode::VisualBlock => write!(f, "VISUAL BLOCK"),
146 ModalMode::HelixNormal => write!(f, "HELIX NORMAL"),
147 }
148 }
149}
150
151impl ModalMode {
152 pub fn is_visual(&self) -> bool {
153 match self {
154 Self::Visual | Self::VisualLine | Self::VisualBlock => true,
155 Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false,
156 }
157 }
158}
159
160impl Default for ModalMode {
161 fn default() -> Self {
162 Self::Normal
163 }
164}
165
166impl Settings for EditorModeSetting {
167 const KEY: Option<&'static str> = Some("editor_mode");
168
169 type FileContent = Option<EditorMode>;
170
171 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
172 Ok(Self(
173 sources
174 .user
175 .or(sources.server)
176 .copied()
177 .flatten()
178 .unwrap_or(sources.default.ok_or_else(Self::missing_default)?),
179 ))
180 }
181
182 fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {
183 // TODO: could possibly check if any of the `vim.<foo>` keys are set?
184 }
185}
186
187impl EditorMode {
188 pub fn is_modal(&self) -> bool {
189 matches!(self, EditorMode::Vim(_) | EditorMode::Helix(_))
190 }
191
192 pub fn vim() -> EditorMode {
193 EditorMode::Vim(ModalMode::default())
194 }
195}