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