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.set(
173            "vim_mode",
174            match self.mode {
175                Mode::Normal => "normal",
176                Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
177                Mode::Insert => "insert",
178            },
179        );
180
181        if self.vim_controlled() {
182            context.add("VimControl");
183        }
184
185        if self.active_operator().is_none() && self.pre_count.is_some()
186            || self.active_operator().is_some() && self.post_count.is_some()
187        {
188            context.add("VimCount");
189        }
190
191        let active_operator = self.active_operator();
192
193        if let Some(active_operator) = active_operator {
194            for context_flag in active_operator.context_flags().into_iter() {
195                context.add(*context_flag);
196            }
197        }
198
199        context.set(
200            "vim_operator",
201            active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
202        );
203
204        context
205    }
206}
207
208impl Operator {
209    pub fn id(&self) -> &'static str {
210        match self {
211            Operator::Object { around: false } => "i",
212            Operator::Object { around: true } => "a",
213            Operator::Change => "c",
214            Operator::Delete => "d",
215            Operator::Yank => "y",
216            Operator::Replace => "r",
217            Operator::FindForward { before: false } => "f",
218            Operator::FindForward { before: true } => "t",
219            Operator::FindBackward { after: false } => "F",
220            Operator::FindBackward { after: true } => "T",
221        }
222    }
223
224    pub fn context_flags(&self) -> &'static [&'static str] {
225        match self {
226            Operator::Object { .. } => &["VimObject"],
227            Operator::FindForward { .. } | Operator::FindBackward { .. } | Operator::Replace => {
228                &["VimWaiting"]
229            }
230            _ => &[],
231        }
232    }
233}