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