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.update_active_editor(cx, |editor, cx| {
60 editor.set_clip_at_line_ends(false, cx);
61 editor.move_selections(cx, |map, selection| {
62 if !selection.reversed {
63 // Head was at the end of the selection, and now is at the start. We need to move the end
64 // forward by one if possible in order to compensate for this change.
65 *selection.end.column_mut() = selection.end.column() + 1;
66 selection.end = map.clip_point(selection.end, Bias::Left);
67 }
68 });
69 editor.insert("", cx);
70 });
71 vim.switch_mode(Mode::Normal, cx);
72 });
73}
74
75#[cfg(test)]
76mod test {
77 use indoc::indoc;
78
79 use crate::{state::Mode, vim_test_context::VimTestContext};
80
81 #[gpui::test]
82 async fn test_enter_visual_mode(cx: &mut gpui::TestAppContext) {
83 let cx = VimTestContext::new(cx, true).await;
84 let mut cx = cx.binding(["v", "w", "j"]).mode_after(Mode::Visual);
85 cx.assert(
86 indoc! {"
87 The |quick brown
88 fox jumps over
89 the lazy dog"},
90 indoc! {"
91 The [quick brown
92 fox jumps }over
93 the lazy dog"},
94 );
95 cx.assert(
96 indoc! {"
97 The quick brown
98 fox jumps over
99 the |lazy dog"},
100 indoc! {"
101 The quick brown
102 fox jumps over
103 the [lazy }dog"},
104 );
105 cx.assert(
106 indoc! {"
107 The quick brown
108 fox jumps |over
109 the lazy dog"},
110 indoc! {"
111 The quick brown
112 fox jumps [over
113 }the lazy dog"},
114 );
115 let mut cx = cx.binding(["v", "b", "k"]).mode_after(Mode::Visual);
116 cx.assert(
117 indoc! {"
118 The |quick brown
119 fox jumps over
120 the lazy dog"},
121 indoc! {"
122 {The q]uick brown
123 fox jumps over
124 the lazy dog"},
125 );
126 cx.assert(
127 indoc! {"
128 The quick brown
129 fox jumps over
130 the |lazy dog"},
131 indoc! {"
132 The quick brown
133 {fox jumps over
134 the l]azy dog"},
135 );
136 cx.assert(
137 indoc! {"
138 The quick brown
139 fox jumps |over
140 the lazy dog"},
141 indoc! {"
142 The {quick brown
143 fox jumps o]ver
144 the lazy dog"},
145 );
146 }
147
148 #[gpui::test]
149 async fn test_visual_delete(cx: &mut gpui::TestAppContext) {
150 let cx = VimTestContext::new(cx, true).await;
151 let mut cx = cx.binding(["v", "w", "x"]);
152 cx.assert("The quick |brown", "The quick| ");
153 let mut cx = cx.binding(["v", "w", "j", "x"]);
154 cx.assert(
155 indoc! {"
156 The |quick brown
157 fox jumps over
158 the lazy dog"},
159 indoc! {"
160 The |ver
161 the lazy dog"},
162 );
163 cx.assert(
164 indoc! {"
165 The quick brown
166 fox jumps over
167 the |lazy dog"},
168 indoc! {"
169 The quick brown
170 fox jumps over
171 the |og"},
172 );
173 cx.assert(
174 indoc! {"
175 The quick brown
176 fox jumps |over
177 the lazy dog"},
178 indoc! {"
179 The quick brown
180 fox jumps |he lazy dog"},
181 );
182 let mut cx = cx.binding(["v", "b", "k", "x"]);
183 cx.assert(
184 indoc! {"
185 The |quick brown
186 fox jumps over
187 the lazy dog"},
188 indoc! {"
189 |uick brown
190 fox jumps over
191 the lazy dog"},
192 );
193 cx.assert(
194 indoc! {"
195 The quick brown
196 fox jumps over
197 the |lazy dog"},
198 indoc! {"
199 The quick brown
200 |azy dog"},
201 );
202 cx.assert(
203 indoc! {"
204 The quick brown
205 fox jumps |over
206 the lazy dog"},
207 indoc! {"
208 The |ver
209 the lazy dog"},
210 );
211 }
212
213 #[gpui::test]
214 async fn test_visual_change(cx: &mut gpui::TestAppContext) {
215 let cx = VimTestContext::new(cx, true).await;
216 let mut cx = cx.binding(["v", "w", "x"]).mode_after(Mode::Insert);
217 cx.assert("The quick |brown", "The quick |");
218 let mut cx = cx.binding(["v", "w", "j", "c"]).mode_after(Mode::Insert);
219 cx.assert(
220 indoc! {"
221 The |quick brown
222 fox jumps over
223 the lazy dog"},
224 indoc! {"
225 The |ver
226 the lazy dog"},
227 );
228 cx.assert(
229 indoc! {"
230 The quick brown
231 fox jumps over
232 the |lazy dog"},
233 indoc! {"
234 The quick brown
235 fox jumps over
236 the |og"},
237 );
238 cx.assert(
239 indoc! {"
240 The quick brown
241 fox jumps |over
242 the lazy dog"},
243 indoc! {"
244 The quick brown
245 fox jumps |he lazy dog"},
246 );
247 let mut cx = cx.binding(["v", "b", "k", "c"]).mode_after(Mode::Insert);
248 cx.assert(
249 indoc! {"
250 The |quick brown
251 fox jumps over
252 the lazy dog"},
253 indoc! {"
254 |uick brown
255 fox jumps over
256 the lazy dog"},
257 );
258 cx.assert(
259 indoc! {"
260 The quick brown
261 fox jumps over
262 the |lazy dog"},
263 indoc! {"
264 The quick brown
265 |azy dog"},
266 );
267 cx.assert(
268 indoc! {"
269 The quick brown
270 fox jumps |over
271 the lazy dog"},
272 indoc! {"
273 The |ver
274 the lazy dog"},
275 );
276 }
277}