Skip to content
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

Determine end of definition location #35

Merged
merged 9 commits into from Apr 29, 2022

Conversation

srchase
Copy link
Contributor

@srchase srchase commented Apr 15, 2022

Currently, the source location of a shape is used to set both the start and end positions of a location range when building the map of definition locations used by the definition provider.

This CR improves the definition locations by finding the appropriate end position of a shape. By working from the bottom of a file to the top, the real end of a shape definition can be found by ignoring the traits and comments of the shape beneath it in a file.

This also adds a getShapeIdFromLocation method which returns the shapeId of the shape defined at the given location within the model.

With improved definition locations, clients can render more accurate definitions.

In VS Code, the Peek Definition pane currently shows the following definition for a shape:
current

With the improved definition location, the entire shape is highlighted:
updated

When using the "Go to Definition" context option, the cursor still moves to the beginning of the shape. With this CR, when selecting "Go to Definition" when the cursor is already within a shape, the cursor will no longer move. Currently, the cursor moves to the beginning of the shape definition due to the definition location being a zero-length location range that precedes the actual shape definition.

This CR also cleans up the unused reload method in SmithyProject.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@srchase srchase requested a review from a team as a code owner April 15, 2022 20:54
String shapeName = shape.getId().getName();
// Members get the same shapeName as their parent structure
// so we ignore them, to avoid producing a location per-member
// TODO: index members somehow as well?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems useful, since apply syntax allows for targeting a member externally. Maybe the location map should use the ShapeId as the key instead of the name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When finding a token in a line, SmithyTextDocumentService only returns a potential shape name. We could use the shape name to try to find the appropriate ShapeId and then change over the location map to use the ShapeId as the key.

try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), modelFiles)) {
Map<String, List<Location>> locations = hs.getProject().getLocations();

correctLocation(locations, "SingleLine", 4, 5);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a little strange that the starts for this are the line before, worth seeing if that's necessary to work properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Positions for LSP are zero-based, so this is currently setting the end position to the start of the next line.

I'll add logic to properly determine the line length so we can determine the actual end of a shape on a given line.

@srchase srchase force-pushed the improve-definition-locations branch from be2e5d7 to c288641 Compare April 19, 2022 15:28
@srchase srchase force-pushed the improve-definition-locations branch from c288641 to 16893c0 Compare April 19, 2022 16:05
@srchase srchase force-pushed the improve-definition-locations branch from 7aa59d0 to 9444923 Compare April 22, 2022 20:03
@Baccata
Copy link
Contributor

Baccata commented Apr 23, 2022

When using the "Go to Definition" context option, the cursor still moves to the beginning of the shape. With this CR, when selecting "Go to Definition" when the cursor is already within a shape, the cursor will no longer move. Currently, the cursor moves to the beginning of the shape definition due to the definition location being a zero-length location range that precedes the actual shape definition.

I've not read the code so bear with me, but could use these more precise locations to actually determine the actual shape the cursor is hovering over ? Go to Definition suffers from not having this information, as it does a naive extraction of the underlying string, then gets you the first shape the name of which matches that string. If we had a Location => Shape method, when the cursor is on a member shape, we could jump to the actual targeted shape. There's also a number of additional LSP endpoints that could be implemented by virtue of having this information.

@srchase
Copy link
Contributor Author

srchase commented Apr 25, 2022

When using the "Go to Definition" context option, the cursor still moves to the beginning of the shape. With this CR, when selecting "Go to Definition" when the cursor is already within a shape, the cursor will no longer move. Currently, the cursor moves to the beginning of the shape definition due to the definition location being a zero-length location range that precedes the actual shape definition.

I've not read the code so bear with me, but could use these more precise locations to actually determine the actual shape the cursor is hovering over ? Go to Definition suffers from not having this information, as it does a naive extraction of the underlying string, then gets you the first shape the name of which matches that string. If we had a Location => Shape method, when the cursor is on a member shape, we could jump to the actual targeted shape. There's also a number of additional LSP endpoints that could be implemented by virtue of having this information.

Yes, the member's location could be used to determine its actual definition which could then allow for jumping to its target. One nice side-effect of the current naive extraction is that tokens can be pulled out of line docs and you can still jump to the definition.
Which other LSP endpoints did you have in mind for being able to use a Location => Shape method?

@Baccata
Copy link
Contributor

Baccata commented Apr 28, 2022

Which other LSP endpoints did you have in mind for being able to use a Location => Shape method?

I was thinking things along the lines of :

@srchase srchase force-pushed the improve-definition-locations branch 2 times, most recently from 0b29f1c to 29b4070 Compare April 28, 2022 14:40
@srchase srchase force-pushed the improve-definition-locations branch from 29b4070 to a8405e4 Compare April 28, 2022 17:45
private static String getUri(String fileName) {
return Utils.isJarFile(fileName)
? Utils.toSmithyJarFile(fileName)
: !fileName.startsWith("file:") ? "file:" + fileName
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider breaking this nested ternary out for readability.

return Optional.ofNullable(matchingShapes.get(0).getKey());
}

private Predicate<Map.Entry<ShapeId, Location>> filterByLocation(Position position) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it should be a Predicate<Location> instead, since it doesn't use the key at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this looks a bit awkward, but the key (the shapeId) is what needs to be carried forward. If I mapped to a location first, I'd lose the reference to the shapeId. I did simplify this a bit by mapping to a shapeId in the stream after filtering.


// Find the end of a member's location by first trimming trailing commas, empty lines and closing
// structure braces.
if (shape.getType() == ShapeType.MEMBER) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it reasonable to break these branches up in to their own methods for readability?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I broke out the non-member branch since it only updates the end marker, but the non-member branch is responsible for updating two separate markers and the end position of a member shape. I think it made readability worse for separate method to do all of that.

// Use location on target, or else default to initial shape.
locations = Collections.singletonList(project.getLocations().get(target.orElse(initialShapeId.get())));
} else {
// If cursor location doesn't correspond to definition, return locations of all shapes that match token.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this comment means.

@@ -254,8 +262,30 @@ private String findToken(String path, Position p) throws IOException {
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(
DefinitionParams params) {
try {
List<Location> locations;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have a description of what this is doing behaviorally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added with an example.

@srchase srchase dismissed JordonPhillips’s stale review April 29, 2022 17:52

1.22.0 not yet released.

@srchase srchase merged commit 6cd1f26 into smithy-lang:main Apr 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants