visual.rs

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