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.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}