Skip to content

Commit d20c5c2

Browse files
kubukozsrchase
andauthored
Implement basic textDocument/documentSymbol (#99)
* Implement basic textDocument/documentSymbol --------- Co-authored-by: Chase Coalwell <782571+srchase@users.noreply.github.com>
1 parent 6933dfe commit d20c5c2

File tree

7 files changed

+169
-4
lines changed

7 files changed

+169
-4
lines changed

src/main/java/software/amazon/smithy/lsp/ProtocolAdapter.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515

1616
package software.amazon.smithy.lsp;
1717

18+
import java.util.Optional;
1819
import org.eclipse.lsp4j.Diagnostic;
1920
import org.eclipse.lsp4j.DiagnosticSeverity;
2021
import org.eclipse.lsp4j.Position;
2122
import org.eclipse.lsp4j.Range;
23+
import org.eclipse.lsp4j.SymbolKind;
24+
import software.amazon.smithy.model.shapes.ShapeType;
2225
import software.amazon.smithy.model.validation.Severity;
2326
import software.amazon.smithy.model.validation.ValidationEvent;
2427

@@ -61,4 +64,64 @@ public static DiagnosticSeverity toDiagnosticSeverity(Severity severity) {
6164
return DiagnosticSeverity.Hint;
6265
}
6366
}
67+
68+
/**
69+
* @param shapeType The type to be converted to a SymbolKind
70+
* @param parentType An optional type of the shape's enclosing definition
71+
* @return An lsp4j SymbolKind
72+
*/
73+
public static SymbolKind toSymbolKind(ShapeType shapeType, Optional<ShapeType> parentType) {
74+
switch (shapeType) {
75+
case BYTE:
76+
case BIG_INTEGER:
77+
case DOUBLE:
78+
case BIG_DECIMAL:
79+
case FLOAT:
80+
case LONG:
81+
case INTEGER:
82+
case SHORT:
83+
return SymbolKind.Number;
84+
case BLOB:
85+
// technically a sequence of bytes, so due to the lack of a better alternative, an array
86+
case LIST:
87+
case SET:
88+
return SymbolKind.Array;
89+
case BOOLEAN:
90+
return SymbolKind.Boolean;
91+
case STRING:
92+
return SymbolKind.String;
93+
case TIMESTAMP:
94+
case UNION:
95+
return SymbolKind.Interface;
96+
97+
case DOCUMENT:
98+
return SymbolKind.Class;
99+
case ENUM:
100+
case INT_ENUM:
101+
return SymbolKind.Enum;
102+
case MAP:
103+
return SymbolKind.Object;
104+
case STRUCTURE:
105+
return SymbolKind.Struct;
106+
case MEMBER:
107+
if (!parentType.isPresent()) {
108+
return SymbolKind.Field;
109+
}
110+
switch (parentType.get()) {
111+
case ENUM:
112+
return SymbolKind.EnumMember;
113+
case UNION:
114+
return SymbolKind.Class;
115+
default: return SymbolKind.Field;
116+
}
117+
case SERVICE:
118+
case RESOURCE:
119+
return SymbolKind.Module;
120+
case OPERATION:
121+
return SymbolKind.Method;
122+
default:
123+
// This case shouldn't be reachable
124+
return SymbolKind.Key;
125+
}
126+
}
64127
}

src/main/java/software/amazon/smithy/lsp/SmithyLanguageServer.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package software.amazon.smithy.lsp;
1717

1818
import java.io.File;
19-
import java.io.FileNotFoundException;
2019
import java.io.IOException;
2120
import java.net.URI;
2221
import java.nio.file.Files;
@@ -45,7 +44,6 @@
4544
import org.eclipse.lsp4j.services.WorkspaceService;
4645
import software.amazon.smithy.lsp.codeactions.SmithyCodeActions;
4746
import software.amazon.smithy.lsp.ext.LspLog;
48-
import software.amazon.smithy.lsp.ext.ValidationException;
4947
import software.amazon.smithy.utils.ListUtils;
5048

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

62-
private void loadSmithyBuild(File root) throws ValidationException, FileNotFoundException {
60+
private void loadSmithyBuild(File root) {
6361
this.tds.ifPresent(tds -> tds.createProject(root));
6462
}
6563

@@ -108,6 +106,7 @@ public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
108106
capabilities.setCompletionProvider(new CompletionOptions(true, null));
109107
capabilities.setHoverProvider(true);
110108
capabilities.setDocumentFormattingProvider(true);
109+
capabilities.setDocumentSymbolProvider(true);
111110

112111
return Utils.completableFuture(new InitializeResult(capabilities));
113112
}

src/main/java/software/amazon/smithy/lsp/SmithyTextDocumentService.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import com.google.common.hash.Hashing;
2020
import java.io.File;
2121
import java.io.IOException;
22+
import java.io.UnsupportedEncodingException;
2223
import java.net.URI;
2324
import java.net.URISyntaxException;
25+
import java.net.URLDecoder;
2426
import java.nio.charset.StandardCharsets;
2527
import java.nio.file.Files;
2628
import java.nio.file.Path;
@@ -49,6 +51,8 @@
4951
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
5052
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
5153
import org.eclipse.lsp4j.DocumentFormattingParams;
54+
import org.eclipse.lsp4j.DocumentSymbol;
55+
import org.eclipse.lsp4j.DocumentSymbolParams;
5256
import org.eclipse.lsp4j.Hover;
5357
import org.eclipse.lsp4j.HoverParams;
5458
import org.eclipse.lsp4j.Location;
@@ -59,6 +63,8 @@
5963
import org.eclipse.lsp4j.Position;
6064
import org.eclipse.lsp4j.PublishDiagnosticsParams;
6165
import org.eclipse.lsp4j.Range;
66+
import org.eclipse.lsp4j.SymbolInformation;
67+
import org.eclipse.lsp4j.SymbolKind;
6268
import org.eclipse.lsp4j.TextDocumentIdentifier;
6369
import org.eclipse.lsp4j.TextDocumentItem;
6470
import org.eclipse.lsp4j.TextEdit;
@@ -85,6 +91,7 @@
8591
import software.amazon.smithy.model.neighbor.Walker;
8692
import software.amazon.smithy.model.shapes.Shape;
8793
import software.amazon.smithy.model.shapes.ShapeId;
94+
import software.amazon.smithy.model.shapes.ShapeType;
8895
import software.amazon.smithy.model.shapes.SmithyIdlModelSerializer;
8996
import software.amazon.smithy.model.validation.ValidatedResult;
9097
import software.amazon.smithy.model.validation.ValidationEvent;
@@ -322,6 +329,9 @@ private File designatedTemporaryFile(File source) {
322329
return new File(this.temporaryFolder, hashed + Constants.SMITHY_EXTENSION);
323330
}
324331

332+
/**
333+
* @return lines in the file or buffer
334+
*/
325335
private List<String> textBufferContents(String path) throws IOException {
326336
List<String> contents;
327337
if (Utils.isSmithyJarFile(path)) {
@@ -388,6 +398,58 @@ private String getLine(List<String> lines, Position position) {
388398
return lines.get(position.getLine());
389399
}
390400

401+
@Override
402+
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(
403+
DocumentSymbolParams params
404+
) {
405+
try {
406+
Map<ShapeId, Location> locations = project.getLocations();
407+
Model model = project.getModel().unwrap();
408+
409+
List<DocumentSymbol> symbols = new ArrayList<>();
410+
411+
URI documentUri = documentIdentifierToUri(params.getTextDocument());
412+
413+
locations.forEach((shapeId, loc) -> {
414+
String[] locSegments = loc.getUri().replace("\\", "/").split(":");
415+
boolean matchesDocument = documentUri.toString().endsWith(locSegments[locSegments.length - 1]);
416+
417+
if (!matchesDocument) {
418+
return;
419+
}
420+
421+
Shape shape = model.expectShape(shapeId);
422+
423+
Optional<ShapeType> parentType = shape.isMemberShape()
424+
? Optional.of(model.expectShape(shapeId.withoutMember()).getType())
425+
: Optional.empty();
426+
427+
SymbolKind kind = ProtocolAdapter.toSymbolKind(shape.getType(), parentType);
428+
429+
String symbolName = shapeId.getMember().orElse(shapeId.getName());
430+
431+
symbols.add(new DocumentSymbol(symbolName, kind, loc.getRange(), loc.getRange()));
432+
});
433+
434+
return Utils.completableFuture(
435+
symbols
436+
.stream()
437+
.map(Either::<SymbolInformation, DocumentSymbol>forRight)
438+
.collect(Collectors.toList())
439+
);
440+
} catch (Exception e) {
441+
e.printStackTrace(System.err);
442+
443+
return Utils.completableFuture(Collections.emptyList());
444+
}
445+
}
446+
447+
private URI documentIdentifierToUri(TextDocumentIdentifier ident) throws UnsupportedEncodingException {
448+
return Utils.isSmithyJarFile(ident.getUri())
449+
? URI.create(URLDecoder.decode(ident.getUri(), StandardCharsets.UTF_8.name()))
450+
: this.fileUri(ident).toURI();
451+
}
452+
391453
@Override
392454
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(
393455
DefinitionParams params) {

src/main/java/software/amazon/smithy/lsp/Utils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public static <U> CompletableFuture<U> completableFuture(U value) {
5656
* @return Returns whether the uri points to a file in jar.
5757
* @throws IOException when rawUri cannot be URL-decoded
5858
*/
59-
public static boolean isSmithyJarFile(String rawUri) throws IOException {
59+
public static boolean isSmithyJarFile(String rawUri) {
6060
try {
6161
String uri = java.net.URLDecoder.decode(rawUri, StandardCharsets.UTF_8.name());
6262
return uri.startsWith("smithyjar:");

src/test/java/software/amazon/smithy/lsp/SmithyTextDocumentServiceTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
4040
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
4141
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
42+
import org.eclipse.lsp4j.DocumentSymbol;
43+
import org.eclipse.lsp4j.DocumentSymbolParams;
4244
import org.eclipse.lsp4j.Hover;
4345
import org.eclipse.lsp4j.HoverParams;
4446
import org.eclipse.lsp4j.Location;
@@ -49,6 +51,8 @@
4951
import org.eclipse.lsp4j.PublishDiagnosticsParams;
5052
import org.eclipse.lsp4j.Range;
5153
import org.eclipse.lsp4j.ShowMessageRequestParams;
54+
import org.eclipse.lsp4j.SymbolInformation;
55+
import org.eclipse.lsp4j.SymbolKind;
5256
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
5357
import org.eclipse.lsp4j.TextDocumentIdentifier;
5458
import org.eclipse.lsp4j.TextDocumentItem;
@@ -863,6 +867,35 @@ public void ensureVersionDiagnostic() throws Exception {
863867

864868
}
865869

870+
@Test
871+
public void documentSymbols() throws Exception {
872+
Path baseDir = Paths.get(SmithyProjectTest.class.getResource("models/document-symbols").toURI());
873+
874+
String currentFile = "current.smithy";
875+
String anotherFile = "another.smithy";
876+
877+
List<Path> files = ListUtils.of(baseDir.resolve(currentFile),baseDir.resolve(anotherFile));
878+
879+
try (Harness hs = Harness.create(SmithyBuildExtensions.builder().build(), files)) {
880+
SmithyTextDocumentService tds = new SmithyTextDocumentService(Optional.empty(), hs.getTempFolder());
881+
tds.createProject(hs.getConfig(), hs.getRoot());
882+
883+
TextDocumentIdentifier currentDocumentIdent = new TextDocumentIdentifier(uri(hs.file(currentFile)));
884+
885+
List<Either<SymbolInformation, DocumentSymbol>> symbols =
886+
tds.documentSymbol(new DocumentSymbolParams(currentDocumentIdent)).get();
887+
888+
assertEquals(2, symbols.size());
889+
890+
assertEquals("city", symbols.get(0).getRight().getName());
891+
assertEquals(SymbolKind.Field, symbols.get(0).getRight().getKind());
892+
893+
assertEquals("Weather", symbols.get(1).getRight().getName());
894+
assertEquals(SymbolKind.Struct, symbols.get(1).getRight().getKind());
895+
}
896+
897+
}
898+
866899
private static class StubClient implements LanguageClient {
867900
public List<PublishDiagnosticsParams> diagnostics = new ArrayList<>();
868901
public List<MessageParams> shown = new ArrayList<>();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
$version: "2"
2+
namespace test
3+
structure City { }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
$version: "2"
2+
namespace test
3+
structure Weather {
4+
@required city: City
5+
}

0 commit comments

Comments
 (0)