diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 4e33d6b25832..d911dfd8c707 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -713,6 +713,19 @@ impl Selection { Selection::new(ranges, self.primary_index) } + + /// Creates a new selection in which all ranges are merged into one. + /// + /// The new range covers from the lowest [Range::from] to the highest + /// [Range::to]. + pub fn join_ranges(&self) -> Selection { + // SAFETY: selections created with Selection::new are asserted to have + // non-empty range vectors. + let first = self.ranges.first().unwrap(); + let last = self.ranges.last().unwrap(); + + Selection::new(smallvec![first.union(*last)], 0) + } } impl<'a> IntoIterator for &'a Selection { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 1fb8a8e52579..0996af82b7e4 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -462,6 +462,7 @@ impl MappableCommand { select_shortest_selection_to_register, "Select shortest selection for each pair and save to register", select_longest_selection_from_register, "Select longest selection for each pair from register", select_longest_selection_to_register, "Select longest selection for each pair and save to register", + join_selections, "Join all selections into one", ); } @@ -5427,3 +5428,18 @@ pub mod range_combination { } } } + +fn join_selections(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let doc_selection = doc.selection(view.id); + let range_count = doc_selection.ranges().len(); + + let selection = doc_selection.join_ranges(); + + doc.set_selection(view.id, selection); + cx.editor.set_status(format!( + "Joined {} range{}", + range_count, + if range_count == 1 { "" } else { "s" } + )); +} diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index c2e1a3b2bd4f..ddc301461fe9 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -134,7 +134,7 @@ pub fn default() -> HashMap { "^" => { "Selections" "s" => save_selection_to_register, "S" => restore_selection, - // "j" => join_selections, + "j" => join_selections, "c" => { "Combine selection from register" "a" => append_selection_from_register, "u" => union_selection_from_register, diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 2d45cb4be694..4194796f03cf 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -378,3 +378,23 @@ async fn test_union_marker() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn test_join_selections() -> anyhow::Result<()> { + test(( + indoc! {"\ + #(foo|)# + bar + #[baz|]#" + }, + "^j", + indoc! {"\ + #[foo + bar + baz|]#" + }, + )) + .await?; + + Ok(()) +}