Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,59 +5,191 @@

package software.amazon.smithy.lsp.language;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Consumer;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import software.amazon.smithy.lsp.document.Document;
import software.amazon.smithy.lsp.protocol.LspAdapter;
import software.amazon.smithy.lsp.syntax.Syntax;

public record DocumentSymbolHandler(Document document, List<Syntax.Statement> statements) {
// Statement types that may appear before the start of a shape's members, which
// we need to skip.
private static final EnumSet<Syntax.Statement.Type> BEFORE_MEMBER_TYPES = EnumSet.of(
Syntax.Statement.Type.ForResource,
Syntax.Statement.Type.Mixins,
Syntax.Statement.Type.Block
);

/**
* @return A list of DocumentSymbol
*/
public List<Either<SymbolInformation, DocumentSymbol>> handle() {
return statements.stream()
.mapMulti(this::addSymbols)
.toList();
List<Either<SymbolInformation, DocumentSymbol>> result = new ArrayList<>();
// Passing around the list would make the code super noisy, and we'd have
// to do Either.forRight everywhere, so use a consumer.
addSymbols((symbol) -> result.add(Either.forRight(symbol)));
Copy link
Contributor

Choose a reason for hiding this comment

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

neat!

return result;
}

private void addSymbols(Consumer<DocumentSymbol> consumer) {
var listIterator = statements.listIterator();
while (listIterator.hasNext()) {
var statement = listIterator.next();
if (statement instanceof Syntax.Statement.Namespace namespace) {
consumer.accept(namespaceSymbol(namespace));
} else if (statement instanceof Syntax.Statement.ShapeDef shapeDef) {
var symbol = rootSymbol(shapeDef);
consumer.accept(symbol);
addMemberSymbols(listIterator, symbol);
}
}
}

private void addMemberSymbols(ListIterator<Syntax.Statement> listIterator, DocumentSymbol parent) {
// We only want to collect members within the block, so we can use Block's lastMemberIndex
// to tell us when to stop.
int lastMemberIndex = 0;
while (listIterator.hasNext()) {
var statement = listIterator.next();

if (!BEFORE_MEMBER_TYPES.contains(statement.type())) {
// No members
listIterator.previous();
return;
}

if (statement instanceof Syntax.Statement.Block block) {
// Update the parent's range to cover all its members
var blockEnd = document.positionAtIndex(block.end());
parent.getRange().setEnd(blockEnd);
lastMemberIndex = block.lastStatementIndex();
break;
}
}

List<DocumentSymbol> children = childrenSymbols(listIterator, lastMemberIndex);
if (!children.isEmpty()) {
parent.setChildren(children);
}
}

private void addSymbols(Syntax.Statement statement, Consumer<Either<SymbolInformation, DocumentSymbol>> consumer) {
switch (statement) {
case Syntax.Statement.TraitApplication app -> addSymbol(consumer, app.id(), SymbolKind.Class);
private List<DocumentSymbol> childrenSymbols(ListIterator<Syntax.Statement> listIterator, int lastChildIndex) {
List<DocumentSymbol> children = new ArrayList<>();

case Syntax.Statement.ShapeDef def -> addSymbol(consumer, def.shapeName(), SymbolKind.Class);
while (listIterator.nextIndex() <= lastChildIndex) {
var statement = listIterator.next();

case Syntax.Statement.EnumMemberDef def -> addSymbol(consumer, def.name(), SymbolKind.Enum);
switch (statement) {
case Syntax.Statement.MemberDef def -> children.add(memberDefSymbol(def));

case Syntax.Statement.ElidedMemberDef def -> addSymbol(consumer, def.name(), SymbolKind.Property);
case Syntax.Statement.EnumMemberDef def -> children.add(enumMemberDefSymbol(def));

case Syntax.Statement.MemberDef def -> {
addSymbol(consumer, def.name(), SymbolKind.Property);
if (def.target() != null) {
addSymbol(consumer, def.target(), SymbolKind.Class);
case Syntax.Statement.ElidedMemberDef def -> children.add(elidedMemberDefSymbol(def));

case Syntax.Statement.NodeMemberDef def -> children.add(nodeMemberDefSymbol(def));

case Syntax.Statement.InlineMemberDef def -> children.add(inlineMemberSymbol(listIterator, def));

default -> {
}
}
default -> {
}
}

return children;
}

private DocumentSymbol namespaceSymbol(Syntax.Statement.Namespace namespace) {
return new DocumentSymbol(
namespace.namespace().stringValue(),
SymbolKind.Namespace,
document.rangeOf(namespace),
document.rangeOfValue(namespace.namespace())
);
}

private DocumentSymbol rootSymbol(Syntax.Statement.ShapeDef shapeDef) {
return new DocumentSymbol(
shapeDef.shapeName().stringValue(),
getSymbolKind(shapeDef),
document.rangeOf(shapeDef),
document.rangeOfValue(shapeDef.shapeName())
);
}

private static SymbolKind getSymbolKind(Syntax.Statement.ShapeDef shapeDef) {
return switch (shapeDef.shapeType().stringValue()) {
case "enum", "intEnum" -> SymbolKind.Enum;
case "operation", "service", "resource" -> SymbolKind.Interface;
default -> SymbolKind.Class;
};
}

private DocumentSymbol memberDefSymbol(Syntax.Statement.MemberDef memberDef) {
var detail = memberDef.target() == null
? null
: memberDef.target().stringValue();

return new DocumentSymbol(
memberDef.name().stringValue(),
SymbolKind.Field,
document.rangeOf(memberDef),
document.rangeOfValue(memberDef.name()),
detail
);
}

private DocumentSymbol enumMemberDefSymbol(Syntax.Statement.EnumMemberDef enumMemberDef) {
return new DocumentSymbol(
enumMemberDef.name().stringValue(),
SymbolKind.EnumMember,
document.rangeOf(enumMemberDef),
document.rangeOfValue(enumMemberDef.name())
);
}

private DocumentSymbol elidedMemberDefSymbol(Syntax.Statement.ElidedMemberDef elidedMemberDef) {
var range = document.rangeOf(elidedMemberDef);
return new DocumentSymbol(
"$" + elidedMemberDef.name().stringValue(),
SymbolKind.Field,
range,
range
);
}

private void addSymbol(
Consumer<Either<SymbolInformation, DocumentSymbol>> consumer,
Syntax.Ident ident,
SymbolKind symbolKind
private DocumentSymbol nodeMemberDefSymbol(Syntax.Statement.NodeMemberDef nodeMemberDef) {
String detail = switch (nodeMemberDef.value()) {
case Syntax.Ident ident -> ident.stringValue();
case null, default -> null;
};

return new DocumentSymbol(
nodeMemberDef.name().stringValue(),
SymbolKind.Property,
document.rangeOf(nodeMemberDef),
document.rangeOfValue(nodeMemberDef.name()),
detail
);
}

private DocumentSymbol inlineMemberSymbol(
ListIterator<Syntax.Statement> listIterator,
Syntax.Statement.InlineMemberDef inlineMemberDef
) {
Range range = LspAdapter.identRange(ident, document);
if (range == null) {
return;
}
var inlineSymbol = new DocumentSymbol(
inlineMemberDef.name().stringValue(),
SymbolKind.Property,
document.rangeOf(inlineMemberDef),
document.rangeOfValue(inlineMemberDef.name())
);

DocumentSymbol symbol = new DocumentSymbol(ident.stringValue(), symbolKind, range, range);
consumer.accept(Either.forRight(symbol));
addMemberSymbols(listIterator, inlineSymbol);
return inlineSymbol;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,19 +120,7 @@ public static Range of(int startLine, int startCharacter, int endLine, int endCh
* @return The range of the identifier in the given document
*/
public static Range identRange(Syntax.Ident ident, Document document) {
int line = document.lineOfIndex(ident.start());
if (line < 0) {
return null;
}

int lineStart = document.indexOfLine(line);
if (lineStart < 0) {
return null;
}

int startCharacter = ident.start() - lineStart;
int endCharacter = ident.end() - lineStart;
return LspAdapter.lineSpan(line, startCharacter, endCharacter);
return document.rangeOfValue(ident);
}

/**
Expand Down
21 changes: 6 additions & 15 deletions src/main/java/software/amazon/smithy/lsp/syntax/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -642,21 +642,12 @@ private void operationMembers(Syntax.Statement.Block parent) {

ws();

if (isIdentStart()) {
var opMemberDef = new Syntax.Statement.MemberDef(parent, memberName);
opMemberDef.start = opMemberStart;
opMemberDef.colonPos = colonPos;
opMemberDef.target = ident();
setEnd(opMemberDef);
addStatement(opMemberDef);
} else {
var nodeMemberDef = new Syntax.Statement.NodeMemberDef(parent, memberName);
nodeMemberDef.start = opMemberStart;
nodeMemberDef.colonPos = colonPos;
nodeMemberDef.value = parseNode();
setEnd(nodeMemberDef);
addStatement(nodeMemberDef);
}
var nodeMemberDef = new Syntax.Statement.NodeMemberDef(parent, memberName);
nodeMemberDef.start = opMemberStart;
nodeMemberDef.colonPos = colonPos;
nodeMemberDef.value = parseNode();
setEnd(nodeMemberDef);
addStatement(nodeMemberDef);

ws();
}
Expand Down
Loading