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 = editor.display_snapshot(cx);
 60            let mut original_selections = editor.selections.all_display(&map);
 61            // The order matters, because it is recorded when the selections are added.
 62            if above {
 63                original_selections.reverse();
 64            }
 65
 66            for origin in original_selections {
 67                let origin = origin.tail()..origin.head();
 68                selections.push(display_point_range_to_offset_range(&origin, &map));
 69                let mut last_origin = origin;
 70                for _ in 1..=times {
 71                    if let Some(duplicate) = find_next_valid_duplicate_space(
 72                        last_origin.clone(),
 73                        &map,
 74                        &advance_search,
 75                        &end_search,
 76                    ) {
 77                        selections.push(display_point_range_to_offset_range(&duplicate, &map));
 78                        last_origin = duplicate;
 79                    } else {
 80                        break;
 81                    }
 82                }
 83            }
 84
 85            editor.change_selections(Default::default(), window, cx, |s| {
 86                s.select_ranges(selections);
 87            });
 88        });
 89    }
 90}
 91
 92fn find_next_valid_duplicate_space(
 93    mut origin: Range<DisplayPoint>,
 94    map: &DisplaySnapshot,
 95    advance_search: &impl Fn(&mut DisplayPoint),
 96    end_search: &impl Fn(&Range<DisplayPoint>, &DisplaySnapshot) -> bool,
 97) -> Option<Range<DisplayPoint>> {
 98    while !end_search(&origin, map) {
 99        advance_search(&mut origin.start);
100        advance_search(&mut origin.end);
101
102        if map.clip_point(origin.start, Bias::Left) == origin.start
103            && map.clip_point(origin.end, Bias::Right) == origin.end
104        {
105            return Some(origin);
106        }
107    }
108    None
109}
110
111fn display_point_range_to_offset_range(
112    range: &Range<DisplayPoint>,
113    map: &DisplaySnapshot,
114) -> Range<usize> {
115    range.start.to_offset(map, Bias::Left)..range.end.to_offset(map, Bias::Right)
116}
117
118#[cfg(test)]
119mod tests {
120    use db::indoc;
121
122    use crate::{state::Mode, test::VimTestContext};
123
124    #[gpui::test]
125    async fn test_selection_duplication(cx: &mut gpui::TestAppContext) {
126        let mut cx = VimTestContext::new(cx, true).await;
127        cx.enable_helix();
128
129        cx.set_state(
130            indoc! {"
131            The quick brown
132            fox «jumpsˇ»
133            over the
134            lazy dog."},
135            Mode::HelixNormal,
136        );
137
138        cx.simulate_keystrokes("C");
139
140        cx.assert_state(
141            indoc! {"
142            The quick brown
143            fox «jumpsˇ»
144            over the
145            lazy« dog.ˇ»"},
146            Mode::HelixNormal,
147        );
148
149        cx.simulate_keystrokes("C");
150
151        cx.assert_state(
152            indoc! {"
153            The quick brown
154            fox «jumpsˇ»
155            over the
156            lazy« dog.ˇ»"},
157            Mode::HelixNormal,
158        );
159
160        cx.simulate_keystrokes("alt-C");
161
162        cx.assert_state(
163            indoc! {"
164            The «quickˇ» brown
165            fox «jumpsˇ»
166            over the
167            lazy« dog.ˇ»"},
168            Mode::HelixNormal,
169        );
170
171        cx.simulate_keystrokes(",");
172
173        cx.assert_state(
174            indoc! {"
175            The «quickˇ» brown
176            fox jumps
177            over the
178            lazy dog."},
179            Mode::HelixNormal,
180        );
181    }
182
183    #[gpui::test]
184    async fn test_selection_duplication_backwards(cx: &mut gpui::TestAppContext) {
185        let mut cx = VimTestContext::new(cx, true).await;
186        cx.enable_helix();
187
188        cx.set_state(
189            indoc! {"
190            The quick brown
191            «ˇfox» jumps
192            over the
193            lazy dog."},
194            Mode::HelixNormal,
195        );
196
197        cx.simulate_keystrokes("C C alt-C");
198
199        cx.assert_state(
200            indoc! {"
201            «ˇThe» quick brown
202            «ˇfox» jumps
203            «ˇove»r the
204            «ˇlaz»y dog."},
205            Mode::HelixNormal,
206        );
207    }
208
209    #[gpui::test]
210    async fn test_selection_duplication_count(cx: &mut gpui::TestAppContext) {
211        let mut cx = VimTestContext::new(cx, true).await;
212        cx.enable_helix();
213
214        cx.set_state(
215            indoc! {"
216            The «qˇ»uick brown
217            fox jumps
218            over the
219            lazy dog."},
220            Mode::HelixNormal,
221        );
222
223        cx.simulate_keystrokes("9 C");
224
225        cx.assert_state(
226            indoc! {"
227            The «qˇ»uick brown
228            fox «jˇ»umps
229            over« ˇ»the
230            lazy« ˇ»dog."},
231            Mode::HelixNormal,
232        );
233    }
234}