state.rs

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