1use editor::Bias;
2use gpui::{actions, MutableAppContext, ViewContext};
3use workspace::Workspace;
4
5use crate::{motion::Motion, state::Mode, Vim};
6
7actions!(vim, [VisualDelete, VisualChange]);
8
9pub fn init(cx: &mut MutableAppContext) {
10 cx.add_action(change);
11 cx.add_action(delete);
12}
13
14pub fn visual_motion(motion: Motion, cx: &mut MutableAppContext) {
15 Vim::update(cx, |vim, cx| {
16 vim.update_active_editor(cx, |editor, cx| {
17 editor.move_selections(cx, |map, selection| {
18 let (new_head, goal) = motion.move_point(map, selection.head(), selection.goal);
19 let new_head = map.clip_at_line_end(new_head);
20 let was_reversed = selection.reversed;
21 selection.set_head(new_head, goal);
22
23 if was_reversed && !selection.reversed {
24 // Head was at the start of the selection, and now is at the end. We need to move the start
25 // back by one if possible in order to compensate for this change.
26 *selection.start.column_mut() = selection.start.column().saturating_sub(1);
27 selection.start = map.clip_point(selection.start, Bias::Left);
28 } else if !was_reversed && selection.reversed {
29 // Head was at the end of the selection, and now is at the start. We need to move the end
30 // forward by one if possible in order to compensate for this change.
31 *selection.end.column_mut() = selection.end.column() + 1;
32 selection.end = map.clip_point(selection.end, Bias::Left);
33 }
34 });
35 });
36 });
37}
38
39pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspace>) {
40 Vim::update(cx, |vim, cx| {
41 vim.update_active_editor(cx, |editor, cx| {
42 editor.set_clip_at_line_ends(false, cx);
43 editor.move_selections(cx, |map, selection| {
44 if !selection.reversed {
45 // Head was at the end of the selection, and now is at the start. We need to move the end
46 // forward by one if possible in order to compensate for this change.
47 *selection.end.column_mut() = selection.end.column() + 1;
48 selection.end = map.clip_point(selection.end, Bias::Left);
49 }
50 });
51 editor.insert("", cx);
52 });
53 vim.switch_mode(Mode::Insert, cx);
54 });
55}
56
57pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspace>) {
58 Vim::update(cx, |vim, cx| {
59 vim.switch_mode(Mode::Normal, cx);
60 vim.update_active_editor(cx, |editor, cx| {
61 editor.set_clip_at_line_ends(false, cx);
62 editor.move_selections(cx, |map, selection| {
63 if !selection.reversed {
64 // Head was at the end of the selection, and now is at the start. We need to move the end
65 // forward by one if possible in order to compensate for this change.
66 *selection.end.column_mut() = selection.end.column() + 1;
67 selection.end = map.clip_point(selection.end, Bias::Left);
68 }
69 });
70 editor.insert("", cx);
71
72 // Fixup cursor position after the deletion
73 editor.set_clip_at_line_ends(true, cx);
74 editor.move_selections(cx, |map, selection| {
75 let mut cursor = selection.head();
76 cursor = map.clip_point(cursor, Bias::Left);
77 selection.collapse_to(cursor, selection.goal)
78 });
79 });
80 });
81}
82
83#[cfg(test)]
84mod test {
85 use indoc::indoc;
86
87 use crate::{state::Mode, vim_test_context::VimTestContext};
88
89 #[gpui::test]
90 async fn test_enter_visual_mode(cx: &mut gpui::TestAppContext) {
91 let cx = VimTestContext::new(cx, true).await;
92 let mut cx = cx.binding(["v", "w", "j"]).mode_after(Mode::Visual);
93 cx.assert(
94 indoc! {"
95 The |quick brown
96 fox jumps over
97 the lazy dog"},
98 indoc! {"
99 The [quick brown
100 fox jumps }over
101 the lazy dog"},
102 );
103 cx.assert(
104 indoc! {"
105 The quick brown
106 fox jumps over
107 the |lazy dog"},
108 indoc! {"
109 The quick brown
110 fox jumps over
111 the [lazy }dog"},
112 );
113 cx.assert(
114 indoc! {"
115 The quick brown
116 fox jumps |over
117 the lazy dog"},
118 indoc! {"
119 The quick brown
120 fox jumps [over
121 }the lazy dog"},
122 );
123 let mut cx = cx.binding(["v", "b", "k"]).mode_after(Mode::Visual);
124 cx.assert(
125 indoc! {"
126 The |quick brown
127 fox jumps over
128 the lazy dog"},
129 indoc! {"
130 {The q]uick brown
131 fox jumps over
132 the lazy dog"},
133 );
134 cx.assert(
135 indoc! {"
136 The quick brown
137 fox jumps over
138 the |lazy dog"},
139 indoc! {"
140 The quick brown
141 {fox jumps over
142 the l]azy dog"},
143 );
144 cx.assert(
145 indoc! {"
146 The quick brown
147 fox jumps |over
148 the lazy dog"},
149 indoc! {"
150 The {quick brown
151 fox jumps o]ver
152 the lazy dog"},
153 );
154 }
155
156 #[gpui::test]
157 async fn test_visual_delete(cx: &mut gpui::TestAppContext) {
158 let cx = VimTestContext::new(cx, true).await;
159 let mut cx = cx.binding(["v", "w", "x"]);
160 cx.assert("The quick |brown", "The quick| ");
161 let mut cx = cx.binding(["v", "w", "j", "x"]);
162 cx.assert(
163 indoc! {"
164 The |quick brown
165 fox jumps over
166 the lazy dog"},
167 indoc! {"
168 The |ver
169 the lazy dog"},
170 );
171 cx.assert(
172 indoc! {"
173 The quick brown
174 fox jumps over
175 the |lazy dog"},
176 indoc! {"
177 The quick brown
178 fox jumps over
179 the |og"},
180 );
181 cx.assert(
182 indoc! {"
183 The quick brown
184 fox jumps |over
185 the lazy dog"},
186 indoc! {"
187 The quick brown
188 fox jumps |he lazy dog"},
189 );
190 let mut cx = cx.binding(["v", "b", "k", "x"]);
191 cx.assert(
192 indoc! {"
193 The |quick brown
194 fox jumps over
195 the lazy dog"},
196 indoc! {"
197 |uick brown
198 fox jumps over
199 the lazy dog"},
200 );
201 cx.assert(
202 indoc! {"
203 The quick brown
204 fox jumps over
205 the |lazy dog"},
206 indoc! {"
207 The quick brown
208 |azy dog"},
209 );
210 cx.assert(
211 indoc! {"
212 The quick brown
213 fox jumps |over
214 the lazy dog"},
215 indoc! {"
216 The |ver
217 the lazy dog"},
218 );
219 }
220
221 #[gpui::test]
222 async fn test_visual_change(cx: &mut gpui::TestAppContext) {
223 let cx = VimTestContext::new(cx, true).await;
224 let mut cx = cx.binding(["v", "w", "c"]).mode_after(Mode::Insert);
225 cx.assert("The quick |brown", "The quick |");
226 let mut cx = cx.binding(["v", "w", "j", "c"]).mode_after(Mode::Insert);
227 cx.assert(
228 indoc! {"
229 The |quick brown
230 fox jumps over
231 the lazy dog"},
232 indoc! {"
233 The |ver
234 the lazy dog"},
235 );
236 cx.assert(
237 indoc! {"
238 The quick brown
239 fox jumps over
240 the |lazy dog"},
241 indoc! {"
242 The quick brown
243 fox jumps over
244 the |og"},
245 );
246 cx.assert(
247 indoc! {"
248 The quick brown
249 fox jumps |over
250 the lazy dog"},
251 indoc! {"
252 The quick brown
253 fox jumps |he lazy dog"},
254 );
255 let mut cx = cx.binding(["v", "b", "k", "c"]).mode_after(Mode::Insert);
256 cx.assert(
257 indoc! {"
258 The |quick brown
259 fox jumps over
260 the lazy dog"},
261 indoc! {"
262 |uick brown
263 fox jumps over
264 the lazy dog"},
265 );
266 cx.assert(
267 indoc! {"
268 The quick brown
269 fox jumps over
270 the |lazy dog"},
271 indoc! {"
272 The quick brown
273 |azy dog"},
274 );
275 cx.assert(
276 indoc! {"
277 The quick brown
278 fox jumps |over
279 the lazy dog"},
280 indoc! {"
281 The |ver
282 the lazy dog"},
283 );
284 }
285}