Skip to content

Commit 1c720cd

Browse files
committed
Properly implement document/documentSymbol
When I re-wrote the language feature implementations in #166, I refactored documentSymbol. But it mostly just copied the previous implementation, which produced a flat list of symbols. This meant that member symbols would appear at the top-level, but documentSymbol is meant to provide a tree-like view, where symbols can have children. So this commit re-writes documentSymbol to produce a list of only the top-level shapes' symbols (and the namespace statement's symbol), with members' symbols being added as children to the top-level symbols, and members of inline i/o are children of the inline i/o symbol. I also changed up the Symbol kind a bit, so root shapes are of type `Class` (except enum/intEnum, which are of type `Enum`), members are of type `Field` (except enum/intEnum members, which are of type `EnumMember`), and service/resource/operation members are of type `Property`. I explored having different symbol kinds for different types of shapes, like making service-type shapes be of kind `Interface`, and primitive types being of their corresponding kind (for example, an integer shape would have kind `Number`), but I wasn't sure what some shape types should be. For example, what should a `blob` shape be? So I decided to just make all top-level shapes just be of kind `Class`. I also fixed the range and selection range of symbols. Range now covers everything from shape type -> end of block (if applicable), and selection range is just the shape/member name. I considered adding more children for service-type shape members, like making the `operations` child symbol of a service shape have a child for each of the operations in the list, but I think it would make the tree view more noisy, plus I think the intent is to show symbol definitions. I also made a minor adjustment to the parsing of operations members, making them always be inline or node member defs, instead of possibly a regular member def for non-inline i/o. This is consistent with resource/service members, which are always node members.
1 parent 21f0d5c commit 1c720cd

File tree

4 files changed

+490
-70
lines changed

4 files changed

+490
-70
lines changed

src/main/java/software/amazon/smithy/lsp/language/DocumentSymbolHandler.java

Lines changed: 178 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,210 @@
55

66
package software.amazon.smithy.lsp.language;
77

8+
import static software.amazon.smithy.lsp.protocol.LspAdapter.identRange;
9+
10+
import java.util.ArrayList;
11+
import java.util.EnumSet;
812
import java.util.List;
13+
import java.util.ListIterator;
914
import java.util.function.Consumer;
1015
import org.eclipse.lsp4j.DocumentSymbol;
11-
import org.eclipse.lsp4j.Range;
1216
import org.eclipse.lsp4j.SymbolInformation;
1317
import org.eclipse.lsp4j.SymbolKind;
1418
import org.eclipse.lsp4j.jsonrpc.messages.Either;
1519
import software.amazon.smithy.lsp.document.Document;
16-
import software.amazon.smithy.lsp.protocol.LspAdapter;
1720
import software.amazon.smithy.lsp.syntax.Syntax;
1821

1922
public record DocumentSymbolHandler(Document document, List<Syntax.Statement> statements) {
23+
// Statement types that may appear before the start of a shape's members, which
24+
// we need to skip.
25+
private static final EnumSet<Syntax.Statement.Type> BEFORE_MEMBER_TYPES = EnumSet.of(
26+
Syntax.Statement.Type.ForResource,
27+
Syntax.Statement.Type.Mixins,
28+
Syntax.Statement.Type.Block
29+
);
30+
2031
/**
2132
* @return A list of DocumentSymbol
2233
*/
2334
public List<Either<SymbolInformation, DocumentSymbol>> handle() {
24-
return statements.stream()
25-
.mapMulti(this::addSymbols)
26-
.toList();
35+
List<Either<SymbolInformation, DocumentSymbol>> result = new ArrayList<>();
36+
// Passing around the list would make the code super noisy, and we'd have
37+
// to do Either.forRight everywhere, so use a consumer.
38+
addSymbols((symbol) -> result.add(Either.forRight(symbol)));
39+
return result;
2740
}
2841

29-
private void addSymbols(Syntax.Statement statement, Consumer<Either<SymbolInformation, DocumentSymbol>> consumer) {
30-
switch (statement) {
31-
case Syntax.Statement.TraitApplication app -> addSymbol(consumer, app.id(), SymbolKind.Class);
42+
private void addSymbols(Consumer<DocumentSymbol> consumer) {
43+
var listIterator = statements.listIterator();
44+
while (listIterator.hasNext()) {
45+
var statement = listIterator.next();
46+
if (statement instanceof Syntax.Statement.Namespace namespace) {
47+
consumer.accept(namespaceSymbol(namespace));
48+
} else if (statement instanceof Syntax.Statement.ShapeDef shapeDef) {
49+
var symbol = rootSymbol(shapeDef);
50+
consumer.accept(symbol);
51+
addMemberSymbols(listIterator, symbol);
52+
}
53+
}
54+
}
3255

33-
case Syntax.Statement.ShapeDef def -> addSymbol(consumer, def.shapeName(), SymbolKind.Class);
56+
private void addMemberSymbols(ListIterator<Syntax.Statement> listIterator, DocumentSymbol parent) {
57+
// We only want to collect members within the block, so we can use Block's lastMemberIndex
58+
// to tell us when to stop.
59+
int lastMemberIndex = 0;
60+
while (listIterator.hasNext()) {
61+
var statement = listIterator.next();
3462

35-
case Syntax.Statement.EnumMemberDef def -> addSymbol(consumer, def.name(), SymbolKind.Enum);
63+
if (!BEFORE_MEMBER_TYPES.contains(statement.type())) {
64+
// No members
65+
listIterator.previous();
66+
return;
67+
}
3668

37-
case Syntax.Statement.ElidedMemberDef def -> addSymbol(consumer, def.name(), SymbolKind.Property);
69+
if (statement instanceof Syntax.Statement.Block block) {
70+
// Update the parent's range to cover all its members
71+
var blockEnd = document.positionAtIndex(block.end());
72+
parent.getRange().setEnd(blockEnd);
73+
lastMemberIndex = block.lastStatementIndex();
74+
break;
75+
}
76+
}
77+
78+
List<DocumentSymbol> children = new ArrayList<>();
3879

39-
case Syntax.Statement.MemberDef def -> {
40-
addSymbol(consumer, def.name(), SymbolKind.Property);
41-
if (def.target() != null) {
42-
addSymbol(consumer, def.target(), SymbolKind.Class);
80+
while (listIterator.nextIndex() <= lastMemberIndex) {
81+
var statement = listIterator.next();
82+
83+
switch (statement) {
84+
case Syntax.Statement.MemberDef memberDef -> {
85+
children.add(memberDefSymbol(memberDef));
86+
}
87+
88+
case Syntax.Statement.EnumMemberDef enumMemberDef -> {
89+
children.add(enumMemberDefSymbol(enumMemberDef));
90+
}
91+
92+
case Syntax.Statement.ElidedMemberDef elidedMemberDef -> {
93+
children.add(elidedMemberDefSymbol(elidedMemberDef));
94+
}
95+
96+
case Syntax.Statement.NodeMemberDef nodeMemberDef -> {
97+
children.add(nodeMemberDefSymbol(nodeMemberDef));
98+
}
99+
100+
case Syntax.Statement.InlineMemberDef inlineMemberDef -> {
101+
children.add(inlineMemberSymbol(listIterator, inlineMemberDef));
102+
}
103+
104+
default -> {
43105
}
44-
}
45-
default -> {
46106
}
47107
}
108+
109+
if (!children.isEmpty()) {
110+
parent.setChildren(children);
111+
}
48112
}
49113

50-
private void addSymbol(
51-
Consumer<Either<SymbolInformation, DocumentSymbol>> consumer,
52-
Syntax.Ident ident,
53-
SymbolKind symbolKind
54-
) {
55-
Range range = LspAdapter.identRange(ident, document);
56-
if (range == null) {
57-
return;
114+
private DocumentSymbol namespaceSymbol(Syntax.Statement.Namespace namespace) {
115+
var range = document.rangeBetween(namespace.start(), namespace.end());
116+
var selectionRange = identRange(namespace.namespace(), document);
117+
return new DocumentSymbol(
118+
namespace.namespace().stringValue(),
119+
SymbolKind.Namespace,
120+
range,
121+
selectionRange
122+
);
123+
}
124+
125+
private DocumentSymbol rootSymbol(Syntax.Statement.ShapeDef shapeDef) {
126+
var symbolKind = getSymbolKind(shapeDef);
127+
var range = document.rangeBetween(shapeDef.start(), shapeDef.end());
128+
var selectionRange = identRange(shapeDef.shapeName(), document);
129+
return new DocumentSymbol(
130+
shapeDef.shapeName().stringValue(),
131+
symbolKind,
132+
range,
133+
selectionRange
134+
);
135+
}
136+
137+
private static SymbolKind getSymbolKind(Syntax.Statement.ShapeDef shapeDef) {
138+
return switch (shapeDef.shapeType().stringValue()) {
139+
case "enum", "intEnum" -> SymbolKind.Enum;
140+
default -> SymbolKind.Class;
141+
};
142+
}
143+
144+
private DocumentSymbol memberDefSymbol(Syntax.Statement.MemberDef memberDef) {
145+
var range = document.rangeBetween(memberDef.start(), memberDef.end());
146+
var selectionRange = identRange(memberDef.name(), document);
147+
var detail = memberDef.target() == null
148+
? null
149+
: memberDef.target().stringValue();
150+
151+
return new DocumentSymbol(
152+
memberDef.name().stringValue(),
153+
SymbolKind.Field,
154+
range,
155+
selectionRange,
156+
detail
157+
);
158+
}
159+
160+
private DocumentSymbol enumMemberDefSymbol(Syntax.Statement.EnumMemberDef enumMemberDef) {
161+
var range = document.rangeBetween(enumMemberDef.start(), enumMemberDef.end());
162+
var selectionRange = identRange(enumMemberDef.name(), document);
163+
return new DocumentSymbol(
164+
enumMemberDef.name().stringValue(),
165+
SymbolKind.EnumMember,
166+
range,
167+
selectionRange
168+
);
169+
}
170+
171+
private DocumentSymbol elidedMemberDefSymbol(Syntax.Statement.ElidedMemberDef elidedMemberDef) {
172+
var range = document.rangeBetween(elidedMemberDef.start(), elidedMemberDef.end());
173+
return new DocumentSymbol(
174+
"$" + elidedMemberDef.name().stringValue(),
175+
SymbolKind.Field,
176+
range,
177+
range
178+
);
179+
}
180+
181+
private DocumentSymbol nodeMemberDefSymbol(Syntax.Statement.NodeMemberDef nodeMemberDef) {
182+
var range = document.rangeBetween(nodeMemberDef.start(), nodeMemberDef.end());
183+
var selectionRange = identRange(nodeMemberDef.name(), document);
184+
String detail = null;
185+
if (nodeMemberDef.value() instanceof Syntax.Ident ident) {
186+
detail = ident.stringValue();
58187
}
59188

60-
DocumentSymbol symbol = new DocumentSymbol(ident.stringValue(), symbolKind, range, range);
61-
consumer.accept(Either.forRight(symbol));
189+
return new DocumentSymbol(
190+
nodeMemberDef.name().stringValue(),
191+
SymbolKind.Property,
192+
range,
193+
selectionRange,
194+
detail
195+
);
196+
}
197+
198+
private DocumentSymbol inlineMemberSymbol(
199+
ListIterator<Syntax.Statement> listIterator,
200+
Syntax.Statement.InlineMemberDef inlineMemberDef
201+
) {
202+
var range = document.rangeBetween(inlineMemberDef.start(), inlineMemberDef.end());
203+
var selectionRange = identRange(inlineMemberDef.name(), document);
204+
var inlineSymbol = new DocumentSymbol(
205+
inlineMemberDef.name().stringValue(),
206+
SymbolKind.Property,
207+
range,
208+
selectionRange
209+
);
210+
211+
addMemberSymbols(listIterator, inlineSymbol);
212+
return inlineSymbol;
62213
}
63214
}

src/main/java/software/amazon/smithy/lsp/syntax/Parser.java

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -642,21 +642,12 @@ private void operationMembers(Syntax.Statement.Block parent) {
642642

643643
ws();
644644

645-
if (isIdentStart()) {
646-
var opMemberDef = new Syntax.Statement.MemberDef(parent, memberName);
647-
opMemberDef.start = opMemberStart;
648-
opMemberDef.colonPos = colonPos;
649-
opMemberDef.target = ident();
650-
setEnd(opMemberDef);
651-
addStatement(opMemberDef);
652-
} else {
653-
var nodeMemberDef = new Syntax.Statement.NodeMemberDef(parent, memberName);
654-
nodeMemberDef.start = opMemberStart;
655-
nodeMemberDef.colonPos = colonPos;
656-
nodeMemberDef.value = parseNode();
657-
setEnd(nodeMemberDef);
658-
addStatement(nodeMemberDef);
659-
}
645+
var nodeMemberDef = new Syntax.Statement.NodeMemberDef(parent, memberName);
646+
nodeMemberDef.start = opMemberStart;
647+
nodeMemberDef.colonPos = colonPos;
648+
nodeMemberDef.value = parseNode();
649+
setEnd(nodeMemberDef);
650+
addStatement(nodeMemberDef);
660651

661652
ws();
662653
}

0 commit comments

Comments
 (0)