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    pub search: SearchState,
 97}
 98
 99#[derive(Default, Clone, Debug)]
100pub enum RecordedSelection {
101    #[default]
102    None,
103    Visual {
104        rows: u32,
105        cols: u32,
106    },
107    SingleLine {
108        cols: u32,
109    },
110    VisualBlock {
111        rows: u32,
112        cols: u32,
113    },
114    VisualLine {
115        rows: u32,
116    },
117}
118
119#[derive(Default, Clone, Debug)]
120pub struct Register {
121    pub(crate) text: SharedString,
122    pub(crate) clipboard_selections: Option<Vec<ClipboardSelection>>,
123}
124
125impl From<Register> for ClipboardItem {
126    fn from(register: Register) -> Self {
127        let item = ClipboardItem::new(register.text.into());
128        if let Some(clipboard_selections) = register.clipboard_selections {
129            item.with_metadata(clipboard_selections)
130        } else {
131            item
132        }
133    }
134}
135
136impl From<ClipboardItem> for Register {
137    fn from(value: ClipboardItem) -> Self {
138        Register {
139            text: value.text().to_owned().into(),
140            clipboard_selections: value.metadata::<Vec<ClipboardSelection>>(),
141        }
142    }
143}
144
145impl From<String> for Register {
146    fn from(text: String) -> Self {
147        Register {
148            text: text.into(),
149            clipboard_selections: None,
150        }
151    }
152}
153
154#[derive(Default, Clone)]
155pub struct WorkspaceState {
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            || (matches!(active_operator, Some(Operator::AddSurrounds { .. }))
292                && self.mode.is_visual())
293        {
294            context.add("VimWaiting");
295        }
296        context
297    }
298}
299
300impl Operator {
301    pub fn id(&self) -> &'static str {
302        match self {
303            Operator::Object { around: false } => "i",
304            Operator::Object { around: true } => "a",
305            Operator::Change => "c",
306            Operator::Delete => "d",
307            Operator::Yank => "y",
308            Operator::Replace => "r",
309            Operator::FindForward { before: false } => "f",
310            Operator::FindForward { before: true } => "t",
311            Operator::FindBackward { after: false } => "F",
312            Operator::FindBackward { after: true } => "T",
313            Operator::AddSurrounds { .. } => "ys",
314            Operator::ChangeSurrounds { .. } => "cs",
315            Operator::DeleteSurrounds => "ds",
316            Operator::Mark => "m",
317            Operator::Jump { line: true } => "'",
318            Operator::Jump { line: false } => "`",
319            Operator::Indent => ">",
320            Operator::Outdent => "<",
321            Operator::Uppercase => "gU",
322            Operator::Lowercase => "gu",
323            Operator::OppositeCase => "g~",
324            Operator::Register => "\"",
325        }
326    }
327
328    pub fn context_flags(&self) -> &'static [&'static str] {
329        match self {
330            Operator::Object { .. } | Operator::ChangeSurrounds { target: None } => &["VimObject"],
331            Operator::FindForward { .. }
332            | Operator::Mark
333            | Operator::Jump { .. }
334            | Operator::FindBackward { .. }
335            | Operator::Register
336            | Operator::Replace
337            | Operator::AddSurrounds { target: Some(_) }
338            | Operator::ChangeSurrounds { .. }
339            | Operator::DeleteSurrounds => &["VimWaiting"],
340            _ => &[],
341        }
342    }
343}