duplicate.rs

  1use std::ops::Range;
  2
  3use editor::{DisplayPoint, display_map::DisplaySnapshot};
  4use gpui::Context;
  5use text::Bias;
  6use ui::Window;
  7
  8use crate::Vim;
  9
 10impl Vim {
 11    /// Creates a duplicate of every selection below it in the first place that has both its start
 12    /// and end
 13    pub(super) fn helix_duplicate_selections_below(
 14        &mut self,
 15        times: Option<usize>,
 16        window: &mut Window,
 17        cx: &mut Context<Self>,
 18    ) {
 19        self.duplicate_selections(
 20            times,
 21            window,
 22            cx,
 23            |prev_point| *prev_point.row_mut() += 1,
 24            |prev_range, map| prev_range.end.row() >= map.max_point().row(),
 25            false,
 26        );
 27    }
 28
 29    /// Creates a duplicate of every selection above it in the first place that has both its start
 30    /// and end
 31    pub(super) fn helix_duplicate_selections_above(
 32        &mut self,
 33        times: Option<usize>,
 34        window: &mut Window,
 35        cx: &mut Context<Self>,
 36    ) {
 37        self.duplicate_selections(
 38            times,
 39            window,
 40            cx,
 41            |prev_point| *prev_point.row_mut() = prev_point.row().0.saturating_sub(1),
 42            |prev_range, _| prev_range.start.row() == DisplayPoint::zero().row(),
 43            true,
 44        );
 45    }
 46
 47    fn duplicate_selections(
 48        &mut self,
 49        times: Option<usize>,
 50        window: &mut Window,
 51        cx: &mut Context<Self>,
 52        advance_search: impl Fn(&mut DisplayPoint),
 53        end_search: impl Fn(&Range<DisplayPoint>, &DisplaySnapshot) -> bool,
 54        above: bool,
 55    ) {
 56        let times = times.unwrap_or(1);
 57        self.update_editor(cx, |_, editor, cx| {
 58            let mut selections = Vec::new();
 59            let (map, mut original_selections) = editor.selections.all_display(cx);
 60            // The order matters, because it is recorded when the selections are added.
 61            if above {
 62                original_selections.reverse();
 63            }
 64
 65            for origin in original_selections {
 66                let origin = origin.tail()..origin.head();
 67                selections.push(display_point_range_to_offset_range(&origin, &map));
 68                let mut last_origin = origin;
 69                for _ in 1..=times {
 70                    if let Some(duplicate) = find_next_valid_duplicate_space(
 71                        last_origin.clone(),
 72                        &map,
 73                        &advance_search,
 74                        &end_search,
 75                    ) {
 76                        selections.push(display_point_range_to_offset_range(&duplicate, &map));
 77                        last_origin = duplicate;
 78                    } else {
 79                        break;
 80                    }
 81                }
 82            }
 83
 84            editor.change_selections(Default::default(), window, cx, |s| {
 85                s.select_ranges(selections);
 86            });
 87        });
 88    }
 89}
 90
 91fn find_next_valid_duplicate_space(
 92    mut origin: Range<DisplayPoint>,
 93    map: &DisplaySnapshot,
 94    advance_search: &impl Fn(&mut DisplayPoint),
 95    end_search: &impl Fn(&Range<DisplayPoint>, &DisplaySnapshot) -> bool,
 96) -> Option<Range<DisplayPoint>> {
 97    while !end_search(&origin, map) {
 98        advance_search(&mut origin.start);
 99        advance_search(&mut origin.end);
100
101        if map.clip_point(origin.start, Bias::Left) == origin.start
102            && map.clip_point(origin.end, Bias::Right) == origin.end
103        {
104            return Some(origin);
105        }
106    }
107    None
108}
109
110fn display_point_range_to_offset_range(
111    range: &Range<DisplayPoint>,
112    map: &DisplaySnapshot,
113) -> Range<usize> {
114    range.start.to_offset(map, Bias::Left)..range.end.to_offset(map, Bias::Right)
115}
116
117#[cfg(test)]
118mod tests {
119    use db::indoc;
120
121    use crate::{state::Mode, test::VimTestContext};
122
123    #[gpui::test]
124    async fn test_selection_duplication(cx: &mut gpui::TestAppContext) {
125        let mut cx = VimTestContext::new(cx, true).await;
126        cx.enable_helix();
127
128        cx.set_state(
129            indoc! {"
130            The quick brown
131            fox «jumpsˇ»
132            over the
133            lazy dog."},
134            Mode::HelixNormal,
135        );
136
137        cx.simulate_keystrokes("C");
138
139        cx.assert_state(
140            indoc! {"
141            The quick brown
142            fox «jumpsˇ»
143            over the
144            lazy« dog.ˇ»"},
145            Mode::HelixNormal,
146        );
147
148        cx.simulate_keystrokes("C");
149
150        cx.assert_state(
151            indoc! {"
152            The quick brown
153            fox «jumpsˇ»
154            over the
155            lazy« dog.ˇ»"},
156            Mode::HelixNormal,
157        );
158
159        cx.simulate_keystrokes("alt-C");
160
161        cx.assert_state(
162            indoc! {"
163            The «quickˇ» brown
164            fox «jumpsˇ»
165            over the
166            lazy« dog.ˇ»"},
167            Mode::HelixNormal,
168        );
169
170        cx.simulate_keystrokes(",");
171
172        cx.assert_state(
173            indoc! {"
174            The «quickˇ» brown
175            fox jumps
176            over the
177            lazy dog."},
178            Mode::HelixNormal,
179        );
180    }
181
182    #[gpui::test]
183    async fn test_selection_duplication_backwards(cx: &mut gpui::TestAppContext) {
184        let mut cx = VimTestContext::new(cx, true).await;
185        cx.enable_helix();
186
187        cx.set_state(
188            indoc! {"
189            The quick brown
190            «ˇfox» jumps
191            over the
192            lazy dog."},
193            Mode::HelixNormal,
194        );
195
196        cx.simulate_keystrokes("C C alt-C");
197
198        cx.assert_state(
199            indoc! {"
200            «ˇThe» quick brown
201            «ˇfox» jumps
202            «ˇove»r the
203            «ˇlaz»y dog."},
204            Mode::HelixNormal,
205        );
206    }
207
208    #[gpui::test]
209    async fn test_selection_duplication_count(cx: &mut gpui::TestAppContext) {
210        let mut cx = VimTestContext::new(cx, true).await;
211        cx.enable_helix();
212
213        cx.set_state(
214            indoc! {"
215            The «qˇ»uick brown
216            fox jumps
217            over the
218            lazy dog."},
219            Mode::HelixNormal,
220        );
221
222        cx.simulate_keystrokes("9 C");
223
224        cx.assert_state(
225            indoc! {"
226            The «qˇ»uick brown
227            fox «jˇ»umps
228            over« ˇ»the
229            lazy« ˇ»dog."},
230            Mode::HelixNormal,
231        );
232    }
233}