Unnamed repository; edit this file 'description' to name the repository.
fix: 2 bugs in the `Alt-)` and `Alt-(` commands (#13985)
Nik Revenco 7 months ago
parent cb7188d · commit f5a399c
-rw-r--r--helix-term/src/commands.rs47
-rw-r--r--helix-term/tests/test/commands.rs2
-rw-r--r--helix-term/tests/test/commands/reverse_selection_contents.rs49
-rw-r--r--helix-term/tests/test/commands/rotate_selection_contents.rs71
4 files changed, 154 insertions, 15 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 9284df28..0d8c2550 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -5368,6 +5368,7 @@ fn rotate_selections_last(cx: &mut Context) {
doc.set_selection(view.id, selection);
}
+#[derive(Debug)]
enum ReorderStrategy {
RotateForward,
RotateBackward,
@@ -5380,34 +5381,50 @@ fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
let text = doc.text().slice(..);
let selection = doc.selection(view.id);
- let mut fragments: Vec<_> = selection
+
+ let mut ranges: Vec<_> = selection
.slices(text)
.map(|fragment| fragment.chunks().collect())
.collect();
- let group = count
- .map(|count| count.get())
- .unwrap_or(fragments.len()) // default to rotating everything as one group
- .min(fragments.len());
-
- for chunk in fragments.chunks_mut(group) {
- // TODO: also modify main index
- match strategy {
- ReorderStrategy::RotateForward => chunk.rotate_right(1),
- ReorderStrategy::RotateBackward => chunk.rotate_left(1),
- ReorderStrategy::Reverse => chunk.reverse(),
- };
- }
+ let rotate_by = count.map_or(1, |count| count.get().min(ranges.len()));
+
+ let primary_index = match strategy {
+ ReorderStrategy::RotateForward => {
+ ranges.rotate_right(rotate_by);
+ // Like `usize::wrapping_add`, but provide a custom range from `0` to `ranges.len()`
+ (selection.primary_index() + ranges.len() + rotate_by) % ranges.len()
+ }
+ ReorderStrategy::RotateBackward => {
+ ranges.rotate_left(rotate_by);
+ // Like `usize::wrapping_sub`, but provide a custom range from `0` to `ranges.len()`
+ (selection.primary_index() + ranges.len() - rotate_by) % ranges.len()
+ }
+ ReorderStrategy::Reverse => {
+ if rotate_by % 2 == 0 {
+ // nothing changed, if we reverse something an even
+ // amount of times, the output will be the same
+ return;
+ }
+ ranges.reverse();
+ // -1 to turn 1-based len into 0-based index
+ (ranges.len() - 1) - selection.primary_index()
+ }
+ };
let transaction = Transaction::change(
doc.text(),
selection
.ranges()
.iter()
- .zip(fragments)
+ .zip(ranges)
.map(|(range, fragment)| (range.from(), range.to(), Some(fragment))),
);
+ doc.set_selection(
+ view.id,
+ Selection::new(selection.ranges().into(), primary_index),
+ );
doc.apply(&transaction, view.id);
}
diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs
index 20e8ac9a..90ff4cf0 100644
--- a/helix-term/tests/test/commands.rs
+++ b/helix-term/tests/test/commands.rs
@@ -4,6 +4,8 @@ use super::*;
mod insert;
mod movement;
+mod reverse_selection_contents;
+mod rotate_selection_contents;
mod write;
#[tokio::test(flavor = "multi_thread")]
diff --git a/helix-term/tests/test/commands/reverse_selection_contents.rs b/helix-term/tests/test/commands/reverse_selection_contents.rs
new file mode 100644
index 00000000..043a03c3
--- /dev/null
+++ b/helix-term/tests/test/commands/reverse_selection_contents.rs
@@ -0,0 +1,49 @@
+use super::*;
+
+const A: &str = indoc! {"
+ #(a|)#
+ #(b|)#
+ #(c|)#
+ #[d|]#
+ #(e|)#"
+};
+const A_REV: &str = indoc! {"
+ #(e|)#
+ #[d|]#
+ #(c|)#
+ #(b|)#
+ #(a|)#"
+};
+const B: &str = indoc! {"
+ #(a|)#
+ #(b|)#
+ #[c|]#
+ #(d|)#
+ #(e|)#"
+};
+const B_REV: &str = indoc! {"
+ #(e|)#
+ #(d|)#
+ #[c|]#
+ #(b|)#
+ #(a|)#"
+};
+
+const CMD: &str = "<space>?reverse_selection_contents<ret>";
+
+#[tokio::test(flavor = "multi_thread")]
+async fn reverse_selection_contents() -> anyhow::Result<()> {
+ test((A, CMD, A_REV)).await?;
+ test((B, CMD, B_REV)).await?;
+
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread")]
+async fn reverse_selection_contents_with_count() -> anyhow::Result<()> {
+ test((B, format!("2{CMD}"), B)).await?;
+ test((B, format!("3{CMD}"), B_REV)).await?;
+ test((B, format!("4{CMD}"), B)).await?;
+
+ Ok(())
+}
diff --git a/helix-term/tests/test/commands/rotate_selection_contents.rs b/helix-term/tests/test/commands/rotate_selection_contents.rs
new file mode 100644
index 00000000..b1930005
--- /dev/null
+++ b/helix-term/tests/test/commands/rotate_selection_contents.rs
@@ -0,0 +1,71 @@
+use super::*;
+
+// Progression: A -> B -> C -> D
+// as we press `A-)`
+const A: &str = indoc! {"
+ #(a|)#
+ #(b|)#
+ #(c|)#
+ #[d|]#
+ #(e|)#"
+};
+
+const B: &str = indoc! {"
+ #(e|)#
+ #(a|)#
+ #(b|)#
+ #(c|)#
+ #[d|]#"
+};
+
+const C: &str = indoc! {"
+ #[d|]#
+ #(e|)#
+ #(a|)#
+ #(b|)#
+ #(c|)#"
+};
+
+const D: &str = indoc! {"
+ #(c|)#
+ #[d|]#
+ #(e|)#
+ #(a|)#
+ #(b|)#"
+};
+
+#[tokio::test(flavor = "multi_thread")]
+async fn rotate_selection_contents_forward_repeated() -> anyhow::Result<()> {
+ test((A, "<A-)>", B)).await?;
+ test((B, "<A-)>", C)).await?;
+ test((C, "<A-)>", D)).await?;
+
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread")]
+async fn rotate_selection_contents_forward_with_count() -> anyhow::Result<()> {
+ test((A, "2<A-)>", C)).await?;
+ test((A, "3<A-)>", D)).await?;
+ test((B, "2<A-)>", D)).await?;
+
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread")]
+async fn rotate_selection_contents_backward_repeated() -> anyhow::Result<()> {
+ test((D, "<A-(>", C)).await?;
+ test((C, "<A-(>", B)).await?;
+ test((B, "<A-(>", A)).await?;
+
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread")]
+async fn rotate_selection_contents_backward_with_count() -> anyhow::Result<()> {
+ test((D, "2<A-(>", B)).await?;
+ test((D, "3<A-(>", A)).await?;
+ test((C, "2<A-(>", A)).await?;
+
+ Ok(())
+}