vim_mode_setting.rs

  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}