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;
7use gpui::{Action, KeyContext};
8use language::{CursorShape, Selection, TransactionId};
9use serde::{Deserialize, Serialize};
10use workspace::searchable::Direction;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
13pub enum Mode {
14 Normal,
15 Insert,
16 Replace,
17 Visual,
18 VisualLine,
19 VisualBlock,
20}
21
22impl Display for Mode {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 Mode::Normal => write!(f, "NORMAL"),
26 Mode::Insert => write!(f, "INSERT"),
27 Mode::Replace => write!(f, "REPLACE"),
28 Mode::Visual => write!(f, "VISUAL"),
29 Mode::VisualLine => write!(f, "VISUAL LINE"),
30 Mode::VisualBlock => write!(f, "VISUAL BLOCK"),
31 }
32 }
33}
34
35impl Mode {
36 pub fn is_visual(&self) -> bool {
37 match self {
38 Mode::Normal | Mode::Insert | Mode::Replace => false,
39 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true,
40 }
41 }
42}
43
44impl Default for Mode {
45 fn default() -> Self {
46 Self::Normal
47 }
48}
49
50#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
51pub enum Operator {
52 Change,
53 Delete,
54 Yank,
55 Replace,
56 Object { around: bool },
57 FindForward { before: bool },
58 FindBackward { after: bool },
59 AddSurrounds { target: Option<SurroundsType> },
60 ChangeSurrounds { target: Option<Object> },
61 DeleteSurrounds,
62 Mark,
63 Jump { line: bool },
64 Indent,
65 Outdent,
66
67 Lowercase,
68 Uppercase,
69 OppositeCase,
70}
71
72#[derive(Default, Clone)]
73pub struct EditorState {
74 pub mode: Mode,
75 pub last_mode: Mode,
76
77 /// pre_count is the number before an operator is specified (3 in 3d2d)
78 pub pre_count: Option<usize>,
79 /// post_count is the number after an operator is specified (2 in 3d2d)
80 pub post_count: Option<usize>,
81
82 pub operator_stack: Vec<Operator>,
83 pub replacements: Vec<(Range<editor::Anchor>, String)>,
84
85 pub marks: HashMap<String, Vec<Anchor>>,
86 pub change_list: Vec<Vec<Anchor>>,
87 pub change_list_position: Option<usize>,
88
89 pub current_tx: Option<TransactionId>,
90 pub current_anchor: Option<Selection<Anchor>>,
91 pub undo_modes: HashMap<TransactionId, Mode>,
92}
93
94#[derive(Default, Clone, Debug)]
95pub enum RecordedSelection {
96 #[default]
97 None,
98 Visual {
99 rows: u32,
100 cols: u32,
101 },
102 SingleLine {
103 cols: u32,
104 },
105 VisualBlock {
106 rows: u32,
107 cols: u32,
108 },
109 VisualLine {
110 rows: u32,
111 },
112}
113
114#[derive(Default, Clone)]
115pub struct WorkspaceState {
116 pub search: SearchState,
117 pub last_find: Option<Motion>,
118
119 pub recording: bool,
120 pub stop_recording_after_next_action: bool,
121 pub replaying: bool,
122 pub recorded_count: Option<usize>,
123 pub recorded_actions: Vec<ReplayableAction>,
124 pub recorded_selection: RecordedSelection,
125
126 pub registers: HashMap<String, String>,
127}
128
129#[derive(Debug)]
130pub enum ReplayableAction {
131 Action(Box<dyn Action>),
132 Insertion {
133 text: Arc<str>,
134 utf16_range_to_replace: Option<Range<isize>>,
135 },
136}
137
138impl Clone for ReplayableAction {
139 fn clone(&self) -> Self {
140 match self {
141 Self::Action(action) => Self::Action(action.boxed_clone()),
142 Self::Insertion {
143 text,
144 utf16_range_to_replace,
145 } => Self::Insertion {
146 text: text.clone(),
147 utf16_range_to_replace: utf16_range_to_replace.clone(),
148 },
149 }
150 }
151}
152
153#[derive(Clone, Default, Debug)]
154pub struct SearchState {
155 pub direction: Direction,
156 pub count: usize,
157 pub initial_query: String,
158
159 pub prior_selections: Vec<Range<Anchor>>,
160 pub prior_operator: Option<Operator>,
161 pub prior_mode: Mode,
162}
163
164impl EditorState {
165 pub fn cursor_shape(&self) -> CursorShape {
166 match self.mode {
167 Mode::Normal => {
168 if self.operator_stack.is_empty() {
169 CursorShape::Block
170 } else {
171 CursorShape::Underscore
172 }
173 }
174 Mode::Replace => CursorShape::Underscore,
175 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
176 Mode::Insert => CursorShape::Bar,
177 }
178 }
179
180 pub fn vim_controlled(&self) -> bool {
181 let is_insert_mode = matches!(self.mode, Mode::Insert);
182 if !is_insert_mode {
183 return true;
184 }
185 matches!(
186 self.operator_stack.last(),
187 Some(Operator::FindForward { .. })
188 | Some(Operator::FindBackward { .. })
189 | Some(Operator::Mark)
190 | Some(Operator::Jump { .. })
191 )
192 }
193
194 pub fn should_autoindent(&self) -> bool {
195 !(self.mode == Mode::Insert && self.last_mode == Mode::VisualBlock)
196 }
197
198 pub fn clip_at_line_ends(&self) -> bool {
199 match self.mode {
200 Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::Replace => {
201 false
202 }
203 Mode::Normal => true,
204 }
205 }
206
207 pub fn active_operator(&self) -> Option<Operator> {
208 self.operator_stack.last().cloned()
209 }
210
211 pub fn keymap_context_layer(&self) -> KeyContext {
212 let mut context = KeyContext::new_with_defaults();
213 context.set(
214 "vim_mode",
215 match self.mode {
216 Mode::Normal => "normal",
217 Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
218 Mode::Insert => "insert",
219 Mode::Replace => "replace",
220 },
221 );
222
223 if self.vim_controlled() {
224 context.add("VimControl");
225 }
226
227 if self.active_operator().is_none() && self.pre_count.is_some()
228 || self.active_operator().is_some() && self.post_count.is_some()
229 {
230 context.add("VimCount");
231 }
232
233 let active_operator = self.active_operator();
234
235 if let Some(active_operator) = active_operator.clone() {
236 for context_flag in active_operator.context_flags().into_iter() {
237 context.add(*context_flag);
238 }
239 }
240
241 context.set(
242 "vim_operator",
243 active_operator
244 .clone()
245 .map(|op| op.id())
246 .unwrap_or_else(|| "none"),
247 );
248
249 if self.mode == Mode::Replace {
250 context.add("VimWaiting");
251 }
252 context
253 }
254}
255
256impl Operator {
257 pub fn id(&self) -> &'static str {
258 match self {
259 Operator::Object { around: false } => "i",
260 Operator::Object { around: true } => "a",
261 Operator::Change => "c",
262 Operator::Delete => "d",
263 Operator::Yank => "y",
264 Operator::Replace => "r",
265 Operator::FindForward { before: false } => "f",
266 Operator::FindForward { before: true } => "t",
267 Operator::FindBackward { after: false } => "F",
268 Operator::FindBackward { after: true } => "T",
269 Operator::AddSurrounds { .. } => "ys",
270 Operator::ChangeSurrounds { .. } => "cs",
271 Operator::DeleteSurrounds => "ds",
272 Operator::Mark => "m",
273 Operator::Jump { line: true } => "'",
274 Operator::Jump { line: false } => "`",
275 Operator::Indent => ">",
276 Operator::Outdent => "<",
277 Operator::Uppercase => "gU",
278 Operator::Lowercase => "gu",
279 Operator::OppositeCase => "g~",
280 }
281 }
282
283 pub fn context_flags(&self) -> &'static [&'static str] {
284 match self {
285 Operator::Object { .. } | Operator::ChangeSurrounds { target: None } => &["VimObject"],
286 Operator::FindForward { .. }
287 | Operator::Mark
288 | Operator::Jump { .. }
289 | Operator::FindBackward { .. }
290 | Operator::Replace
291 | Operator::AddSurrounds { target: Some(_) }
292 | Operator::ChangeSurrounds { .. }
293 | Operator::DeleteSurrounds => &["VimWaiting"],
294 _ => &[],
295 }
296 }
297}