-
Notifications
You must be signed in to change notification settings - Fork 24
Implement references and rename #213
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
Adds support for 'textDocument/references', 'textDocument/rename', and 'textDocument/prepareRename'. These all work very similarly, so I implemented them together. This implementation only works for root shapes, not members or resource identifiers/properties (which I call resource fields for brevity). Originally I intended to make it work for members, but for 'complete' member support, you really need to support resource fields, otherwise if you tried to rename a member, it wouldn't rename the resource field it references, and finding references wouldn't show fully accurate information. The problem is, the relationship between members and resource fields can be implicit, and/or determined by a number of different traits (`resourceIdentifier`, `property`, `references`, `nestedProperties`). And some of these traits refer to the resource field by string name. Current KnowledgeIndex implementations don't support going from an arbitrary member -> field bindings and back. Plus, we can't reliably use KnowledgeIndicies because they usually operate on a complete, valid model, whereas the language server wants to operate on the minimum subset of a possibly invalid model. I think it would be possible still to support members in the future, either with some clever code in the language server or enhancements to the smithy library, but I didn't want to block rename/references for regular shapes on that. Some specific implementation details to note: 1. Rename/References throw ResponseErrorException on an unsupported position (like a member name), or when the rename would be invalid (i.e. renaming a shape from a dependency, or providing an invalid shape name). This is what the spec says you should do, and we should update other features to do the same. 2. Rename disambiguates shapes that would be conflicting after the rename by using an absolute shape id, and removes conflicting use statements. 3. Rename/References both work on references to members of the target shape, i.e. `com.foo#Foo$bar`, only if the request position is before the `$`, otherwise it assumes you're getting the reference of a member. 4. Prepare returns the range of the identifier to be renamed (or throws an error as specified in 1.), so if you go to rename `com.foo#Bar`, the rename range will be the range of `Bar`. Other notable changes: 1. Changed `DocumentId` to only have distinct types for root shapes and members. The type will be `ROOT` when the cursor is in any part of the shape id before a member, and `MEMBER` only when the cursor is actually within the member part of the shape id. The rest of the other possible id types didn't seem to be useful enough to keep. 2. Added a `line` field to Syntax.Node.Str, which makes it _much_ more efficient to get the range of a Str or Ident. 3. Fixed an issue in TextWithPositions when there are multiple positions on the same line.
Fixes an issue with ShapeSearch::findShape where the local namespace was checked before imports. It was inconsistent with the way the model loader resolves shapes, so you could end up jumping to the wrong definition/references, or renaming the wrong shape.
Test assertions weren't working properly with windows line separators
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.
Left some small comments but otherwise LGTM.
Also tested functionality by building this PR locally and using it in Neovim 0.12.
| LOGGER.finest("PrepareRename"); | ||
| String uri = params.getTextDocument().getUri(); | ||
| ProjectAndFile projectAndFile = state.findProjectAndFile(uri); | ||
| if (projectAndFile == null) { |
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.
would be nice if we could extract some of this logic to a helper function but not sure if it's worth the trouble given the different return types of each request method
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.
Yea I agree. There's also a lot of duplicated setup code in each of the handlers, for example the ReferencesHandler.Config class which is shared with rename. I'm thinking I'll follow up with some changes to refactor all that
| private static ResponseErrorException notSupported() { | ||
| var error = new ResponseError(); | ||
| error.setCode(ResponseErrorCode.RequestFailed); | ||
| error.setMessage("Not supported 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.
| error.setMessage("Not supported here."); | |
| error.setMessage("References not supported 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.
Updated
| addPendingReferences(); | ||
| } | ||
|
|
||
| private void collect(Syntax.Statement statement) { |
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 don't know if I like collect() since the function returns void and uses side effects
| var conflictingShape = ShapeSearch.findShape(sourceFile.getParse(), conflictingId, config.model()) | ||
| .orElse(null); | ||
| if (conflictingShape == null) { | ||
| return; | ||
| } |
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.
can this be improved? it looks awkward lol but maybe Optional is not that flexible
| * identifiers, so this class a single subclass {@link Ident}. | ||
| */ | ||
| public static sealed class Str extends Node { | ||
| final int 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.
is this supposed to be line 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.
Yes
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.
Made this lineNumber
Adds support for 'textDocument/references', 'textDocument/rename', and
'textDocument/prepareRename'. These all work very similarly, so I
implemented them together.
This implementation only works for root shapes, not members or resource
identifiers/properties (which I call resource fields for brevity).
Originally I intended to make it work for members, but for 'complete'
member support, you really need to support resource fields, otherwise if
you tried to rename a member, it wouldn't rename the resource field it
references, and finding references wouldn't show fully accurate
information. The problem is, the relationship between members and
resource fields can be implicit, and/or determined by a number of
different traits (
resourceIdentifier,property,references,nestedProperties). And some of these traits refer to the resourcefield by string name. Current KnowledgeIndex implementations don't
support going from an arbitrary member -> field bindings and back. Plus,
we can't reliably use KnowledgeIndicies because they usually operate on
a complete, valid model, whereas the language server wants to operate on
the minimum subset of a possibly invalid model.
I think it would be possible still to support members in the future,
either with some clever code in the language server or enhancements to
the smithy library, but I didn't want to block rename/references for
regular shapes on that.
Some specific implementation details to note:
position (like a member name), or when the rename would be invalid
(i.e. renaming a shape from a dependency, or providing an invalid
shape name). This is what the spec says you should do, and we should
update other features to do the same.
by using an absolute shape id, and removes conflicting use statements.
shape, i.e.
com.foo#Foo$bar, only if the request position is beforethe
$, otherwise it assumes you're getting the reference of amember.
an error as specified in 1.), so if you go to rename
com.foo#Bar,the rename range will be the range of
Bar.Other notable changes:
DocumentIdto only have distinct types for root shapes andmembers. The type will be
ROOTwhen the cursor is in any part ofthe shape id before a member, and
MEMBERonly when the cursor isactually within the member part of the shape id. The rest of the
other possible id types didn't seem to be useful enough to keep.
linefield to Syntax.Node.Str, which makes it muchmore efficient to get the range of a Str or Ident.
on the same line.
Rev: Fix shape search resolution
Fixes an issue with ShapeSearch::findShape where the local namespace was
checked before imports. It was inconsistent with the way the model
loader resolves shapes, so you could end up jumping to the wrong
definition/references, or renaming the wrong shape.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.