state.rs

  1use std::{fmt::Display, ops::Range, sync::Arc};
  2
  3use crate::surrounds::SurroundsType;
  4use crate::{motion::Motion, object::Object};
  5use collections::HashMap;
  6use editor::Anchor;
  7use gpui::{Action, KeyContext};
  8use language::{CursorShape, Selection, TransactionId};
  9use serde::{Deserialize, Serialize};
 10use workspace::searchable::Direction;
 11
 12#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
 13pub enum Mode {
 14    Normal,
 15    Insert,
 16    Replace,
 17    Visual,
 18    VisualLine,
 19    VisualBlock,
 20}
 21
 22impl Display for Mode {
 23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 24        match self {
 25            Mode::Normal => write!(f, "NORMAL"),
 26            Mode::Insert => write!(f, "INSERT"),
 27            Mode::Replace => write!(f, "REPLACE"),
 28            Mode::Visual => write!(f, "VISUAL"),
 29            Mode::VisualLine => write!(f, "VISUAL LINE"),
 30            Mode::VisualBlock => write!(f, "VISUAL BLOCK"),
 31        }
 32    }
 33}
 34
 35impl Mode {
 36    pub fn is_visual(&self) -> bool {
 37        match self {
 38            Mode::Normal | Mode::Insert | Mode::Replace => false,
 39            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true,
 40        }
 41    }
 42}
 43
 44impl Default for Mode {
 45    fn default() -> Self {
 46        Self::Normal
 47    }
 48}
 49
 50#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
 51pub enum Operator {
 52    Change,
 53    Delete,
 54    Yank,
 55    Replace,
 56    Object { around: bool },
 57    FindForward { before: bool },
 58    FindBackward { after: bool },
 59    AddSurrounds { target: Option<SurroundsType> },
 60    ChangeSurrounds { target: Option<Object> },
 61    DeleteSurrounds,
 62    Mark,
 63    Jump { line: bool },
 64    Indent,
 65    Outdent,
 66
 67    Lowercase,
 68    Uppercase,
 69    OppositeCase,
 70}
 71
 72#[derive(Default, Clone)]
 73pub struct EditorState {
 74    pub mode: Mode,
 75    pub last_mode: Mode,
 76
 77    /// pre_count is the number before an operator is specified (3 in 3d2d)
 78    pub pre_count: Option<usize>,
 79    /// post_count is the number after an operator is specified (2 in 3d2d)
 80    pub post_count: Option<usize>,
 81
 82    pub operator_stack: Vec<Operator>,
 83    pub replacements: Vec<(Range<editor::Anchor>, String)>,
 84
 85    pub marks: HashMap<String, Vec<Anchor>>,
 86    pub change_list: Vec<Vec<Anchor>>,
 87    pub change_list_position: Option<usize>,
 88
 89    pub current_tx: Option<TransactionId>,
 90    pub current_anchor: Option<Selection<Anchor>>,
 91    pub undo_modes: HashMap<TransactionId, Mode>,
 92}
 93
 94#[derive(Default, Clone, Debug)]
 95pub enum RecordedSelection {
 96    #[default]
 97    None,
 98    Visual {
 99        rows: u32,
100        cols: u32,
101    },
102    SingleLine {
103        cols: u32,
104    },
105    VisualBlock {
106        rows: u32,
107        cols: u32,
108    },
109    VisualLine {
110        rows: u32,
111    },
112}
113
114#[derive(Default, Clone)]
115pub struct WorkspaceState {
116    pub search: SearchState,
117    pub last_find: Option<Motion>,
118
119    pub recording: bool,
120    pub stop_recording_after_next_action: bool,
121    pub replaying: bool,
122    pub recorded_count: Option<usize>,
123    pub recorded_actions: Vec<ReplayableAction>,
124    pub recorded_selection: RecordedSelection,
125
126    pub registers: HashMap<String, String>,
127}
128
129#[derive(Debug)]
130pub enum ReplayableAction {
131    Action(Box<dyn Action>),
132    Insertion {
133        text: Arc<str>,
134        utf16_range_to_replace: Option<Range<isize>>,
135    },
136}
137
138impl Clone for ReplayableAction {
139    fn clone(&self) -> Self {
140        match self {
141            Self::Action(action) => Self::Action(action.boxed_clone()),
142            Self::Insertion {
143                text,
144                utf16_range_to_replace,
145            } => Self::Insertion {
146                text: text.clone(),
147                utf16_range_to_replace: utf16_range_to_replace.clone(),
148            },
149        }
150    }
151}
152
153#[derive(Clone, Default, Debug)]
154pub struct SearchState {
155    pub direction: Direction,
156    pub count: usize,
157    pub initial_query: String,
158
159    pub prior_selections: Vec<Range<Anchor>>,
160    pub prior_operator: Option<Operator>,
161    pub prior_mode: Mode,
162}
163
164impl EditorState {
165    pub fn cursor_shape(&self) -> CursorShape {
166        match self.mode {
167            Mode::Normal => {
168                if self.operator_stack.is_empty() {
169                    CursorShape::Block
170                } else {
171                    CursorShape::Underscore
172                }
173            }
174            Mode::Replace => CursorShape::Underscore,
175            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
176            Mode::Insert => CursorShape::Bar,
177        }
178    }
179
180    pub fn vim_controlled(&self) -> bool {
181        let is_insert_mode = matches!(self.mode, Mode::Insert);
182        if !is_insert_mode {
183            return true;
184        }
185        matches!(
186            self.operator_stack.last(),
187            Some(Operator::FindForward { .. })
188                | Some(Operator::FindBackward { .. })
189                | Some(Operator::Mark)
190                | Some(Operator::Jump { .. })
191        )
192    }
193
194    pub fn should_autoindent(&self) -> bool {
195        !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
196    }
197
198    pub fn clip_at_line_ends(&self) -> bool {
199        match self.mode {
200            Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::Replace => {
201                false
202            }
203            Mode::Normal => true,
204        }
205    }
206
207    pub fn active_operator(&self) -> Option<Operator> {
208        self.operator_stack.last().cloned()
209    }
210
211    pub fn keymap_context_layer(&self) -> KeyContext {
212        let mut context = KeyContext::new_with_defaults();
213        context.set(
214            "vim_mode",
215            match self.mode {
216                Mode::Normal => "normal",
217                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
218                Mode::Insert => "insert",
219                Mode::Replace => "replace",
220            },
221        );
222
223        if self.vim_controlled() {
224            context.add("VimControl");
225        }
226
227        if self.active_operator().is_none() && self.pre_count.is_some()
228            || self.active_operator().is_some() && self.post_count.is_some()
229        {
230            context.add("VimCount");
231        }
232
233        let active_operator = self.active_operator();
234
235        if let Some(active_operator) = active_operator.clone() {
236            for context_flag in active_operator.context_flags().into_iter() {
237                context.add(*context_flag);
238            }
239        }
240
241        context.set(
242            "vim_operator",
243            active_operator
244                .clone()
245                .map(|op| op.id())
246                .unwrap_or_else(|| "none"),
247        );
248
249        if self.mode == Mode::Replace {
250            context.add("VimWaiting");
251        }
252        context
253    }
254}
255
256impl Operator {
257    pub fn id(&self) -> &'static str {
258        match self {
259            Operator::Object { around: false } => "i",
260            Operator::Object { around: true } => "a",
261            Operator::Change => "c",
262            Operator::Delete => "d",
263            Operator::Yank => "y",
264            Operator::Replace => "r",
265            Operator::FindForward { before: false } => "f",
266            Operator::FindForward { before: true } => "t",
267            Operator::FindBackward { after: false } => "F",
268            Operator::FindBackward { after: true } => "T",
269            Operator::AddSurrounds { .. } => "ys",
270            Operator::ChangeSurrounds { .. } => "cs",
271            Operator::DeleteSurrounds => "ds",
272            Operator::Mark => "m",
273            Operator::Jump { line: true } => "'",
274            Operator::Jump { line: false } => "`",
275            Operator::Indent => ">",
276            Operator::Outdent => "<",
277            Operator::Uppercase => "gU",
278            Operator::Lowercase => "gu",
279            Operator::OppositeCase => "g~",
280        }
281    }
282
283    pub fn context_flags(&self) -> &'static [&'static str] {
284        match self {
285            Operator::Object { .. } | Operator::ChangeSurrounds { target: None } => &["VimObject"],
286            Operator::FindForward { .. }
287            | Operator::Mark
288            | Operator::Jump { .. }
289            | Operator::FindBackward { .. }
290            | Operator::Replace
291            | Operator::AddSurrounds { target: Some(_) }
292            | Operator::ChangeSurrounds { .. }
293            | Operator::DeleteSurrounds => &["VimWaiting"],
294            _ => &[],
295        }
296    }
297}