visual.rs

  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}