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