-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix crash when deleting with multiple cursors #6024
Conversation
thanks for fixing this Pascal, it also fixes #5272 |
Thanks for confirming that 👍 That issue was on my todo too so I get two for one here 😄 |
I added a fix for #6029 here as the refactor in 3edcc5d made that easier to implement. The TLDR is: Deleting forward in append mode used to delete one char forward and then delete backwards, because the selection was shrunk. I have changed the command to always delete forward. The following example of pressing What I expect to happen (and how it behaves with this PR):
This is inline with how other editors like kakoune work.
|
f0f2dae
to
94d6633
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few minor comments, otherwise I think this looks good
ca0315a
to
4bfbbd6
Compare
(force pushed to keep the history intact since I did put some effort into commit messages here :D) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok the changes themselves look good to me. I would humbly request regression tests to codify expected behavior, but not sure if you or others feel this is worth blocking on.
I spent a little time thinking over the larger API—it doesn't quite feel right to fix the function that generates invalid transactions by adding another function that doesn't and changing specific call sites to that instead. But looking over the change
function, I can see why we can't just move the from
marker forward in the case of overlap, since this could just end up inserting text in the wrong place.
My first instinct was to suggest that we either
- Just keep adding and deleting text separate, or
- Fix this issue in
change
and just makedelete
a thin wrapper around it
to fix even the possibility of this happening through the API. But 1 would make cases like R
very awkward, and 2 I can't think of a reasonable way to handle overlapping ranges when it handles both deletions and text replacement, if it's even possible, without making it super complex.
So at the end of the day, having another function that relaxes the overlapping ranges invariant for text deletions gets the job done.
344c44b
to
5cbc736
Compare
@dead10ck I add some integration tests that was a great suggestions. We should really make more use of the integration test framework it works really great. I was pretty much just able to use the reproduction cases straight from the issues (plus some additional tests). Regarding the API I actually think having a second function makes sense. For normal edits (replacements) there is no unambiguous way to merge overlapping edits. Instead callet must deal with that (or we panic). There is no correct way to handle this generally in I spent a lot of time thinking about a better API here when I created this PR but I came to the conclusion that there really is none. Overlapping edits are fundamentally incorrect and must be prevented in the caller. The only exceptions are:
When I created this PR I actually manually went trough all callsites to the |
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`.
Currently, when forward deleting (`delete_char_forward` bound to `del`, `delete_word_forward`, `kill_to_line_end`) the cursor is moved to the left in append mode (or generally when the cursor is at the end of the selection). For example in a document `|abc|def` (|indicates selection) if enter append mode the cursor is moved to `c` and the selection becomes: `|abcd|ef`. When deleting forward (`del`) `d` is deleted. The expectation would be that the selection doesn't shrink so that `del` again deletes `e` and then `f`. This would look as follows: `|abcd|ef` `|abce|f` `|abcf|` `|abc |` This is inline with how other editors like kakoune work. However, helix currently moves the selection backwards leading to the following behavior: `|abcd|ef` `|abc|ef` `|ab|ef` `ef` This means that `delete_char_forward` essentially acts like `delete_char_backward` after deleting the first character in append mode. To fix the problem the cursor must be moved to the right while deleting forward (first fix in this commit). Furthermore, when the EOF char is reached a newline char must be inserted (just like when entering appendmode) to prevent the cursor from moving to the right
5cbc736
to
013fa65
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for adding tests! This will help a lot in the future for anyone who makes changes to deleting behavior.
Also, I agree with your points about the API and adding a function. It does make sense to have both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, this looks good to me! Thanks for adding tests and bearing with me. This will help when I get to auto pair deletions.
Fixes #6021
Fixes #5272
Fixes #6029
The
delete_char_backward
command delete indentation when at the start of the line. The problem is that this can cause multiple overlaing deletions with multiple cursors. For example in #6021 one of the cursors is after the 3. space and one after the 1. char. Because the indent unit is 4 spacesdelete_char_backward
deletes to the start of the line for both cursors. The generated transaction is therefore invalid which causes an integer underflow when the transaction is applied.I fixed a similar crash in #5728. Mutlicursors potentially generating overlapping changes is a common pitfall and something we should be wary of. In that PR I simply discarded changes that overlapped a previous change. That seemed the right behaviour to me for the replacements created by completions. For deletions however we can simply merge the overlapping deletions together (otherwise the behaviour here would be incorrect). To that end I added a new
delete
anddelete_by_selection
function which does not accept a tendril for replacement and allows overlapping ranges.In the process I noticed that the
delete_by_selection_insert_mode
function was implemented a bit weirdly:Transaction::change_by_selection
which requires cloneing the selectionlsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
eventough all callsites do (wihout doing that its barely wort moving the logic to a shared function)Therefore I cleaned up that function a little in a second commit as I was touching that code anyways (and there were no open PRs that touch that code).
If we don't want these cosmetic changes I can easily drop that second commit.