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