1#[cfg(test)]
2mod test;
3
4mod editor_events;
5mod insert;
6mod motion;
7mod normal;
8mod object;
9mod state;
10mod utils;
11mod visual;
12
13use std::sync::Arc;
14
15use command_palette::CommandPaletteFilter;
16use editor::{Bias, Cancel, Editor, EditorMode};
17use gpui::{
18 impl_actions, MutableAppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle,
19};
20use language::CursorShape;
21use motion::Motion;
22use normal::normal_replace;
23use serde::Deserialize;
24use settings::Settings;
25use state::{Mode, Operator, VimState};
26use visual::visual_replace;
27use workspace::{self, Workspace};
28
29#[derive(Clone, Deserialize, PartialEq)]
30pub struct SwitchMode(pub Mode);
31
32#[derive(Clone, Deserialize, PartialEq)]
33pub struct PushOperator(pub Operator);
34
35#[derive(Clone, Deserialize, PartialEq)]
36struct Number(u8);
37
38impl_actions!(vim, [Number, SwitchMode, PushOperator]);
39
40pub fn init(cx: &mut MutableAppContext) {
41 editor_events::init(cx);
42 normal::init(cx);
43 visual::init(cx);
44 insert::init(cx);
45 object::init(cx);
46 motion::init(cx);
47
48 // Vim Actions
49 cx.add_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
50 Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
51 });
52 cx.add_action(
53 |_: &mut Workspace, &PushOperator(operator): &PushOperator, cx| {
54 Vim::update(cx, |vim, cx| vim.push_operator(operator, cx))
55 },
56 );
57 cx.add_action(|_: &mut Workspace, n: &Number, cx: _| {
58 Vim::update(cx, |vim, cx| vim.push_number(n, cx));
59 });
60
61 // Editor Actions
62 cx.add_action(|_: &mut Editor, _: &Cancel, cx| {
63 // If we are in aren't in normal mode or have an active operator, swap to normal mode
64 // Otherwise forward cancel on to the editor
65 let vim = Vim::read(cx);
66 if vim.state.mode != Mode::Normal || vim.active_operator().is_some() {
67 MutableAppContext::defer(cx, |cx| {
68 Vim::update(cx, |state, cx| {
69 state.switch_mode(Mode::Normal, false, cx);
70 });
71 });
72 } else {
73 cx.propagate_action();
74 }
75 });
76
77 // Sync initial settings with the rest of the app
78 Vim::update(cx, |state, cx| state.sync_vim_settings(cx));
79
80 // Any time settings change, update vim mode to match
81 cx.observe_global::<Settings, _>(|cx| {
82 Vim::update(cx, |state, cx| {
83 state.set_enabled(cx.global::<Settings>().vim_mode, cx)
84 })
85 })
86 .detach();
87}
88
89pub fn observe_keystrokes(window_id: usize, cx: &mut MutableAppContext) {
90 cx.observe_keystrokes(window_id, |_keystroke, _result, handled_by, cx| {
91 if let Some(handled_by) = handled_by {
92 // Keystroke is handled by the vim system, so continue forward
93 // Also short circuit if it is the special cancel action
94 if handled_by.namespace() == "vim"
95 || (handled_by.namespace() == "editor" && handled_by.name() == "Cancel")
96 {
97 return true;
98 }
99 }
100
101 Vim::update(cx, |vim, cx| match vim.active_operator() {
102 Some(Operator::FindForward { .. } | Operator::FindBackward { .. }) => {}
103 Some(_) => {
104 vim.clear_operator(cx);
105 }
106 _ => {}
107 });
108 true
109 })
110 .detach()
111}
112
113#[derive(Default)]
114pub struct Vim {
115 active_editor: Option<WeakViewHandle<Editor>>,
116 editor_subscription: Option<Subscription>,
117
118 enabled: bool,
119 state: VimState,
120}
121
122impl Vim {
123 fn read(cx: &mut MutableAppContext) -> &Self {
124 cx.default_global()
125 }
126
127 fn update<F, S>(cx: &mut MutableAppContext, update: F) -> S
128 where
129 F: FnOnce(&mut Self, &mut MutableAppContext) -> S,
130 {
131 cx.update_default_global(update)
132 }
133
134 fn update_active_editor<S>(
135 &self,
136 cx: &mut MutableAppContext,
137 update: impl FnOnce(&mut Editor, &mut ViewContext<Editor>) -> S,
138 ) -> Option<S> {
139 self.active_editor
140 .clone()
141 .and_then(|ae| ae.upgrade(cx))
142 .map(|ae| ae.update(cx, update))
143 }
144
145 fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut MutableAppContext) {
146 self.state.mode = mode;
147 self.state.operator_stack.clear();
148
149 // Sync editor settings like clip mode
150 self.sync_vim_settings(cx);
151
152 if leave_selections {
153 return;
154 }
155
156 // Adjust selections
157 if let Some(editor) = self
158 .active_editor
159 .as_ref()
160 .and_then(|editor| editor.upgrade(cx))
161 {
162 editor.update(cx, |editor, cx| {
163 editor.change_selections(None, cx, |s| {
164 s.move_with(|map, selection| {
165 if self.state.empty_selections_only() {
166 let new_head = map.clip_point(selection.head(), Bias::Left);
167 selection.collapse_to(new_head, selection.goal)
168 } else {
169 selection.set_head(
170 map.clip_point(selection.head(), Bias::Left),
171 selection.goal,
172 );
173 }
174 });
175 })
176 })
177 }
178 }
179
180 fn push_operator(&mut self, operator: Operator, cx: &mut MutableAppContext) {
181 self.state.operator_stack.push(operator);
182 self.sync_vim_settings(cx);
183 }
184
185 fn push_number(&mut self, Number(number): &Number, cx: &mut MutableAppContext) {
186 if let Some(Operator::Number(current_number)) = self.active_operator() {
187 self.pop_operator(cx);
188 self.push_operator(Operator::Number(current_number * 10 + *number as usize), cx);
189 } else {
190 self.push_operator(Operator::Number(*number as usize), cx);
191 }
192 }
193
194 fn pop_operator(&mut self, cx: &mut MutableAppContext) -> Operator {
195 let popped_operator = self.state.operator_stack.pop()
196 .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
197 self.sync_vim_settings(cx);
198 popped_operator
199 }
200
201 fn pop_number_operator(&mut self, cx: &mut MutableAppContext) -> usize {
202 let mut times = 1;
203 if let Some(Operator::Number(number)) = self.active_operator() {
204 times = number;
205 self.pop_operator(cx);
206 }
207 times
208 }
209
210 fn clear_operator(&mut self, cx: &mut MutableAppContext) {
211 self.state.operator_stack.clear();
212 self.sync_vim_settings(cx);
213 }
214
215 fn active_operator(&self) -> Option<Operator> {
216 self.state.operator_stack.last().copied()
217 }
218
219 fn active_editor_input_ignored(text: Arc<str>, cx: &mut MutableAppContext) {
220 if text.is_empty() {
221 return;
222 }
223
224 match Vim::read(cx).active_operator() {
225 Some(Operator::FindForward { before }) => {
226 motion::motion(Motion::FindForward { before, text }, cx)
227 }
228 Some(Operator::FindBackward { after }) => {
229 motion::motion(Motion::FindBackward { after, text }, cx)
230 }
231 Some(Operator::Replace) => match Vim::read(cx).state.mode {
232 Mode::Normal => normal_replace(text, cx),
233 Mode::Visual { line } => visual_replace(text, line, cx),
234 _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
235 },
236 _ => {}
237 }
238 }
239
240 fn set_enabled(&mut self, enabled: bool, cx: &mut MutableAppContext) {
241 if self.enabled != enabled {
242 self.enabled = enabled;
243 self.state = Default::default();
244 if enabled {
245 self.switch_mode(Mode::Normal, false, cx);
246 }
247 self.sync_vim_settings(cx);
248 }
249 }
250
251 fn sync_vim_settings(&self, cx: &mut MutableAppContext) {
252 let state = &self.state;
253 let cursor_shape = state.cursor_shape();
254
255 cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
256 if self.enabled {
257 filter.filtered_namespaces.remove("vim");
258 } else {
259 filter.filtered_namespaces.insert("vim");
260 }
261 });
262
263 if let Some(editor) = self
264 .active_editor
265 .as_ref()
266 .and_then(|editor| editor.upgrade(cx))
267 {
268 if self.enabled && editor.read(cx).mode() == EditorMode::Full {
269 editor.update(cx, |editor, cx| {
270 editor.set_cursor_shape(cursor_shape, cx);
271 editor.set_clip_at_line_ends(state.clip_at_line_end(), cx);
272 editor.set_input_enabled(!state.vim_controlled());
273 editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
274 let context_layer = state.keymap_context_layer();
275 editor.set_keymap_context_layer::<Self>(context_layer);
276 });
277 } else {
278 self.unhook_vim_settings(editor, cx);
279 }
280 }
281 }
282
283 fn unhook_vim_settings(&self, editor: ViewHandle<Editor>, cx: &mut MutableAppContext) {
284 editor.update(cx, |editor, cx| {
285 editor.set_cursor_shape(CursorShape::Bar, cx);
286 editor.set_clip_at_line_ends(false, cx);
287 editor.set_input_enabled(true);
288 editor.selections.line_mode = false;
289 editor.remove_keymap_context_layer::<Self>();
290 });
291 }
292}