on master help sign in
Add command to merge consecutive ranges in selection (#5047)
commit1107296ca9bfd728258a4571be35eb7c811ff3b3 authorDylanBulfin <31492860+DylanBulfin@users.noreply.github.com> dateThu 22 Dec 2022, 21:27 (4 years ago) parent1b89d3e
4 files changed+92−12
book/src/keymap.md+1 helix-core/src/selection.rs+83−12 helix-term/src/commands.rs+7 helix-term/src/keymap/default.rs+1
book/src/keymap.md+1
@@ -111,6 +111,7 @@
111111| `s` | Select all regex matches inside selections | `select_regex` |
112112| `S` | Split selection into subselections on regex matches | `split_selection` |
113113| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
114+| `Alt-_ ` | Merge consecutive selections | `merge_consecutive_selections` |
114115| `&` | Align selection in columns | `align_selections` |
115116| `_` | Trim whitespace from the selection | `trim_selections` |
116117| `;` | Collapse selection onto a single cursor | `collapse_selection` |
helix-core/src/selection.rs+83−12
@@ -495,28 +495,53 @@ impl Selection {
495495
496496 /// Normalizes a `Selection`.
497497 fn normalize(mut self) -> Self {
498− let primary = self.ranges[self.primary_index];
498+ let mut primary = self.ranges[self.primary_index];
499499 self.ranges.sort_unstable_by_key(Range::from);
500+
501+ self.ranges.dedup_by(|curr_range, prev_range| {
502+ if prev_range.overlaps(curr_range) {
503+ let new_range = curr_range.merge(*prev_range);
504+ if prev_range == &primary || curr_range == &primary {
505+ primary = new_range;
506+ }
507+ *prev_range = new_range;
508+ true
509+ } else {
510+ false
511+ }
512+ });
513+
500514 self.primary_index = self
501515 .ranges
502516 .iter()
503517 .position(|&range| range == primary)
504518 .unwrap();
505519
506− let mut prev_i = 0;
507− for i in 1..self.ranges.len() {
508− if self.ranges[prev_i].overlaps(&self.ranges[i]) {
509− self.ranges[prev_i] = self.ranges[prev_i].merge(self.ranges[i]);
520+ self
521+ }
522+
523+ // Merges all ranges that are consecutive
524+ pub fn merge_consecutive_ranges(mut self) -> Self {
525+ let mut primary = self.ranges[self.primary_index];
526+
527+ self.ranges.dedup_by(|curr_range, prev_range| {
528+ if prev_range.to() == curr_range.from() {
529+ let new_range = curr_range.merge(*prev_range);
530+ if prev_range == &primary || curr_range == &primary {
531+ primary = new_range;
532+ }
533+ *prev_range = new_range;
534+ true
510535 } else {
511− prev_i += 1;
512− self.ranges[prev_i] = self.ranges[i];
536+ false
513537 }
514− if i == self.primary_index {
515− self.primary_index = prev_i;
516− }
517− }
538+ });
518539
519− self.ranges.truncate(prev_i + 1);
540+ self.primary_index = self
541+ .ranges
542+ .iter()
543+ .position(|&range| range == primary)
544+ .unwrap();
520545
521546 self
522547 }
@@ -1132,6 +1157,52 @@ mod test {
11321157 &["", "abcd", "efg", "rs", "xyz"]
11331158 );
11341159 }
1160+
1161+ #[test]
1162+ fn test_merge_consecutive_ranges() {
1163+ let selection = Selection::new(
1164+ smallvec![
1165+ Range::new(0, 1),
1166+ Range::new(1, 10),
1167+ Range::new(15, 20),
1168+ Range::new(25, 26),
1169+ Range::new(26, 30)
1170+ ],
1171+ 4,
1172+ );
1173+
1174+ let result = selection.merge_consecutive_ranges();
1175+
1176+ assert_eq!(
1177+ result.ranges(),
1178+ &[Range::new(0, 10), Range::new(15, 20), Range::new(25, 30)]
1179+ );
1180+ assert_eq!(result.primary_index, 2);
1181+
1182+ let selection = Selection::new(smallvec![Range::new(0, 1)], 0);
1183+ let result = selection.merge_consecutive_ranges();
1184+
1185+ assert_eq!(result.ranges(), &[Range::new(0, 1)]);
1186+ assert_eq!(result.primary_index, 0);
1187+
1188+ let selection = Selection::new(
1189+ smallvec![
1190+ Range::new(0, 1),
1191+ Range::new(1, 5),
1192+ Range::new(5, 8),
1193+ Range::new(8, 10),
1194+ Range::new(10, 15),
1195+ Range::new(18, 25)
1196+ ],
1197+ 3,
1198+ );
1199+
1200+ let result = selection.merge_consecutive_ranges();
1201+
1202+ assert_eq!(result.ranges(), &[Range::new(0, 15), Range::new(18, 25)]);
1203+ assert_eq!(result.primary_index, 0);
1204+ }
1205+
11351206 #[test]
11361207 fn test_selection_contains() {
11371208 fn contains(a: Vec<(usize, usize)>, b: Vec<(usize, usize)>) -> bool {
helix-term/src/commands.rs+7
@@ -244,6 +244,7 @@ impl MappableCommand {
244244 select_regex, "Select all regex matches inside selections",
245245 split_selection, "Split selections on regex matches",
246246 split_selection_on_newline, "Split selection on newlines",
247+ merge_consecutive_selections, "Merge consecutive selections",
247248 search, "Search for regex pattern",
248249 rsearch, "Reverse search for regex pattern",
249250 search_next, "Select next search match",
@@ -1589,6 +1590,12 @@ fn split_selection_on_newline(cx: &mut Context) {
15891590 doc.set_selection(view.id, selection);
15901591}
15911592
1593+fn merge_consecutive_selections(cx: &mut Context) {
1594+ let (view, doc) = current!(cx.editor);
1595+ let selection = doc.selection(view.id).clone().merge_consecutive_ranges();
1596+ doc.set_selection(view.id, selection);
1597+}
1598+
15921599#[allow(clippy::too_many_arguments)]
15931600fn search_impl(
15941601 editor: &mut Editor,
helix-term/src/keymap/default.rs+1
@@ -76,6 +76,7 @@ pub fn default() -> HashMap<Mode, Keymap> {
7676
7777 "s" => select_regex,
7878 "A-s" => split_selection_on_newline,
79+ "A-_" => merge_consecutive_selections,
7980 "S" => split_selection,
8081 ";" => collapse_selection,
8182 "A-;" => flip_selections,