Skip to content

Commit

Permalink
Add command to merge consecutive ranges in selection (helix-editor#5047)
Browse files Browse the repository at this point in the history
  • Loading branch information
DylanBulfin authored and Frederik Vestre committed Feb 6, 2023
1 parent 0a02627 commit bc54369
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 12 deletions.
1 change: 1 addition & 0 deletions book/src/keymap.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down
95 changes: 83 additions & 12 deletions helix-core/src/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,28 +495,53 @@ 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]);
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() {
let new_range = curr_range.merge(*prev_range);
if prev_range == &primary || curr_range == &primary {
primary = new_range;
}
*prev_range = new_range;
true
} else {
prev_i += 1;
self.ranges[prev_i] = self.ranges[i];
false
}
if i == self.primary_index {
self.primary_index = prev_i;
}
}
});

self.ranges.truncate(prev_i + 1);
self.primary_index = self
.ranges
.iter()
.position(|&range| range == primary)
.unwrap();

self
}
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_selections, "Merge consecutive selections",
search, "Search for regex pattern",
rsearch, "Reverse search for regex pattern",
search_next, "Select next search match",
Expand Down Expand Up @@ -1589,6 +1590,12 @@ fn split_selection_on_newline(cx: &mut Context) {
doc.set_selection(view.id, selection);
}

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);
}

#[allow(clippy::too_many_arguments)]
fn search_impl(
editor: &mut Editor,
Expand Down
1 change: 1 addition & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub fn default() -> HashMap<Mode, Keymap> {

"s" => select_regex,
"A-s" => split_selection_on_newline,
"A-_" => merge_consecutive_selections,
"S" => split_selection,
";" => collapse_selection,
"A-;" => flip_selections,
Expand Down

0 comments on commit bc54369

Please sign in to comment.