diff options
author | Pascal Kuthe | 2023-02-16 21:15:06 +0000 |
---|---|---|
committer | Blaž Hrastnik | 2023-05-18 06:20:55 +0000 |
commit | f8225ed9219f23cf04bd378ec43e1e1a1059a0ed (patch) | |
tree | 92b9b7f4d44b2655bb8d3fc3825203655bfd465f /helix-term | |
parent | 6842fd4c36c5855023b007a36b0b5c8bd965d8de (diff) |
fix panic when deleting overlapping ranges
Some deletion operations (especially those that use indentation)
can generate overlapping deletion ranges when using multiple cursors.
To fix that problem a new `Transaction::delete` and
`Transaction:delete_by_selection` function were added. These functions
merge overlapping deletion ranges instead of generating an invalid
transaction. This merging of changes is only possible for deletions
and not for other changes and therefore require its own function.
The function has been used in all commands that currently delete
text by using `Transaction::change_by_selection`.
Diffstat (limited to 'helix-term')
-rw-r--r-- | helix-term/src/commands.rs | 37 | ||||
-rw-r--r-- | helix-term/tests/test/commands.rs | 44 |
2 files changed, 55 insertions, 26 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 80774cea..964d87ff 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2315,9 +2315,8 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) { }; // then delete - let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { - (range.from(), range.to(), None) - }); + let transaction = + Transaction::delete_by_selection(doc.text(), selection, |range| (range.from(), range.to())); doc.apply(&transaction, view.id); match op { @@ -2333,9 +2332,8 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) { #[inline] fn delete_selection_insert_mode(doc: &mut Document, view: &mut View, selection: &Selection) { - let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { - (range.from(), range.to(), None) - }); + let transaction = + Transaction::delete_by_selection(doc.text(), selection, |range| (range.from(), range.to())); doc.apply(&transaction, view.id); } @@ -3422,10 +3420,10 @@ pub mod insert { let auto_pairs = doc.auto_pairs(cx.editor); let transaction = - Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { + Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| { let pos = range.cursor(text); if pos == 0 { - return (pos, pos, None); + return (pos, pos); } let line_start_pos = text.line_to_char(range.cursor_line(text)); // consider to delete by indent level if all characters before `pos` are indent units. @@ -3433,11 +3431,7 @@ pub mod insert { if !fragment.is_empty() && fragment.chars().all(|ch| ch == ' ' || ch == '\t') { if text.get_char(pos.saturating_sub(1)) == Some('\t') { // fast path, delete one char - ( - graphemes::nth_prev_grapheme_boundary(text, pos, 1), - pos, - None, - ) + (graphemes::nth_prev_grapheme_boundary(text, pos, 1), pos) } else { let width: usize = fragment .chars() @@ -3464,7 +3458,7 @@ pub mod insert { _ => break, } } - (start, pos, None) // delete! + (start, pos) // delete! } } else { match ( @@ -3482,17 +3476,12 @@ pub mod insert { ( graphemes::nth_prev_grapheme_boundary(text, pos, count), graphemes::nth_next_grapheme_boundary(text, pos, count), - None, ) } _ => // delete 1 char { - ( - graphemes::nth_prev_grapheme_boundary(text, pos, count), - pos, - None, - ) + (graphemes::nth_prev_grapheme_boundary(text, pos, count), pos) } } } @@ -3508,13 +3497,9 @@ pub mod insert { let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); let transaction = - Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { + Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| { let pos = range.cursor(text); - ( - pos, - graphemes::nth_next_grapheme_boundary(text, pos, count), - None, - ) + (pos, graphemes::nth_next_grapheme_boundary(text, pos, count)) }); doc.apply(&transaction, view.id); diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 342a849b..1efb204e 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -385,3 +385,47 @@ async fn test_character_info() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn test_delete_char_backward() -> anyhow::Result<()> { + // don't panic when deleting overlapping ranges + test(( + platform_line("#(x|)# #[x|]#").as_str(), + "c<space><backspace><esc>", + platform_line("#[\n|]#").as_str(), + )) + .await?; + test(( + platform_line("#( |)##( |)#a#( |)#axx#[x|]#a").as_str(), + "li<backspace><esc>", + platform_line("#(a|)##(|a)#xx#[|a]#").as_str(), + )) + .await?; + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_delete_word_backward() -> anyhow::Result<()> { + // don't panic when deleting overlapping ranges + test(( + platform_line("fo#[o|]#ba#(r|)#").as_str(), + "a<C-w><esc>", + platform_line("#[\n|]#").as_str(), + )) + .await?; + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_delete_word_forward() -> anyhow::Result<()> { + // don't panic when deleting overlapping ranges + test(( + platform_line("fo#[o|]#b#(|ar)#").as_str(), + "i<A-d><esc>", + platform_line("fo#[\n|]#").as_str(), + )) + .await?; + Ok(()) +} + |