-
Notifications
You must be signed in to change notification settings - Fork 33.3k
feat: Support multi cursor line moves #68695
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
base: main
Are you sure you want to change the base?
feat: Support multi cursor line moves #68695
Conversation
Needs some more work, lines get scrambled |
Perhaps a comment about why selections needs to be reversed and executed individually should be added for clarity, tell me if I can assist more |
Latest commit covers scenario when selection-direction goes upwards. Reversing the processing of selections should only occur when lines are moved in the same direction as the selections are made. Also had to make the selections distinct by line. Keeping the selections can probably be solved too since their column-positions wont be changed. If you're interested in this approach in this PR I can give that a try aswell |
Decided to implement the stashing of selections anyway, also added a block when top/end of doc is reached. I'm probably done by now. :) |
@rebornix any status on this? Just curious |
@2mas let's shoot for next week (beginning of next iteration). BTW, I'd love to see some tests as well but I can do that if the time doesn't work for you. |
Awesome, I’ll see if I can come up with something (limited available time I’m afraid). |
Added a few testcases and resolved a conflict with master, should be good to test-drive now. |
Is this still relevant? |
It seems so, the issue is still there in the editor and keeps annoying users apparently. - #11809 (comment) No idea about the state of the code in my PR today though, spent some time on it years ago to make it good to testdrive and merge but it could have fallen short in prioritization since its probably not impacting the majority of the users, or there is something else about the solution that stops it |
This change requires comprehensive testing before merging. At the same time I'm not sure if we are investing more in the textmate indentation rules. I'll leave this to @aiday-mar to decide. |
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.
Hi thank you for making this PR and apologies for the delay in reviewing this PR. I think this PR would be a very valuable contribution. I have looked at it, and I think the approach is overall correct but I have several questions as well as changes I think we should make before merging. It would be good to also test this PR on many different corner cases to make sure it does not introduce bugs. I will do that too. There is also a merge conflict which needs to be resolved first.
|
||
editor.pushUndoStop(); | ||
} else { | ||
editor.pushUndoStop(); |
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.
Could we unify this part of the code between the two cases? We could construct the commands array entirely first and then call executeCommands
on this commands
array. Then after this, if we have moved multiple lines, we can set the selections.
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.
This didn't work out for me. For some reason it just doesn't process all of the commands
|
||
for (const selection of selections) { | ||
commands.push(new MoveLinesCommand(selection, this.down, autoIndent)); | ||
if (movingMultipleLines) { | ||
// If we are at the beginning/end of document and multiple lines are moved, abort. |
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.
Do we need to do this check for whether the selection is at the beginning or end of the document? The other case when we are not moving multiple lines does not do such a check, and it would be good to unify as much as possible the code. Instead of doing the check, when we calculate the new selections, if one of the selections is on the first line and we move up, the new selection should be the same as the old selection, because as I assume, the operation would be a no-op in this case.
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.
This is actually one thing that would be best if you decide on probably, we abort the whole thing here to keep the "format" of the selections, for example if we have these two lines selected on top of the document and move up (line 5 & 6), it will be a no-op currently and we maintain one line between them.
If we remove this block, it will continue with the second selection and move it up, this may be a good thing to do:
And if we move it up again it will replace the first selection (since the first one is processed first and stays in place/no-op, the second one will be processed afterwards and take its place, putting the first one below). To prevent this there could be a check for an existing selection on the line above/below and no-op in that case. I'll see if I can come up with something clean and easy for this
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.
I think this behavior is okay. We could just not treat this case when the selection is at the beginning or at the end and wait to see what users think.
newSelections.push(new Selection(newStartLine, startCol, newEndLine, endCol)); | ||
} | ||
|
||
// Work only with one selection per line |
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.
Let's put this filter operation before the for loop above, so as not to loop over uneccessary selections in the for loop.
Also instead of doing a filter operation followed by a map operation followed by an indexOf operation which is O(n^2)
where n
is the number of selections could we just loop once over the selections, keep track of the endLineNumber
of the last processed selection and if the current selection does not match this endLineNumber
add the current selection to a pre-created selections array. This will allow us to reduce the complexity to O(n).
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.
Great catch, will fix
return arr.map(sel => sel['endLineNumber']).indexOf(s['endLineNumber']) === idx; | ||
}); | ||
|
||
selectionDirectionDown = selections[0].endLineNumber < selections[selections.length - 1].endLineNumber; |
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.
I am not sure I understand this piece of code. Why do we calculate the selectionDirectionDown. What does it mean?
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.
We need to know if the selection direction is down or up (Add Next Occurrence vs Add Previous Occurrence) and if we are initiating a down-move, then we need to process the selections in reversed order from the bottom and up, otherwise they would replace each other. I will try to simplify and clarify this with a comment
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.
In that case is not sufficient to just check if we are initiating a downwards movement? I am not exactly sure, but it seems to me selections are not necessarily ordered from top to bottom.
Could you give a concrete example for what this code solves?
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.
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.
It's because "line 3" gets processed first -> moved to row 6, then the next selection is processed on row 6, which is now "line 3" -> moved to row 7, next selection gets processed on row 7, which is "line 3" -> ends up on row 8. So this is why process order must be right :)
if (movingMultipleLines) { | ||
// Stash selections while processing, set new selections +/- line-change only | ||
for (const selection of selections) { | ||
let startCol = selection.getStartPosition().column; |
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.
To unify the code between the two cases, you can just calculate the new selections array in both cases (whether there are multiple selections or not). And then at the very end set the new selections in all cases. This will help avoid these if/else
branching.
Hi! Nice to see that you find this useful, let me get back with resolved conflicts and some adjustments and clarifications. I have some issues running this project locally though, but I managed to get the web-editor working at least, but tests are only running half-way for some reason, perhaps I can rely on CI anyway, I'll do my best effort |
What issues are you seeing while running this project locally? You should be able to compile as well as run the tests and test with OSS |
…tions, otherwise its already selected
Correcting multiple move-lines when having made selections in reversed order
… line. Applies stash after line movement to keep previous selections. Aborting when moving multiple lines and the line is currently at top or end of document
Firstly tried running test.sh and code.sh in WSL when cloning into devcontainer which failed, can't remember what was logged. I got closest to full experience with running directly on Windows with node v22.15.1, python 3.13.0 and VS 2022 + workload and individual components installed as per docs, I get these types of issues when running locally in Windows:
|
493b319
to
076ad04
Compare
What exactly is the error you see when running |
I have pushed some updates and covered some edge cases as well, for example if one puts cursors on multiple lines with no order, we will still sort them and apply the move only once per line. There are some conditions in the code that look a bit complex, tried to explain as comments. Also, I'm not able to run tests locally and I can see from CI that something is wrong, maybe you can assist me with this? |
PR title updated to better reflect what this does, previous one was not so good since multiple lines can be moved within a single selection, and this PR solves more than that |
Hi thanks for this PR. Today I am a bit busy, but I will make sure to look at this soon. |
No stress at all. I got the test runner working now (not the .bat, but the integrated runner in vscode started working) so test suite is now repaired |
Hi thanks for the PR. I pushed some polish changes. I need to discuss this PR with my team, but hopefully this can get merged in. |
Hi! Thanks I will have a look again at your comment later this week! |
Fixes #68694 moving multiple consequitive lines