Skip to content

Commit

Permalink
Merge pull request #41 from disneystreaming/upstream-2023-05
Browse files Browse the repository at this point in the history
Sync with upstream (2023-05)
  • Loading branch information
daddykotex committed May 5, 2023
2 parents d8aa2c5 + 6237550 commit 1bac608
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 29 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Expand Up @@ -157,9 +157,9 @@ publishing {

dependencies {
implementation "org.eclipse.lsp4j:org.eclipse.lsp4j:0.14.0"
implementation "software.amazon.smithy:smithy-model:[1.30.0, 2.0["
implementation "software.amazon.smithy:smithy-model:[1.31.0, 2.0["
implementation 'io.get-coursier:interface:1.0.4'
implementation 'com.disneystreaming.smithy:smithytranslate-formatter-jvm-java-api:0.3.3'
implementation 'com.disneystreaming.smithy:smithytranslate-formatter-jvm-java-api:0.3.4'

// Use JUnit test framework
testImplementation "junit:junit:4.13"
Expand Down
4 changes: 2 additions & 2 deletions build.sc
Expand Up @@ -9,9 +9,9 @@ object lsp extends MavenModule with PublishModule {

def ivyDeps = Agg(
ivy"org.eclipse.lsp4j:org.eclipse.lsp4j:0.14.0",
ivy"software.amazon.smithy:smithy-model:1.30.0",
ivy"software.amazon.smithy:smithy-model:1.31.0",
ivy"io.get-coursier:interface:1.0.4",
ivy"com.disneystreaming.smithy:smithytranslate-formatter-jvm-java-api:0.3.3"
ivy"com.disneystreaming.smithy:smithytranslate-formatter-jvm-java-api:0.3.4"
)

def publishVersion = T { gitVersion() }
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/software/amazon/smithy/lsp/ProtocolAdapter.java
Expand Up @@ -15,10 +15,13 @@

package software.amazon.smithy.lsp;

import java.util.Optional;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolKind;
import software.amazon.smithy.model.shapes.ShapeType;
import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidationEvent;

Expand Down Expand Up @@ -61,4 +64,64 @@ public static DiagnosticSeverity toDiagnosticSeverity(Severity severity) {
return DiagnosticSeverity.Hint;
}
}

/**
* @param shapeType The type to be converted to a SymbolKind
* @param parentType An optional type of the shape's enclosing definition
* @return An lsp4j SymbolKind
*/
public static SymbolKind toSymbolKind(ShapeType shapeType, Optional<ShapeType> parentType) {
switch (shapeType) {
case BYTE:
case BIG_INTEGER:
case DOUBLE:
case BIG_DECIMAL:
case FLOAT:
case LONG:
case INTEGER:
case SHORT:
return SymbolKind.Number;
case BLOB:
// technically a sequence of bytes, so due to the lack of a better alternative, an array
case LIST:
case SET:
return SymbolKind.Array;
case BOOLEAN:
return SymbolKind.Boolean;
case STRING:
return SymbolKind.String;
case TIMESTAMP:
case UNION:
return SymbolKind.Interface;

case DOCUMENT:
return SymbolKind.Class;
case ENUM:
case INT_ENUM:
return SymbolKind.Enum;
case MAP:
return SymbolKind.Object;
case STRUCTURE:
return SymbolKind.Struct;
case MEMBER:
if (!parentType.isPresent()) {
return SymbolKind.Field;
}
switch (parentType.get()) {
case ENUM:
return SymbolKind.EnumMember;
case UNION:
return SymbolKind.Class;
default: return SymbolKind.Field;
}
case SERVICE:
case RESOURCE:
return SymbolKind.Module;
case OPERATION:
return SymbolKind.Method;
default:
// This case shouldn't be reachable
return SymbolKind.Key;
}
}
}
Expand Up @@ -16,7 +16,6 @@
package software.amazon.smithy.lsp;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
Expand Down Expand Up @@ -45,7 +44,6 @@
import org.eclipse.lsp4j.services.WorkspaceService;
import software.amazon.smithy.lsp.codeactions.SmithyCodeActions;
import software.amazon.smithy.lsp.ext.LspLog;
import software.amazon.smithy.lsp.ext.ValidationException;
import software.amazon.smithy.utils.ListUtils;

public class SmithyLanguageServer implements LanguageServer, LanguageClientAware, SmithyProtocolExtensions {
Expand All @@ -59,7 +57,7 @@ public CompletableFuture<Object> shutdown() {
return Utils.completableFuture(new Object());
}

private void loadSmithyBuild(File root) throws ValidationException, FileNotFoundException {
private void loadSmithyBuild(File root) {
this.tds.ifPresent(tds -> tds.createProject(root));
}

Expand Down Expand Up @@ -108,6 +106,7 @@ public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
capabilities.setCompletionProvider(new CompletionOptions(true, null));
capabilities.setHoverProvider(true);
capabilities.setDocumentFormattingProvider(true);
capabilities.setDocumentSymbolProvider(true);

return Utils.completableFuture(new InitializeResult(capabilities));
}
Expand Down
Expand Up @@ -19,8 +19,10 @@
import com.google.common.hash.Hashing;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -49,6 +51,8 @@
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.DocumentFormattingParams;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.Location;
Expand All @@ -59,6 +63,8 @@
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextEdit;
Expand All @@ -85,6 +91,7 @@
import software.amazon.smithy.model.neighbor.Walker;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.ShapeType;
import software.amazon.smithy.model.shapes.SmithyIdlModelSerializer;
import software.amazon.smithy.model.validation.ValidatedResult;
import software.amazon.smithy.model.validation.ValidationEvent;
Expand Down Expand Up @@ -322,6 +329,9 @@ private File designatedTemporaryFile(File source) {
return new File(this.temporaryFolder, hashed + Constants.SMITHY_EXTENSION);
}

/**
* @return lines in the file or buffer
*/
private List<String> textBufferContents(String path) throws IOException {
List<String> contents;
if (Utils.isSmithyJarFile(path)) {
Expand Down Expand Up @@ -388,6 +398,58 @@ private String getLine(List<String> lines, Position position) {
return lines.get(position.getLine());
}

@Override
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(
DocumentSymbolParams params
) {
try {
Map<ShapeId, Location> locations = project.getLocations();
Model model = project.getModel().unwrap();

List<DocumentSymbol> symbols = new ArrayList<>();

URI documentUri = documentIdentifierToUri(params.getTextDocument());

locations.forEach((shapeId, loc) -> {
String[] locSegments = loc.getUri().replace("\\", "/").split(":");
boolean matchesDocument = documentUri.toString().endsWith(locSegments[locSegments.length - 1]);

if (!matchesDocument) {
return;
}

Shape shape = model.expectShape(shapeId);

Optional<ShapeType> parentType = shape.isMemberShape()
? Optional.of(model.expectShape(shapeId.withoutMember()).getType())
: Optional.empty();

SymbolKind kind = ProtocolAdapter.toSymbolKind(shape.getType(), parentType);

String symbolName = shapeId.getMember().orElse(shapeId.getName());

symbols.add(new DocumentSymbol(symbolName, kind, loc.getRange(), loc.getRange()));
});

return Utils.completableFuture(
symbols
.stream()
.map(Either::<SymbolInformation, DocumentSymbol>forRight)
.collect(Collectors.toList())
);
} catch (Exception e) {
e.printStackTrace(System.err);

return Utils.completableFuture(Collections.emptyList());
}
}

private URI documentIdentifierToUri(TextDocumentIdentifier ident) throws UnsupportedEncodingException {
return Utils.isSmithyJarFile(ident.getUri())
? URI.create(URLDecoder.decode(ident.getUri(), StandardCharsets.UTF_8.name()))
: this.fileUri(ident).toURI();
}

@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(
DefinitionParams params) {
Expand Down Expand Up @@ -489,21 +551,21 @@ private Optional<Shape> getTargetShape(Shape initialShape, String token, Model m
}

private String getHoverContentsForShape(Shape shape, Model model) {
try {
String serializedShape = serializeShape(shape, model);
List<ValidationEvent> validationEvents = getValidationEventsForShape(shape);
String serializedShape = serializeShape(shape, model);
if (validationEvents.isEmpty()) {
return "```smithy\n" + serializedShape + "\n```";
} catch (Exception e) {
List<ValidationEvent> validationEvents = getValidationEventsForShape(shape);
StringBuilder contents = new StringBuilder();
contents.append("Can't display shape ").append("`").append(shape.getId().toString()).append("`:");
for (ValidationEvent event : validationEvents) {
contents.append(System.lineSeparator()).append(event.getMessage());
}
if (validationEvents.isEmpty()) {
contents.append(System.lineSeparator()).append(e);
}
return contents.toString();
}
StringBuilder contents = new StringBuilder();
contents.append("```smithy\n");
contents.append(serializedShape);
contents.append("\n");
contents.append("---\n");
for (ValidationEvent event : validationEvents) {
contents.append(event.getSeverity() + ": " + event.getMessage() + "\n");
}
contents.append("```");
return contents.toString();
}

private String serializeShape(Shape shape, Model model) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/software/amazon/smithy/lsp/Utils.java
Expand Up @@ -56,7 +56,7 @@ public static <U> CompletableFuture<U> completableFuture(U value) {
* @return Returns whether the uri points to a file in jar.
* @throws IOException when rawUri cannot be URL-decoded
*/
public static boolean isSmithyJarFile(String rawUri) throws IOException {
public static boolean isSmithyJarFile(String rawUri) {
try {
String uri = java.net.URLDecoder.decode(rawUri, StandardCharsets.UTF_8.name());
return uri.startsWith("smithyjar:");
Expand Down
Expand Up @@ -39,6 +39,8 @@
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.Location;
Expand All @@ -49,6 +51,8 @@
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ShowMessageRequestParams;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
Expand Down Expand Up @@ -210,7 +214,7 @@ public void allowsHoverWhenThereAreUnknownTraits() throws Exception {
}

@Test
public void hoverOnBrokenShapeShowsErrorMessage() throws Exception {
public void hoverOnBrokenShapeAppendsValidations() throws Exception {
Path baseDir = Paths.get(getClass().getResource("ext/models").toURI());
String modelFilename = "unknown-trait.smithy";
Path modelFilePath = baseDir.resolve(modelFilename);
Expand All @@ -223,7 +227,10 @@ public void hoverOnBrokenShapeShowsErrorMessage() throws Exception {
TextDocumentIdentifier tdi = new TextDocumentIdentifier(hs.file(modelFilename).toString());
Hover hover = tds.hover(hoverParams(tdi, 10, 13)).get();
MarkupContent hoverContent = hover.getContents().getRight();
assertTrue(hoverContent.getValue().startsWith("Can't display shape"));
assertEquals(hoverContent.getKind(),"markdown");
assertTrue(hoverContent.getValue().startsWith("```smithy"));
assertTrue(hoverContent.getValue().contains("structure Foo {}"));
assertTrue(hoverContent.getValue().contains("WARNING: Unable to resolve trait `com.external#unknownTrait`"));
}
}

Expand Down Expand Up @@ -549,9 +556,9 @@ public void hoverV1() throws Exception {

// Resolves via resource read.
Hover readHover = tds.hover(hoverParams(mainTdi, 76, 12)).get();
correctHover(mainHoverPrefix, "@http(\n method: \"PUT\"\n uri: \"/bar\"\n code: 200\n)\n@readonly\n"
+ "operation MyOperation {\n input: MyOperationInput\n output: MyOperationOutput\n"
+ " errors: [\n MyError\n ]\n}", readHover);
assertTrue(readHover.getContents().getRight().getValue().contains("@http(\n method: \"PUT\"\n "
+ "uri: \"/bar\"\n code: 200\n)\n@readonly\noperation MyOperation {\n input: "
+ "MyOperationInput\n output: MyOperationOutput\n errors: [\n MyError\n ]\n}"));

// Does not correspond to shape.
Hover noMatchHover = tds.hover(hoverParams(mainTdi, 0, 0)).get();
Expand Down Expand Up @@ -662,9 +669,9 @@ public void hoverV2() throws Exception {

// Resolves via resource read.
Hover readHover = tds.hover(hoverParams(mainTdi, 78, 12)).get();
correctHover(mainHoverPrefix, "@http(\n method: \"PUT\"\n uri: \"/bar\"\n code: 200\n)\n@readonly\n"
+ "operation MyOperation {\n input: MyOperationInput\n output: MyOperationOutput\n"
+ " errors: [\n MyError\n ]\n}", readHover);
assertTrue(readHover.getContents().getRight().getValue().contains("@http(\n method: \"PUT\"\n "
+ "uri: \"/bar\"\n code: 200\n)\n@readonly\noperation MyOperation {\n input: "
+ "MyOperationInput\n output: MyOperationOutput\n errors: [\n MyError\n ]\n}"));

// Does not correspond to shape.
Hover noMatchHover = tds.hover(hoverParams(mainTdi, 0, 0)).get();
Expand Down Expand Up @@ -863,6 +870,35 @@ public void ensureVersionDiagnostic() throws Exception {

}

@Test
public void documentSymbols() throws Exception {
Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/document-symbols").toURI());

String currentFile = "current.smithy";
String anotherFile = "another.smithy";

List<Path> files = ListUtils.of(baseDir.resolve(currentFile),baseDir.resolve(anotherFile));

try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) {
SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder());
tds.createProject(hs.getConfig(), hs.getRoot());

TextDocumentIdentifier currentDocumentIdent = new TextDocumentIdentifier(uri(hs.file(currentFile)));

List<Either<SymbolInformation, DocumentSymbol>> symbols =
tds.documentSymbol(new DocumentSymbolParams(currentDocumentIdent)).get();

assertEquals(2, symbols.size());

assertEquals("city", symbols.get(0).getRight().getName());
assertEquals(SymbolKind.Field, symbols.get(0).getRight().getKind());

assertEquals("Weather", symbols.get(1).getRight().getName());
assertEquals(SymbolKind.Struct, symbols.get(1).getRight().getKind());
}

}

private static class StubClient implements LanguageClient {
public List<PublishDiagnosticsParams> diagnostics = new ArrayList<>();
public List<MessageParams> shown = new ArrayList<>();
Expand Down
@@ -0,0 +1,3 @@
$version: "2"
namespace test
structure City { }
@@ -0,0 +1,5 @@
$version: "2"
namespace test
structure Weather {
@required city: City
}

0 comments on commit 1bac608

Please sign in to comment.