state.rs

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