From f26677dddba793f634da6281df5cf35ec607962d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Vinyals=20Valdepe=C3=B1as?= Date: Mon, 10 Jul 2023 09:25:30 +0200 Subject: [PATCH 1/7] enhance(sourround_replace): provide visual feedback during the operation When the target character is pressed, the selection is temporarily updated to display the affected positions. It restores the original selection after the operation is cancelled or completed. --- helix-term/src/commands.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index b90f418a63f7..f3c94304c5b1 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5115,11 +5115,25 @@ fn surround_replace(cx: &mut Context) { } }; + // Visual feedback + let selection = selection.clone(); + let mut ranges: SmallVec<[Range; 1]> = SmallVec::new(); + change_pos.chunks(2).for_each(|p| { + if p.len() == 2 { + let from = *p.first().unwrap(); + let to = *p.get(1).unwrap(); + ranges.push(Range::new(from, from + 1)); + ranges.push(Range::new(to, to + 1)); + } + }); + let feedback = Selection::new(ranges, 0); + doc.set_selection(view.id, feedback); + cx.on_next_key(move |cx, event| { let (view, doc) = current!(cx.editor); let to = match event.char() { Some(to) => to, - None => return, + None => return doc.set_selection(view.id, selection), }; let (open, close) = surround::get_pair(to); let transaction = Transaction::change( @@ -5130,6 +5144,7 @@ fn surround_replace(cx: &mut Context) { (pos, pos + 1, Some(t)) }), ); + doc.set_selection(view.id, selection); doc.apply(&transaction, view.id); exit_select_mode(cx); }); From a558743dc590a90ead91f43e09b66558f5ccc98f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Vinyals=20Valdepe=C3=B1as?= Date: Mon, 10 Jul 2023 11:22:44 +0200 Subject: [PATCH 2/7] feedback: much cleaner `for_each` logic --- helix-term/src/commands.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index f3c94304c5b1..1d28cde2a082 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5118,16 +5118,13 @@ fn surround_replace(cx: &mut Context) { // Visual feedback let selection = selection.clone(); let mut ranges: SmallVec<[Range; 1]> = SmallVec::new(); - change_pos.chunks(2).for_each(|p| { - if p.len() == 2 { - let from = *p.first().unwrap(); - let to = *p.get(1).unwrap(); - ranges.push(Range::new(from, from + 1)); - ranges.push(Range::new(to, to + 1)); - } + // TODO: Use [`array_chunks`] once stabilized + change_pos.chunks_exact(2).for_each(|p| { + let [from, to] = *p else { unreachable!() }; + ranges.push(Range::point(from)); + ranges.push(Range::point(to)); }); - let feedback = Selection::new(ranges, 0); - doc.set_selection(view.id, feedback); + doc.set_selection(view.id, Selection::new(ranges, 0)); cx.on_next_key(move |cx, event| { let (view, doc) = current!(cx.editor); From c9c392ecdd22a1fcbd7fe561e0f2d62c9c997c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Vinyals=20Valdepe=C3=B1as?= Date: Mon, 10 Jul 2023 21:19:55 +0200 Subject: [PATCH 3/7] idiomatic loop --- helix-term/src/commands.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 1d28cde2a082..4e88b69d5754 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5119,11 +5119,11 @@ fn surround_replace(cx: &mut Context) { let selection = selection.clone(); let mut ranges: SmallVec<[Range; 1]> = SmallVec::new(); // TODO: Use [`array_chunks`] once stabilized - change_pos.chunks_exact(2).for_each(|p| { + for p in change_pos.chunks_exact(2) { let [from, to] = *p else { unreachable!() }; ranges.push(Range::point(from)); ranges.push(Range::point(to)); - }); + } doc.set_selection(view.id, Selection::new(ranges, 0)); cx.on_next_key(move |cx, event| { From 6127ff3c46a78180a484e4569ccd9a9edbf256da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Vinyals=20Valdepe=C3=B1as?= Date: Tue, 11 Jul 2023 07:39:38 +0200 Subject: [PATCH 4/7] Keep simplifying: update transaction to work on selection, stop chunking. --- helix-term/src/commands.rs | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 4e88b69d5754..7224e6640b0c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5115,33 +5115,26 @@ fn surround_replace(cx: &mut Context) { } }; - // Visual feedback - let selection = selection.clone(); - let mut ranges: SmallVec<[Range; 1]> = SmallVec::new(); - // TODO: Use [`array_chunks`] once stabilized - for p in change_pos.chunks_exact(2) { - let [from, to] = *p else { unreachable!() }; - ranges.push(Range::point(from)); - ranges.push(Range::point(to)); - } + let original_selection = selection.clone(); + let ranges: SmallVec<_> = change_pos.iter().map(|&p| Range::new(p, p + 1)).collect(); doc.set_selection(view.id, Selection::new(ranges, 0)); cx.on_next_key(move |cx, event| { let (view, doc) = current!(cx.editor); let to = match event.char() { Some(to) => to, - None => return doc.set_selection(view.id, selection), + None => return doc.set_selection(view.id, original_selection), }; let (open, close) = surround::get_pair(to); - let transaction = Transaction::change( - doc.text(), - change_pos.iter().enumerate().map(|(i, &pos)| { + let mut i = 0; + let transaction = + Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { let mut t = Tendril::new(); t.push(if i % 2 == 0 { open } else { close }); - (pos, pos + 1, Some(t)) - }), - ); - doc.set_selection(view.id, selection); + i += 1; + (range.from(), range.to(), Some(t)) + }); + doc.set_selection(view.id, original_selection); doc.apply(&transaction, view.id); exit_select_mode(cx); }); From b5835b571d939bc02645f3614156e16a8631b000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Vinyals=20Valdepe=C3=B1as?= Date: Tue, 11 Jul 2023 08:51:36 +0200 Subject: [PATCH 5/7] do not touch transaction code --- helix-term/src/commands.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7224e6640b0c..a0a59650d5d2 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5115,26 +5115,26 @@ fn surround_replace(cx: &mut Context) { } }; - let original_selection = selection.clone(); - let ranges: SmallVec<_> = change_pos.iter().map(|&p| Range::new(p, p + 1)).collect(); + let selection = selection.clone(); + let ranges: SmallVec<_> = change_pos.iter().map(|&pos| Range::point(pos)).collect(); doc.set_selection(view.id, Selection::new(ranges, 0)); cx.on_next_key(move |cx, event| { let (view, doc) = current!(cx.editor); let to = match event.char() { Some(to) => to, - None => return doc.set_selection(view.id, original_selection), + None => return doc.set_selection(view.id, selection), }; let (open, close) = surround::get_pair(to); - let mut i = 0; - let transaction = - Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { + let transaction = Transaction::change( + doc.text(), + change_pos.iter().enumerate().map(|(i, &pos)| { let mut t = Tendril::new(); t.push(if i % 2 == 0 { open } else { close }); - i += 1; - (range.from(), range.to(), Some(t)) - }); - doc.set_selection(view.id, original_selection); + (pos, pos + 1, Some(t)) + }), + ); + doc.set_selection(view.id, selection); doc.apply(&transaction, view.id); exit_select_mode(cx); }); From 6dd6347f732431fc78f5dd338572281ac4de3f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Vinyals=20Valdepe=C3=B1as?= Date: Wed, 12 Jul 2023 09:41:04 +0200 Subject: [PATCH 6/7] Compute correct primary index to minimize view movement --- helix-term/src/commands.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a0a59650d5d2..440541e4e8e6 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5116,8 +5116,22 @@ fn surround_replace(cx: &mut Context) { }; let selection = selection.clone(); - let ranges: SmallVec<_> = change_pos.iter().map(|&pos| Range::point(pos)).collect(); - doc.set_selection(view.id, Selection::new(ranges, 0)); + let ranges: SmallVec<[Range; 1]> = change_pos.iter().map(|&p| Range::point(p)).collect(); + // TODO: Use [`array_chunks`] once stabilized + let primary_index = ranges + .chunks_exact(2) + .enumerate() + .find_map(|(i, pair)| { + let [from, to] = *pair else { unreachable!() }; + // The cursor must be somewhere between both matches. + if Range::new(from.from(), to.to()).overlaps(&selection.primary()) { + Some(i * 2) + } else { + None + } + }) + .unwrap_or(0); + doc.set_selection(view.id, Selection::new(ranges, primary_index)); cx.on_next_key(move |cx, event| { let (view, doc) = current!(cx.editor); From 7917f63fe161b94717f11416e3ed7f872e11628c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Vinyals=20Valdepe=C3=B1as?= Date: Wed, 12 Jul 2023 17:22:52 +0200 Subject: [PATCH 7/7] Finding the primary index on the new selection is easy --- helix-term/src/commands.rs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 440541e4e8e6..68e321cb8922 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5117,21 +5117,10 @@ fn surround_replace(cx: &mut Context) { let selection = selection.clone(); let ranges: SmallVec<[Range; 1]> = change_pos.iter().map(|&p| Range::point(p)).collect(); - // TODO: Use [`array_chunks`] once stabilized - let primary_index = ranges - .chunks_exact(2) - .enumerate() - .find_map(|(i, pair)| { - let [from, to] = *pair else { unreachable!() }; - // The cursor must be somewhere between both matches. - if Range::new(from.from(), to.to()).overlaps(&selection.primary()) { - Some(i * 2) - } else { - None - } - }) - .unwrap_or(0); - doc.set_selection(view.id, Selection::new(ranges, primary_index)); + doc.set_selection( + view.id, + Selection::new(ranges, selection.primary_index() * 2), + ); cx.on_next_key(move |cx, event| { let (view, doc) = current!(cx.editor);