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 `editor_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 let options = Self::get_schema_options();
42 json_schema!({
43 "oneOf": options,
44 "description": "Editor mode"
45 })
46 }
47}
48
49impl<'de> Deserialize<'de> for EditorMode {
50 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
51 where
52 D: Deserializer<'de>,
53 {
54 let s = String::deserialize(deserializer)?;
55 match s.as_str() {
56 "default" => Ok(EditorMode::Default),
57 "vim" => Ok(EditorMode::Vim(ModalMode::Normal)),
58 "vim_normal" => Ok(EditorMode::Vim(ModalMode::Normal)),
59 "vim_insert" => Ok(EditorMode::Vim(ModalMode::Insert)),
60 "vim_replace" => Ok(EditorMode::Vim(ModalMode::Replace)),
61 "vim_visual" => Ok(EditorMode::Vim(ModalMode::Visual)),
62 "vim_visual_line" => Ok(EditorMode::Vim(ModalMode::VisualLine)),
63 "vim_visual_block" => Ok(EditorMode::Vim(ModalMode::VisualBlock)),
64 "helix_experimental" => Ok(EditorMode::Helix(ModalMode::HelixNormal)),
65 _ => Err(D::Error::custom(format!("Unknown editor mode: {}", s))),
66 }
67 }
68}
69
70impl Serialize for EditorMode {
71 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
72 where
73 S: Serializer,
74 {
75 let s = match self {
76 EditorMode::Default => "default",
77 EditorMode::Vim(ModalMode::Normal) => "vim",
78 EditorMode::Vim(ModalMode::Insert) => "vim_insert",
79 EditorMode::Vim(ModalMode::Replace) => "vim_replace",
80 EditorMode::Vim(ModalMode::Visual) => "vim_visual",
81 EditorMode::Vim(ModalMode::VisualLine) => "vim_visual_line",
82 EditorMode::Vim(ModalMode::VisualBlock) => "vim_visual_block",
83 EditorMode::Helix(ModalMode::HelixNormal) => "helix_experimental",
84 _ => return Err(serde::ser::Error::custom("unsupported editor mode variant")),
85 };
86 serializer.serialize_str(s)
87 }
88}
89
90#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
91pub enum ModalMode {
92 Normal,
93 Insert,
94 Replace,
95 Visual,
96 VisualLine,
97 VisualBlock,
98 HelixNormal,
99}
100
101impl Display for ModalMode {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 match self {
104 ModalMode::Normal => write!(f, "NORMAL"),
105 ModalMode::Insert => write!(f, "INSERT"),
106 ModalMode::Replace => write!(f, "REPLACE"),
107 ModalMode::Visual => write!(f, "VISUAL"),
108 ModalMode::VisualLine => write!(f, "VISUAL LINE"),
109 ModalMode::VisualBlock => write!(f, "VISUAL BLOCK"),
110 ModalMode::HelixNormal => write!(f, "HELIX NORMAL"),
111 }
112 }
113}
114
115impl ModalMode {
116 pub fn is_visual(&self) -> bool {
117 match self {
118 Self::Visual | Self::VisualLine | Self::VisualBlock => true,
119 Self::Normal | Self::Insert | Self::Replace | Self::HelixNormal => false,
120 }
121 }
122}
123
124impl Default for ModalMode {
125 fn default() -> Self {
126 Self::Normal
127 }
128}
129
130impl Settings for EditorModeSetting {
131 const KEY: Option<&'static str> = Some("editor_mode");
132
133 type FileContent = Option<EditorMode>;
134
135 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
136 Ok(Self(
137 sources
138 .user
139 .or(sources.server)
140 .copied()
141 .flatten()
142 .unwrap_or(sources.default.ok_or_else(Self::missing_default)?),
143 ))
144 }
145
146 fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {
147 // TODO: could possibly check if any of the `vim.<foo>` keys are set?
148 }
149}
150
151impl EditorMode {
152 pub fn is_modal(&self) -> bool {
153 matches!(self, EditorMode::Vim(_) | EditorMode::Helix(_))
154 }
155
156 pub fn vim() -> EditorMode {
157 EditorMode::Vim(ModalMode::default())
158 }
159
160 pub fn get_schema_options() -> Vec<serde_json::Value> {
161 vec![
162 serde_json::json!({
163 "const": "default",
164 "description": "Standard editing mode"
165 }),
166 serde_json::json!({
167 "const": "vim",
168 "description": "Vim normal mode"
169 }),
170 serde_json::json!({
171 "const": "vim_normal",
172 "description": "Vim normal mode"
173 }),
174 serde_json::json!({
175 "const": "vim_insert",
176 "description": "Vim insert mode"
177 }),
178 serde_json::json!({
179 "const": "vim_replace",
180 "description": "Vim replace mode"
181 }),
182 serde_json::json!({
183 "const": "vim_visual",
184 "description": "Vim visual mode"
185 }),
186 serde_json::json!({
187 "const": "vim_visual_line",
188 "description": "Vim visual line mode"
189 }),
190 serde_json::json!({
191 "const": "vim_visual_block",
192 "description": "Vim visual block mode"
193 }),
194 serde_json::json!({
195 "const": "helix_experimental",
196 "description": "Helix mode (experimental)"
197 }),
198 ]
199 }
200}