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 stored_visual_mode: Option<(Mode, Vec<bool>)>,
 88    pub change_list: Vec<Vec<Anchor>>,
 89    pub change_list_position: Option<usize>,
 90
 91    pub current_tx: Option<TransactionId>,
 92    pub current_anchor: Option<Selection<Anchor>>,
 93    pub undo_modes: HashMap<TransactionId, Mode>,
 94
 95    pub selected_register: Option<char>,
 96}
 97
 98#[derive(Default, Clone, Debug)]
 99pub enum RecordedSelection {
100    #[default]
101    None,
102    Visual {
103        rows: u32,
104        cols: u32,
105    },
106    SingleLine {
107        cols: u32,
108    },
109    VisualBlock {
110        rows: u32,
111        cols: u32,
112    },
113    VisualLine {
114        rows: u32,
115    },
116}
117
118#[derive(Default, Clone, Debug)]
119pub struct Register {
120    pub(crate) text: SharedString,
121    pub(crate) clipboard_selections: Option<Vec<ClipboardSelection>>,
122}
123
124impl From<Register> for ClipboardItem {
125    fn from(register: Register) -> Self {
126        let item = ClipboardItem::new(register.text.into());
127        if let Some(clipboard_selections) = register.clipboard_selections {
128            item.with_metadata(clipboard_selections)
129        } else {
130            item
131        }
132    }
133}
134
135impl From<ClipboardItem> for Register {
136    fn from(value: ClipboardItem) -> Self {
137        Register {
138            text: value.text().to_owned().into(),
139            clipboard_selections: value.metadata::<Vec<ClipboardSelection>>(),
140        }
141    }
142}
143
144impl From<String> for Register {
145    fn from(text: String) -> Self {
146        Register {
147            text: text.into(),
148            clipboard_selections: None,
149        }
150    }
151}
152
153#[derive(Default, Clone)]
154pub struct WorkspaceState {
155    pub search: SearchState,
156    pub last_find: Option<Motion>,
157
158    pub recording: bool,
159    pub stop_recording_after_next_action: bool,
160    pub replaying: bool,
161    pub recorded_count: Option<usize>,
162    pub recorded_actions: Vec<ReplayableAction>,
163    pub recorded_selection: RecordedSelection,
164
165    pub last_yank: Option<SharedString>,
166    pub registers: HashMap<char, Register>,
167}
168
169#[derive(Debug)]
170pub enum ReplayableAction {
171    Action(Box<dyn Action>),
172    Insertion {
173        text: Arc<str>,
174        utf16_range_to_replace: Option<Range<isize>>,
175    },
176}
177
178impl Clone for ReplayableAction {
179    fn clone(&self) -> Self {
180        match self {
181            Self::Action(action) => Self::Action(action.boxed_clone()),
182            Self::Insertion {
183                text,
184                utf16_range_to_replace,
185            } => Self::Insertion {
186                text: text.clone(),
187                utf16_range_to_replace: utf16_range_to_replace.clone(),
188            },
189        }
190    }
191}
192
193#[derive(Clone, Default, Debug)]
194pub struct SearchState {
195    pub direction: Direction,
196    pub count: usize,
197    pub initial_query: String,
198
199    pub prior_selections: Vec<Range<Anchor>>,
200    pub prior_operator: Option<Operator>,
201    pub prior_mode: Mode,
202}
203
204impl EditorState {
205    pub fn cursor_shape(&self) -> CursorShape {
206        match self.mode {
207            Mode::Normal => {
208                if self.operator_stack.is_empty() {
209                    CursorShape::Block
210                } else {
211                    CursorShape::Underscore
212                }
213            }
214            Mode::Replace => CursorShape::Underscore,
215            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
216            Mode::Insert => CursorShape::Bar,
217        }
218    }
219
220    pub fn vim_controlled(&self) -> bool {
221        let is_insert_mode = matches!(self.mode, Mode::Insert);
222        if !is_insert_mode {
223            return true;
224        }
225        matches!(
226            self.operator_stack.last(),
227            Some(Operator::FindForward { .. })
228                | Some(Operator::FindBackward { .. })
229                | Some(Operator::Mark)
230                | Some(Operator::Register)
231                | Some(Operator::Jump { .. })
232        )
233    }
234
235    pub fn should_autoindent(&self) -> bool {
236        !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
237    }
238
239    pub fn clip_at_line_ends(&self) -> bool {
240        match self.mode {
241            Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::Replace => {
242                false
243            }
244            Mode::Normal => true,
245        }
246    }
247
248    pub fn active_operator(&self) -> Option<Operator> {
249        self.operator_stack.last().cloned()
250    }
251
252    pub fn keymap_context_layer(&self) -> KeyContext {
253        let mut context = KeyContext::new_with_defaults();
254        context.set(
255            "vim_mode",
256            match self.mode {
257                Mode::Normal => "normal",
258                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
259                Mode::Insert => "insert",
260                Mode::Replace => "replace",
261            },
262        );
263
264        if self.vim_controlled() {
265            context.add("VimControl");
266        }
267
268        if self.active_operator().is_none() && self.pre_count.is_some()
269            || self.active_operator().is_some() && self.post_count.is_some()
270        {
271            context.add("VimCount");
272        }
273
274        let active_operator = self.active_operator();
275
276        if let Some(active_operator) = active_operator.clone() {
277            for context_flag in active_operator.context_flags().into_iter() {
278                context.add(*context_flag);
279            }
280        }
281
282        context.set(
283            "vim_operator",
284            active_operator
285                .clone()
286                .map(|op| op.id())
287                .unwrap_or_else(|| "none"),
288        );
289
290        if self.mode == Mode::Replace {
291            context.add("VimWaiting");
292        }
293        context
294    }
295}
296
297impl Operator {
298    pub fn id(&self) -> &'static str {
299        match self {
300            Operator::Object { around: false } => "i",
301            Operator::Object { around: true } => "a",
302            Operator::Change => "c",
303            Operator::Delete => "d",
304            Operator::Yank => "y",
305            Operator::Replace => "r",
306            Operator::FindForward { before: false } => "f",
307            Operator::FindForward { before: true } => "t",
308            Operator::FindBackward { after: false } => "F",
309            Operator::FindBackward { after: true } => "T",
310            Operator::AddSurrounds { .. } => "ys",
311            Operator::ChangeSurrounds { .. } => "cs",
312            Operator::DeleteSurrounds => "ds",
313            Operator::Mark => "m",
314            Operator::Jump { line: true } => "'",
315            Operator::Jump { line: false } => "`",
316            Operator::Indent => ">",
317            Operator::Outdent => "<",
318            Operator::Uppercase => "gU",
319            Operator::Lowercase => "gu",
320            Operator::OppositeCase => "g~",
321            Operator::Register => "\"",
322        }
323    }
324
325    pub fn context_flags(&self) -> &'static [&'static str] {
326        match self {
327            Operator::Object { .. } | Operator::ChangeSurrounds { target: None } => &["VimObject"],
328            Operator::FindForward { .. }
329            | Operator::Mark
330            | Operator::Jump { .. }
331            | Operator::FindBackward { .. }
332            | Operator::Register
333            | Operator::Replace
334            | Operator::AddSurrounds { target: Some(_) }
335            | Operator::ChangeSurrounds { .. }
336            | Operator::DeleteSurrounds => &["VimWaiting"],
337            _ => &[],
338        }
339    }
340}