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