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

Implement basic textDocument/documentSymbol #99

Merged
merged 6 commits into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/main/java/software/amazon/smithy/lsp/ProtocolAdapter.java
Original file line number Diff line number Diff line change
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;
}
}
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
2 changes: 1 addition & 1 deletion src/main/java/software/amazon/smithy/lsp/Utils.java
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 @@ -863,6 +867,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
$version: "2"
namespace test
structure City { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
$version: "2"
namespace test
structure Weather {
@required city: City
}