From a0b7fd7a878d5fb33a74cba7216ee74aa2520f13 Mon Sep 17 00:00:00 2001 From: Dylan Bulfin Date: Sun, 4 Dec 2022 18:50:23 -0500 Subject: [PATCH 1/4] Added command Rewrote command, added test Moved keymap Refactoring command, added test coverage Added primary index calculation Moving primary_index calculation Refactored code to use fold instead of loop Changed to use dedup_by Removed duplicate merge call Fix primary index calculation --- helix-core/src/selection.rs | 71 ++++++++++++++++++++++++++++++++ helix-term/src/commands.rs | 7 ++++ helix-term/src/keymap/default.rs | 1 + 3 files changed, 79 insertions(+) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 1f28ecefb750..f6db2bfff91a 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -521,6 +521,31 @@ impl Selection { self } + // Merges all ranges that are consecutive + pub fn merge_consecutive_ranges(mut self) -> Self { + let mut primary = self.ranges[self.primary_index]; + + self.ranges.dedup_by(|curr_range, prev_range| { + if prev_range.to() == curr_range.from() { + if prev_range == &primary || curr_range == &primary { + primary = curr_range.merge(*prev_range); + } + *prev_range = curr_range.merge(*prev_range); + true + } else { + false + } + }); + + self.primary_index = self + .ranges + .iter() + .position(|&range| range == primary) + .unwrap(); + + self + } + // TODO: consume an iterator or a vec to reduce allocations? #[must_use] pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self { @@ -1132,6 +1157,52 @@ mod test { &["", "abcd", "efg", "rs", "xyz"] ); } + + #[test] + fn test_merge_consecutive_ranges() { + let selection = Selection::new( + smallvec![ + Range::new(0, 1), + Range::new(1, 10), + Range::new(15, 20), + Range::new(25, 26), + Range::new(26, 30) + ], + 4, + ); + + let result = selection.merge_consecutive_ranges(); + + assert_eq!( + result.ranges(), + &[Range::new(0, 10), Range::new(15, 20), Range::new(25, 30)] + ); + assert_eq!(result.primary_index, 2); + + let selection = Selection::new(smallvec![Range::new(0, 1)], 0); + let result = selection.merge_consecutive_ranges(); + + assert_eq!(result.ranges(), &[Range::new(0, 1)]); + assert_eq!(result.primary_index, 0); + + let selection = Selection::new( + smallvec![ + Range::new(0, 1), + Range::new(1, 5), + Range::new(5, 8), + Range::new(8, 10), + Range::new(10, 15), + Range::new(18, 25) + ], + 3, + ); + + let result = selection.merge_consecutive_ranges(); + + assert_eq!(result.ranges(), &[Range::new(0, 15), Range::new(18, 25)]); + assert_eq!(result.primary_index, 0); + } + #[test] fn test_selection_contains() { fn contains(a: Vec<(usize, usize)>, b: Vec<(usize, usize)>) -> bool { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 1310417e47be..3c5c48841388 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -244,6 +244,7 @@ impl MappableCommand { select_regex, "Select all regex matches inside selections", split_selection, "Split selections on regex matches", split_selection_on_newline, "Split selection on newlines", + merge_consecutive_ranges, "Merge consecutive ranges", search, "Search for regex pattern", rsearch, "Reverse search for regex pattern", search_next, "Select next search match", @@ -1589,6 +1590,12 @@ fn split_selection_on_newline(cx: &mut Context) { doc.set_selection(view.id, selection); } +fn merge_consecutive_ranges(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let selection = doc.selection(view.id).clone().merge_consecutive_ranges(); + doc.set_selection(view.id, selection); +} + #[allow(clippy::too_many_arguments)] fn search_impl( editor: &mut Editor, diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index ebcd125aa129..f581304471a6 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -76,6 +76,7 @@ pub fn default() -> HashMap { "s" => select_regex, "A-s" => split_selection_on_newline, + "A-_" => merge_consecutive_ranges, "S" => split_selection, ";" => collapse_selection, "A-;" => flip_selections, From dd9535128b8defb593a76cbf1c30a7275136bb77 Mon Sep 17 00:00:00 2001 From: Dylan Bulfin Date: Fri, 16 Dec 2022 10:43:59 -0500 Subject: [PATCH 2/4] Updated normalize implementation --- helix-core/src/selection.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index f6db2bfff91a..39c570ab7f04 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -495,29 +495,28 @@ impl Selection { /// Normalizes a `Selection`. fn normalize(mut self) -> Self { - let primary = self.ranges[self.primary_index]; + let mut primary = self.ranges[self.primary_index]; self.ranges.sort_unstable_by_key(Range::from); + + self.ranges.dedup_by(|curr_range, prev_range| { + if prev_range.overlaps(&curr_range) { + let new_range = curr_range.merge(*prev_range); + if prev_range == &primary || curr_range == &primary { + primary = new_range; + } + *prev_range = new_range; + true + } else { + false + } + }); + self.primary_index = self .ranges .iter() .position(|&range| range == primary) .unwrap(); - let mut prev_i = 0; - for i in 1..self.ranges.len() { - if self.ranges[prev_i].overlaps(&self.ranges[i]) { - self.ranges[prev_i] = self.ranges[prev_i].merge(self.ranges[i]); - } else { - prev_i += 1; - self.ranges[prev_i] = self.ranges[i]; - } - if i == self.primary_index { - self.primary_index = prev_i; - } - } - - self.ranges.truncate(prev_i + 1); - self } @@ -527,10 +526,11 @@ impl Selection { self.ranges.dedup_by(|curr_range, prev_range| { if prev_range.to() == curr_range.from() { + let new_range = curr_range.merge(*prev_range); if prev_range == &primary || curr_range == &primary { - primary = curr_range.merge(*prev_range); + primary = new_range; } - *prev_range = curr_range.merge(*prev_range); + *prev_range = new_range; true } else { false From cffa47838beeb497bbb18a0e752d60cc2c6b1136 Mon Sep 17 00:00:00 2001 From: Dylan Bulfin Date: Fri, 16 Dec 2022 16:19:28 -0500 Subject: [PATCH 3/4] Fixed clippy issue --- helix-core/src/selection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 39c570ab7f04..ffba46ab70ef 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -499,7 +499,7 @@ impl Selection { self.ranges.sort_unstable_by_key(Range::from); self.ranges.dedup_by(|curr_range, prev_range| { - if prev_range.overlaps(&curr_range) { + if prev_range.overlaps(curr_range) { let new_range = curr_range.merge(*prev_range); if prev_range == &primary || curr_range == &primary { primary = new_range; From 5c86ecba2e6bfa203edea51c70743fe282434452 Mon Sep 17 00:00:00 2001 From: Dylan Bulfin Date: Sat, 17 Dec 2022 17:45:07 -0500 Subject: [PATCH 4/4] Added function to book, renamed function --- book/src/keymap.md | 1 + helix-term/src/commands.rs | 4 ++-- helix-term/src/keymap/default.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index 139e8fddf613..f163cc92b40f 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -111,6 +111,7 @@ | `s` | Select all regex matches inside selections | `select_regex` | | `S` | Split selection into subselections on regex matches | `split_selection` | | `Alt-s` | Split selection on newlines | `split_selection_on_newline` | +| `Alt-_ ` | Merge consecutive selections | `merge_consecutive_selections` | | `&` | Align selection in columns | `align_selections` | | `_` | Trim whitespace from the selection | `trim_selections` | | `;` | Collapse selection onto a single cursor | `collapse_selection` | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 3c5c48841388..d125a6c8ada7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -244,7 +244,7 @@ impl MappableCommand { select_regex, "Select all regex matches inside selections", split_selection, "Split selections on regex matches", split_selection_on_newline, "Split selection on newlines", - merge_consecutive_ranges, "Merge consecutive ranges", + merge_consecutive_selections, "Merge consecutive selections", search, "Search for regex pattern", rsearch, "Reverse search for regex pattern", search_next, "Select next search match", @@ -1590,7 +1590,7 @@ fn split_selection_on_newline(cx: &mut Context) { doc.set_selection(view.id, selection); } -fn merge_consecutive_ranges(cx: &mut Context) { +fn merge_consecutive_selections(cx: &mut Context) { let (view, doc) = current!(cx.editor); let selection = doc.selection(view.id).clone().merge_consecutive_ranges(); doc.set_selection(view.id, selection); diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index f581304471a6..ef93dee08a77 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -76,7 +76,7 @@ pub fn default() -> HashMap { "s" => select_regex, "A-s" => split_selection_on_newline, - "A-_" => merge_consecutive_ranges, + "A-_" => merge_consecutive_selections, "S" => split_selection, ";" => collapse_selection, "A-;" => flip_selections,