1use crate::{Vim, motion::Motion, object::Object, state::Mode};
2use collections::HashMap;
3use editor::SelectionEffects;
4use editor::{Bias, Editor, display_map::ToDisplayPoint};
5use gpui::actions;
6use gpui::{Context, Window};
7use language::SelectionGoal;
8
9#[derive(PartialEq, Eq)]
10pub(crate) enum IndentDirection {
11 In,
12 Out,
13 Auto,
14}
15
16actions!(vim, [Indent, Outdent, AutoIndent]);
17
18pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
19 Vim::action(editor, cx, |vim, _: &Indent, window, cx| {
20 vim.record_current_action(cx);
21 let count = Vim::take_count(cx).unwrap_or(1);
22 Vim::take_forced_motion(cx);
23 vim.store_visual_marks(window, cx);
24 vim.update_editor(window, cx, |vim, editor, window, cx| {
25 editor.transact(window, cx, |editor, window, cx| {
26 let original_positions = vim.save_selection_starts(editor, cx);
27 for _ in 0..count {
28 editor.indent(&Default::default(), window, cx);
29 }
30 vim.restore_selection_cursors(editor, window, cx, original_positions);
31 });
32 });
33 if vim.mode.is_visual() {
34 vim.switch_mode(Mode::Normal, true, window, cx)
35 }
36 });
37
38 Vim::action(editor, cx, |vim, _: &Outdent, window, cx| {
39 vim.record_current_action(cx);
40 let count = Vim::take_count(cx).unwrap_or(1);
41 Vim::take_forced_motion(cx);
42 vim.store_visual_marks(window, cx);
43 vim.update_editor(window, cx, |vim, editor, window, cx| {
44 editor.transact(window, cx, |editor, window, cx| {
45 let original_positions = vim.save_selection_starts(editor, cx);
46 for _ in 0..count {
47 editor.outdent(&Default::default(), window, cx);
48 }
49 vim.restore_selection_cursors(editor, window, cx, original_positions);
50 });
51 });
52 if vim.mode.is_visual() {
53 vim.switch_mode(Mode::Normal, true, window, cx)
54 }
55 });
56
57 Vim::action(editor, cx, |vim, _: &AutoIndent, window, cx| {
58 vim.record_current_action(cx);
59 let count = Vim::take_count(cx).unwrap_or(1);
60 Vim::take_forced_motion(cx);
61 vim.store_visual_marks(window, cx);
62 vim.update_editor(window, cx, |vim, editor, window, cx| {
63 editor.transact(window, cx, |editor, window, cx| {
64 let original_positions = vim.save_selection_starts(editor, cx);
65 for _ in 0..count {
66 editor.autoindent(&Default::default(), window, cx);
67 }
68 vim.restore_selection_cursors(editor, window, cx, original_positions);
69 });
70 });
71 if vim.mode.is_visual() {
72 vim.switch_mode(Mode::Normal, true, window, cx)
73 }
74 });
75}
76
77impl Vim {
78 pub(crate) fn indent_motion(
79 &mut self,
80 motion: Motion,
81 times: Option<usize>,
82 forced_motion: bool,
83 dir: IndentDirection,
84 window: &mut Window,
85 cx: &mut Context<Self>,
86 ) {
87 self.stop_recording(cx);
88 self.update_editor(window, cx, |_, editor, window, cx| {
89 let text_layout_details = editor.text_layout_details(window);
90 editor.transact(window, cx, |editor, window, cx| {
91 let mut selection_starts: HashMap<_, _> = Default::default();
92 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
93 s.move_with(|map, selection| {
94 let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
95 selection_starts.insert(selection.id, anchor);
96 motion.expand_selection(
97 map,
98 selection,
99 times,
100 &text_layout_details,
101 forced_motion,
102 );
103 });
104 });
105 match dir {
106 IndentDirection::In => editor.indent(&Default::default(), window, cx),
107 IndentDirection::Out => editor.outdent(&Default::default(), window, cx),
108 IndentDirection::Auto => editor.autoindent(&Default::default(), window, cx),
109 }
110 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
111 s.move_with(|map, selection| {
112 let anchor = selection_starts.remove(&selection.id).unwrap();
113 selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
114 });
115 });
116 });
117 });
118 }
119
120 pub(crate) fn indent_object(
121 &mut self,
122 object: Object,
123 around: bool,
124 dir: IndentDirection,
125 window: &mut Window,
126 cx: &mut Context<Self>,
127 ) {
128 self.stop_recording(cx);
129 self.update_editor(window, cx, |_, editor, window, cx| {
130 editor.transact(window, cx, |editor, window, cx| {
131 let mut original_positions: HashMap<_, _> = Default::default();
132 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
133 s.move_with(|map, selection| {
134 let anchor = map.display_point_to_anchor(selection.head(), Bias::Right);
135 original_positions.insert(selection.id, anchor);
136 object.expand_selection(map, selection, around);
137 });
138 });
139 match dir {
140 IndentDirection::In => editor.indent(&Default::default(), window, cx),
141 IndentDirection::Out => editor.outdent(&Default::default(), window, cx),
142 IndentDirection::Auto => editor.autoindent(&Default::default(), window, cx),
143 }
144 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
145 s.move_with(|map, selection| {
146 let anchor = original_positions.remove(&selection.id).unwrap();
147 selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None);
148 });
149 });
150 });
151 });
152 }
153}
154
155#[cfg(test)]
156mod test {
157 use crate::{
158 state::Mode,
159 test::{NeovimBackedTestContext, VimTestContext},
160 };
161 use indoc::indoc;
162
163 #[gpui::test]
164 async fn test_indent_gv(cx: &mut gpui::TestAppContext) {
165 let mut cx = NeovimBackedTestContext::new(cx).await;
166 cx.set_neovim_option("shiftwidth=4").await;
167
168 cx.set_shared_state("ˇhello\nworld\n").await;
169 cx.simulate_shared_keystrokes("v j > g v").await;
170 cx.shared_state()
171 .await
172 .assert_eq("« hello\n ˇ» world\n");
173 }
174
175 #[gpui::test]
176 async fn test_autoindent_op(cx: &mut gpui::TestAppContext) {
177 let mut cx = VimTestContext::new(cx, true).await;
178
179 cx.set_state(
180 indoc!(
181 "
182 fn a() {
183 b();
184 c();
185
186 d();
187 ˇe();
188 f();
189
190 g();
191 }
192 "
193 ),
194 Mode::Normal,
195 );
196
197 cx.simulate_keystrokes("= a p");
198 cx.assert_state(
199 indoc!(
200 "
201 fn a() {
202 b();
203 c();
204
205 d();
206 ˇe();
207 f();
208
209 g();
210 }
211 "
212 ),
213 Mode::Normal,
214 );
215 }
216}