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}