-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Fix cursor offset tracking #4397
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
Conversation
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.
Awesome!
tests/cursor/jsfmt.spec.js
Outdated
|
||
|
||
const z = 9 | ||
`) |
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.
What about
const y = 5
<|>
const z = 9
IMO that should either put it on the blank line between the two or right after the 5
.
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.
Yeah it puts it on the blank line between the two. I added this test case just now 👍
b57f553
to
d7f54bf
Compare
src/main/core.js
Outdated
// diff old and new cursor node texts, with a special cursor | ||
// symbol inserted to find out where it moves to | ||
|
||
const CURSOR = Symbol("cursor"); |
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 we can move this to the file top level (together with UTF8BOM
)
src/main/core.js
Outdated
for (const entry of cursorNodeDiff) { | ||
if (entry.removed) { | ||
if (entry.value.indexOf(CURSOR) > -1) { | ||
break; |
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.
Maybe?
if (entry.removed && entry.value.indexOf(CURSOR) > -1) {
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 not the same thing, because there's an else
clause. Would need to change the else
to else if (!entry.removed)
in order for that to work, which IMO is a bit more confusing. But happy to change it if 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.
Oops sorry! No need to change
src/main/core.js
Outdated
return text.slice(0, rangeStart) + rangeTrimmed + text.slice(rangeEnd); | ||
return { | ||
formatted: formatted, | ||
cursorOffset: cursorOffset |
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.
return { formatted, cursorOffset }
=)
src/main/core.js
Outdated
result = Object.assign({}, result, { | ||
formatted: trimmed + opts.newLine, | ||
cursorNodeStart | ||
}); |
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.
You can change the result
object here
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.
Removing result =
and {},
should have that effect.
src/main/core.js
Outdated
} else { | ||
i += entry.count; | ||
} | ||
} |
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 cursorOffset = newCursorNodeStart;
for (const entry of cursorNodeDiff) {
/* code */
cursorOffset += entry.count;
}
return { formatted: result.formatted, cursorOffset };
"original": "return <|> 15", | ||
} | ||
`; | ||
|
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 we can improve these snapshots, see below
I pushed a change to use our current infrastructure for tests, it's been working really well.. just create files in |
Cool stuff with the snapshots! I'll try to get around to those small changes later today. |
All done @duailibe ! |
@ds300 Looks good! I forgot to mention I only moved some of the tests to snapshots to show how to do it. What do you think making them all like that? Just put each test in a new file (don't worry about having too many) :) |
Haha, I wasn't sure whether you'd finish. I did the rest just now. Cheers! |
Thank you!!! 💯 👏 🎉 |
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 for fixing all those issues! I believed that cursor preservation was going to be critical and required to have a good format on save experience but it turned out I was wrong, people used it without it. But this is something that I think will provide a better user experience nonetheless!
|
||
thisWillBeFormatted(2, 3); | ||
|
||
thisWontBeFormatted (2, 9<|>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.
Those snapshot formats are really cool, super easy to understand what’s going on!
I forgot to say that the reason I was working on this is that I was building a little toy text editor to see what it would feel like if prettier's formatting happened with a smooth animated transition. Turns out it feels great — https://code-in-motion.herokuapp.com/ |
This sounds awesome! I’m currently in vacation with just my iphone and it doesn’t seem to be working. I’d love if you could make a small video :) |
That looks slick! Do you do it “on save” or are you able to do it on arbitrary keystrokes that would lead to a code that actually parses? |
just on save so far. I tried to do it after every keystroke but it doesn't work because sometimes prettier deletes things you type like commas :) so to work automatically it would need some basic heuristics about when to format and when not to format. But also triggering it manually gives this nice cathartic feeling, for me at least. |
Howdy! 👋 😄
This PR is fixing a few things related to the cursorOffset option:
Fix two bugs that were present before
Improve tracking with a more nuanced approach (fixes --cursor-offset output is wrong sometimes #1981)
Allow cursorOffset to be used with rangeStart and rangeEnd (fixes Allow
cursorOffset
to be used withrangeStart
andrangeEnd
#1869)I'll explain how I did number 2, to help you read this PR.
How cursor offset was tracked before, in broad terms:
This was producing bad results because if the text between the start of the AST node and the cursor would change (e.g. whitespace being inserted or deleted) then the offset calculated in step 2 is useless and should be ignored.
The approach I take in this PR is to use that relative offset as long as the text didn't change. Otherwise, I calculate a character diff between the input and output text for the cursor node, and then figure out where the best place for the cursor would be from that diff.
In order to do that, i had to "wrap" the printed version of the cursor node using two 'cursor placeholder's, one at either end. Previously there was only one inserted at the beginning.
Let me know if you have any more questions.
Thanks for the amazing work on Prettier. I can't live without it. 🙇