state.rs

  1use std::{ops::Range, sync::Arc};
  2
  3use gpui::{Action, KeyContext};
  4use language::CursorShape;
  5use serde::{Deserialize, Serialize};
  6use workspace::searchable::Direction;
  7
  8use crate::motion::Motion;
  9
 10#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
 11pub enum Mode {
 12    Normal,
 13    Insert,
 14    Visual,
 15    VisualLine,
 16    VisualBlock,
 17}
 18
 19impl Mode {
 20    pub fn is_visual(&self) -> bool {
 21        match self {
 22            Mode::Normal | Mode::Insert => false,
 23            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true,
 24        }
 25    }
 26}
 27
 28impl Default for Mode {
 29    fn default() -> Self {
 30        Self::Normal
 31    }
 32}
 33
 34#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
 35pub enum Operator {
 36    Change,
 37    Delete,
 38    Yank,
 39    Replace,
 40    Object { around: bool },
 41    FindForward { before: bool },
 42    FindBackward { after: bool },
 43}
 44
 45#[derive(Default, Clone)]
 46pub struct EditorState {
 47    pub mode: Mode,
 48    pub last_mode: Mode,
 49
 50    /// pre_count is the number before an operator is specified (3 in 3d2d)
 51    pub pre_count: Option<usize>,
 52    /// post_count is the number after an operator is specified (2 in 3d2d)
 53    pub post_count: Option<usize>,
 54
 55    pub operator_stack: Vec<Operator>,
 56}
 57
 58#[derive(Default, Clone, Debug)]
 59pub enum RecordedSelection {
 60    #[default]
 61    None,
 62    Visual {
 63        rows: u32,
 64        cols: u32,
 65    },
 66    SingleLine {
 67        cols: u32,
 68    },
 69    VisualBlock {
 70        rows: u32,
 71        cols: u32,
 72    },
 73    VisualLine {
 74        rows: u32,
 75    },
 76}
 77
 78#[derive(Default, Clone)]
 79pub struct WorkspaceState {
 80    pub search: SearchState,
 81    pub last_find: Option<Motion>,
 82
 83    pub recording: bool,
 84    pub stop_recording_after_next_action: bool,
 85    pub replaying: bool,
 86    pub recorded_count: Option<usize>,
 87    pub recorded_actions: Vec<ReplayableAction>,
 88    pub recorded_selection: RecordedSelection,
 89}
 90
 91#[derive(Debug)]
 92pub enum ReplayableAction {
 93    Action(Box<dyn Action>),
 94    Insertion {
 95        text: Arc<str>,
 96        utf16_range_to_replace: Option<Range<isize>>,
 97    },
 98}
 99
100impl Clone for ReplayableAction {
101    fn clone(&self) -> Self {
102        match self {
103            Self::Action(action) => Self::Action(action.boxed_clone()),
104            Self::Insertion {
105                text,
106                utf16_range_to_replace,
107            } => Self::Insertion {
108                text: text.clone(),
109                utf16_range_to_replace: utf16_range_to_replace.clone(),
110            },
111        }
112    }
113}
114
115#[derive(Clone)]
116pub struct SearchState {
117    pub direction: Direction,
118    pub count: usize,
119    pub initial_query: String,
120}
121
122impl Default for SearchState {
123    fn default() -> Self {
124        Self {
125            direction: Direction::Next,
126            count: 1,
127            initial_query: "".to_string(),
128        }
129    }
130}
131
132impl EditorState {
133    pub fn cursor_shape(&self) -> CursorShape {
134        match self.mode {
135            Mode::Normal => {
136                if self.operator_stack.is_empty() {
137                    CursorShape::Block
138                } else {
139                    CursorShape::Underscore
140                }
141            }
142            Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
143            Mode::Insert => CursorShape::Bar,
144        }
145    }
146
147    pub fn vim_controlled(&self) -> bool {
148        !matches!(self.mode, Mode::Insert)
149            || matches!(
150                self.operator_stack.last(),
151                Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. })
152            )
153    }
154
155    pub fn should_autoindent(&self) -> bool {
156        !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
157    }
158
159    pub fn clip_at_line_ends(&self) -> bool {
160        match self.mode {
161            Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => false,
162            Mode::Normal => true,
163        }
164    }
165
166    pub fn active_operator(&self) -> Option<Operator> {
167        self.operator_stack.last().copied()
168    }
169
170    pub fn keymap_context_layer(&self) -> KeyContext {
171        let mut context = KeyContext::default();
172        context.add("VimEnabled");
173        context.set(
174            "vim_mode",
175            match self.mode {
176                Mode::Normal => "normal",
177                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
178                Mode::Insert => "insert",
179            },
180        );
181
182        if self.vim_controlled() {
183            context.add("VimControl");
184        }
185
186        if self.active_operator().is_none() && self.pre_count.is_some()
187            || self.active_operator().is_some() && self.post_count.is_some()
188        {
189            context.add("VimCount");
190        }
191
192        let active_operator = self.active_operator();
193
194        if let Some(active_operator) = active_operator {
195            for context_flag in active_operator.context_flags().into_iter() {
196                context.add(*context_flag);
197            }
198        }
199
200        context.set(
201            "vim_operator",
202            active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
203        );
204
205        context
206    }
207}
208
209impl Operator {
210    pub fn id(&self) -> &'static str {
211        match self {
212            Operator::Object { around: false } => "i",
213            Operator::Object { around: true } => "a",
214            Operator::Change => "c",
215            Operator::Delete => "d",
216            Operator::Yank => "y",
217            Operator::Replace => "r",
218            Operator::FindForward { before: false } => "f",
219            Operator::FindForward { before: true } => "t",
220            Operator::FindBackward { after: false } => "F",
221            Operator::FindBackward { after: true } => "T",
222        }
223    }
224
225    pub fn context_flags(&self) -> &'static [&'static str] {
226        match self {
227            Operator::Object { .. } => &["VimObject"],
228            Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => {
229                &["VimWaiting"]
230            }
231            _ => &[],
232        }
233    }
234}