-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Adds linked editing for JSX tags #53284
Conversation
Thanks for the PR! It looks like you've changed the TSServer protocol in some way. Please ensure that any changes here don't break consumers of the current TSServer API. For some extra review, we'll ping @sheetalkamat, @mjbvz, @zkat, and @joj for you. Feel free to loop in other consumers/maintainers if necessary |
It looks like you've sent a pull request to update our 'lib' files. These files aren't meant to be edited by hand, as they consist of last-known good states of the compiler and are generated from 'src/lib' or possibly our lib generator. Unless this is necessary, consider closing the pull request and sending a separate PR to update 'src/lib' or https://github.com/microsoft/TypeScript-DOM-lib-generator |
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.
Looks like this is all coming together pretty nicely. I left a bit of feedback about
- why things weren't working correctly with fragments,
- some ideas for how fourslash testing can be made easier
- some thoughts on the API (
TextSpan
vs.TextRange
- maybe we revisit that?) - some style feedback
With regards to style, at some point we should consider getting an automatic formatter - but in the meantime, try to stay consistent with the rest of the codebase, and use the editor's built-in formatter as well.
I've renamed the feature to |
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 you could use a few more edge-case tests. Tests where you're at the beginning of the file, end of the file, and where the code is not syntactically valid (e.g. a closing tag missing its tag name, or a closing fragment tag that "accidentally" has a tag name.
src/server/protocol.ts
Outdated
@@ -23,6 +23,7 @@ import { | |||
|
|||
export const enum CommandTypes { | |||
JsxClosingTag = "jsxClosingTag", | |||
LinkedEditing = "LinkedEditing", |
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'm guessing LSP uses this name, but every other command type in the TS Server protocol is mostly camelCase.
interface LinkedEditingRequest extends FileLocationRequest { | ||
readonly command: CommandTypes.LinkedEditing; | ||
} | ||
interface LinkedEditingRanges { |
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 odd that the ranges are a subfield. Looking at existing precedent, there's FileReferencesResponseBody
, so maybe:
interface LinkedEditingRanges { | |
interface LinkedEditingRangesBody { |
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 had written the LinkedEditingRanges
and LinkedEditingRangeResponse
this way, as this is how they were set up in the stubs and LSP
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 would also prefer if it matched the style we had in tsserver and was named Body
; LSP may define it one way but I don't think we should be inconsistent to match them.
src/services/services.ts
Outdated
|
||
return { | ||
ranges: [{ start: openTagStart, length: openTagEnd - openTagStart }, { start: closeTagStart, length: closeTagEnd - closeTagStart }], | ||
wordPattern: openingTagText |
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 should actually be regex for valid tag names. Something like [a-z\._$][a-z0-9\._$]+
perhaps. You can also try omitting it if VS Code's word pattern is good
With the current implementation, VS Code stops linked editing as soon you type anything that is not the tag 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.
What happens if the returned ranges don't satisfy the regex? No linked cursors I assume?
I think the problem with that regex is that it expects at least two characters. Maybe you wanted:
[a-z\._$][a-z0-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.
Yes, but also try omitting it and seeing if VS Code does the right thing already. You should only need to return wordRange
if the default behavior isn't correct
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, in that case let's leave off wordPattern
for now.
This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise. |
tests/cases/fourslash/fourslash.ts
Outdated
@@ -759,6 +760,11 @@ declare namespace FourSlashInterface { | |||
generateReturnInDocTemplate?: boolean; | |||
} | |||
|
|||
type LinkedEditingInfo = { | |||
readonly ranges : { start: number, length: number }[]; |
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.
readonly ranges : { start: number, length: number }[]; | |
readonly ranges: { start: number, length: number }[]; | |
tests/cases/fourslash/fourslash.ts
Outdated
@@ -759,6 +760,11 @@ declare namespace FourSlashInterface { | |||
generateReturnInDocTemplate?: boolean; | |||
} | |||
|
|||
type LinkedEditingInfo = { | |||
readonly ranges : { start: number, length: number }[]; | |||
wordPattern? : string; |
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.
wordPattern? : string; | |
wordPattern?: string; | |
src/services/services.ts
Outdated
} | ||
return false; | ||
}); | ||
if (!element || !(isJsxOpeningElement(element) || isJsxClosingElement(element))) return undefined; |
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.
Is (isJsxOpeningElement(element) || isJsxClosingElement(element))
possibly false
given what the findAncestor
call does? Fine to leave in just in case, but I'm curious if this/how this happens
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.
Seems to now be mostly for the typeguard feature, as it seems other code I've added since covers these cases. Should I change it to an as JsxOpeningElement | JsxClosingElement | undefined
assertion to the result of findAncestor
?
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.
Things seem good, but, I think I would like to see the protocol naming be consistent, as once we add it, we're not going to change it.
interface LinkedEditingRequest extends FileLocationRequest { | ||
readonly command: CommandTypes.LinkedEditing; | ||
} | ||
interface LinkedEditingRanges { |
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 would also prefer if it matched the style we had in tsserver and was named Body
; LSP may define it one way but I don't think we should be inconsistent to match them.
verify.linkedEditing( { | ||
"0": linkedCursors, | ||
"1": linkedCursors, | ||
"2": undefined, |
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 this is a lot of work, but maybe it's better to have a baseline for this instead, rather than all of these undefined
s? I know that a lot of our current tests have this, but, I think we're trying to make them friendlier, e.g. @sandersn's new completion baselines.
src/services/services.ts
Outdated
// determines if the cursor is in an element tag | ||
const element = findAncestor(token.parent, | ||
n => { | ||
if (!n.parent) return "quit"; |
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.
Is this required? I think the only case where you won't have a parent is on SourceFile
, in which case iteration is going to stop.
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 caused an issue when you checked the kind of .parent
, and .parent
is undefined
, but turns out, I was able to rewrite findAncestor
so I didn't need to check .parent
anymore
The TypeScript team hasn't accepted the linked issue #51832. If you can get it accepted, this PR will have a better chance of being reviewed. |
Adds linked editing of JSX tags (mirror cursor)
fixes #51832