-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Merge history changes into a single operation. Fixes #427 #496
Conversation
The way I would test this would be to mash keys while also mashing backspace for about 30 seconds, then hit esc and then i and then do it again a few times. Then use u and c-r to see if all my gibberish was properly maintained. Would you mind doing a test like that? As you pointed out, I am very cautious about making changes to history tracking, since that's one thing I definitely don't want to break. However if we can be confident that this works, then I'm excited to get it merged in. In any case, this is a really awesome and big change in terms of VSCodeVim usability! Thanks for the contribution! |
I just pushed again, fixing a bug (more missed optimization) and adding comments to make the merge code a bit more understandable. Also, since I added method It seems very stable so far. One way to test it that gives a bit of visibility into what it's doing is to add One part that I haven't tested is where there are multiple |
Yeah, this is tough. Possibly using search and replace with ctrl-f? |
Hmm, the VSCode replace function doesn't appear to call |
var iter = (this.changes[Symbol.iterator]()); | ||
var prev = iter.next().value; | ||
for (const change of iter) { | ||
if (prev.text.length === 0) { |
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 prev
represents changes that we are going to merge or merging, maybe we can give it a more intuitive name?
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 loop is basically an Array.reduce()
, but we can't use that directly because it can output more than one DocumentChange. e.g. changes in different parts of the document or a del+add (see below). reduce passes two arguments to the function: previous
and current
.
Here we have prev
and change
instead.
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.
Ah, I see. Since we can't use reduce, I'd suggest names that more closely map to what's going on here. Maybe currentChange
for prev
and nextChange
or perhaps toMerge
for change
.
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 I'll go for previous
and current
to match most of the examples on MDN, after making it a bit clearer that this loop is a manual reduce().
// create new changes list | ||
var merged: DocumentChange[] = []; | ||
// iterate through the changes, saving the first outside the loop | ||
var iter = (this.changes[Symbol.iterator]()); |
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.
Did you consider something like this?
prev = this.changes[0]
for (const change of this.changes) {
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.
Yes I considered that at first, but then on the first iteration prev
and change
would be the same object. change
needs to skip the first element, which is why I make an iterator, get the first element, then iterate through the rest.
Other options include using var prev = this.changes.splice(0, 1)[0];
or maybe var prev = this.changes[0]; for (const next of this.changes.slice(1))
. I'd prefer the slice method just because it doesn't alter the original array, and it looks a bit clearer to me.
I went with the manual iterator because it doesn't change the original array, it only loops through the array once, and it doesn't create another object. I fully admit these are quite arbitrary reasons, two of which pretty clearly premature optimization. 😄
What would you prefer?
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.
Oh, right right.
Of all the ideas listed, I prefer
prev = this.changes[0];
for (const next of this.changes.slice(1)) {
I think it's the most readable. 😄 I always favor readability over optimization, unless we've found a bottleneck.
This is completely awesome. It will definitely improve everyone's Vim experience. If you can fix up the few minor things I mentioned, we should be good to go here. |
Updated to fix the stuff you mentioned. Tests pass fine locally. |
I just realized the most beautiful possible way to handle getting the first element of a list and the rest would have been:
rather than that splicing thing we did. I'm gonna just merge this because I'm impatient. I'll fix it on master sometime. |
Anyways, again, thanks for the awesome pull request. This is really great work. |
Adds a method
HistoryStep.merge
that merges all ofthis.changes
into as fewDocumentChange
s as possible, and call it infinishCurrentStep()
. This makes undoing much faster.I did some manual testing and I'm pretty sure the logic is sound, but it should probably be reviewed carefully.
See #427.