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

Upgrade completions, definition, hover #166

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,4 @@ bin
.settings

.java-version
*.smithy
!/src/test/resources/**/*.smithy
.ammonite
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ publishing {
}
}

checkstyle {
toolVersion = "10.12.4"
}

dependencies {
implementation "org.eclipse.lsp4j:org.eclipse.lsp4j:0.23.1"
Expand All @@ -153,6 +156,8 @@ dependencies {
testImplementation "org.hamcrest:hamcrest:2.2"

testRuntimeOnly "org.junit.platform:junit-platform-launcher"

checkstyle "com.puppycrawl.tools:checkstyle:${checkstyle.toolVersion}"
}

tasks.withType(Javadoc).all {
Expand Down
1 change: 0 additions & 1 deletion config/checkstyle/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@
<!-- See http://checkstyle.sf.net/config_design.html -->
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
<module name="OneTopLevelClass"/>

<!-- Miscellaneous other checks. -->
Expand Down
7 changes: 3 additions & 4 deletions src/main/java/software/amazon/smithy/lsp/ServerState.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ ProjectAndFile findProjectAndFile(String uri) {
String path = LspAdapter.toPath(uri);
ProjectFile projectFile = detachedProject.getProjectFile(path);
if (projectFile != null) {
return new ProjectAndFile(detachedProject, projectFile);
return new ProjectAndFile(uri, detachedProject, projectFile, true);
}
}

Expand Down Expand Up @@ -133,17 +133,16 @@ private ProjectAndFile findAttachedAndRemoveDetached(String uri) {
ProjectFile projectFile = project.getProjectFile(path);
if (projectFile != null) {
detachedProjects.remove(uri);
return new ProjectAndFile(project, projectFile);
return new ProjectAndFile(uri, project, projectFile, false);
}
}

return null;
}

Project createDetachedProject(String uri, String text) {
void createDetachedProject(String uri, String text) {
Project project = ProjectLoader.loadDetached(uri, text);
detachedProjects.put(uri, project);
return project;
}

List<Exception> tryInitProject(Path root) {
Expand Down
191 changes: 34 additions & 157 deletions src/main/java/software/amazon/smithy/lsp/SmithyLanguageServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.DefinitionParams;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
Expand Down Expand Up @@ -74,7 +73,6 @@
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.SetTraceParams;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.TextDocumentChangeRegistrationOptions;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
Expand All @@ -100,26 +98,25 @@
import software.amazon.smithy.lsp.codeactions.SmithyCodeActions;
import software.amazon.smithy.lsp.diagnostics.SmithyDiagnostics;
import software.amazon.smithy.lsp.document.Document;
import software.amazon.smithy.lsp.document.DocumentParser;
import software.amazon.smithy.lsp.document.DocumentShape;
import software.amazon.smithy.lsp.ext.OpenProject;
import software.amazon.smithy.lsp.ext.SelectorParams;
import software.amazon.smithy.lsp.ext.ServerStatus;
import software.amazon.smithy.lsp.ext.SmithyProtocolExtensions;
import software.amazon.smithy.lsp.handler.CompletionHandler;
import software.amazon.smithy.lsp.handler.DefinitionHandler;
import software.amazon.smithy.lsp.handler.HoverHandler;
import software.amazon.smithy.lsp.language.CompletionHandler;
import software.amazon.smithy.lsp.language.DefinitionHandler;
import software.amazon.smithy.lsp.language.DocumentSymbolHandler;
import software.amazon.smithy.lsp.language.HoverHandler;
import software.amazon.smithy.lsp.project.BuildFile;
import software.amazon.smithy.lsp.project.IdlFile;
import software.amazon.smithy.lsp.project.Project;
import software.amazon.smithy.lsp.project.ProjectAndFile;
import software.amazon.smithy.lsp.project.SmithyFile;
import software.amazon.smithy.lsp.protocol.LspAdapter;
import software.amazon.smithy.model.SourceLocation;
import software.amazon.smithy.lsp.syntax.Syntax;
import software.amazon.smithy.model.loader.IdlTokenizer;
import software.amazon.smithy.model.selector.Selector;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.syntax.Formatter;
import software.amazon.smithy.syntax.TokenTree;
import software.amazon.smithy.utils.IoUtils;
Expand Down Expand Up @@ -163,6 +160,10 @@ ServerState getState() {
return state;
}

Severity getMinimumSeverity() {
return minimumSeverity;
}

@Override
public void connect(LanguageClient client) {
LOGGER.finest("Connect");
Expand Down Expand Up @@ -517,10 +518,11 @@ public void didChange(DidChangeTextDocumentParams params) {
}

// Don't reload or update the project on build file changes, only on save
if (projectAndFile.file() instanceof BuildFile) {
if (!(projectAndFile.file() instanceof SmithyFile smithyFile)) {
return;
}

smithyFile.reparse();
if (!onlyReloadOnSave) {
Project project = projectAndFile.project();

Expand All @@ -529,7 +531,7 @@ public void didChange(DidChangeTextDocumentParams params) {
// Report any parse/shape/trait loading errors
CompletableFuture<Void> future = CompletableFuture
.runAsync(() -> project.updateModelWithoutValidating(uri))
.thenComposeAsync(unused -> sendFileDiagnostics(uri));
.thenComposeAsync(unused -> sendFileDiagnostics(projectAndFile));
state.lifecycleManager().putTask(uri, future);
}
}
Expand All @@ -549,9 +551,10 @@ public void didOpen(DidOpenTextDocumentParams params) {
projectAndFile.file().document().applyEdit(null, text);
} else {
state.createDetachedProject(uri, text);
projectAndFile = state.findProjectAndFile(uri); // Note: This will always be present
}

state.lifecycleManager().putTask(uri, sendFileDiagnostics(uri));
state.lifecycleManager().putTask(uri, sendFileDiagnostics(projectAndFile));
}

@Override
Expand Down Expand Up @@ -597,7 +600,7 @@ public void didSave(DidSaveTextDocumentParams params) {
} else {
CompletableFuture<Void> future = CompletableFuture
.runAsync(() -> project.updateAndValidateModel(uri))
.thenCompose(unused -> sendFileDiagnostics(uri));
.thenCompose(unused -> sendFileDiagnostics(projectAndFile));
state.lifecycleManager().putTask(uri, future);
}
}
Expand All @@ -613,15 +616,13 @@ public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completio
return completedFuture(Either.forLeft(Collections.emptyList()));
}

if (!(projectAndFile.file() instanceof SmithyFile smithyFile)) {
if (!(projectAndFile.file() instanceof IdlFile smithyFile)) {
return completedFuture(Either.forLeft(List.of()));
}

Project project = projectAndFile.project();
return CompletableFutures.computeAsync((cc) -> {
CompletionHandler handler = new CompletionHandler(project, smithyFile);
return Either.forLeft(handler.handle(params, cc));
});
var handler = new CompletionHandler(project, smithyFile);
return CompletableFutures.computeAsync((cc) -> Either.forLeft(handler.handle(params, cc)));
}

@Override
Expand All @@ -643,54 +644,13 @@ public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem un
return completedFuture(Collections.emptyList());
}

if (!(projectAndFile.file() instanceof SmithyFile smithyFile)) {
if (!(projectAndFile.file() instanceof IdlFile idlFile)) {
return completedFuture(List.of());
}

return CompletableFutures.computeAsync((cc) -> {
Collection<DocumentShape> documentShapes = smithyFile.documentShapes();
if (documentShapes.isEmpty()) {
return Collections.emptyList();
}

if (cc.isCanceled()) {
return Collections.emptyList();
}

List<Either<SymbolInformation, DocumentSymbol>> documentSymbols = new ArrayList<>(documentShapes.size());
for (DocumentShape documentShape : documentShapes) {
SymbolKind symbolKind;
switch (documentShape.kind()) {
case Inline:
// No shape name in the document text, so no symbol
continue;
case DefinedMember:
case Elided:
symbolKind = SymbolKind.Property;
break;
case DefinedShape:
case Targeted:
default:
symbolKind = SymbolKind.Class;
break;
}

// Check before copying shapeName, which is actually a reference to the underlying document, and may
// be changed.
cc.checkCanceled();

String symbolName = documentShape.shapeName().toString();
if (symbolName.isEmpty()) {
LOGGER.warning("[DocumentSymbols] Empty shape name for " + documentShape);
continue;
}
Range symbolRange = documentShape.range();
DocumentSymbol symbol = new DocumentSymbol(symbolName, symbolKind, symbolRange, symbolRange);
documentSymbols.add(Either.forRight(symbol));
}

return documentSymbols;
});
List<Syntax.Statement> statements = idlFile.getParse().statements();
var handler = new DocumentSymbolHandler(idlFile.document(), statements);
return CompletableFuture.supplyAsync(handler::handle);
}

@Override
Expand All @@ -705,13 +665,13 @@ public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem un
return completedFuture(null);
}

if (!(projectAndFile.file() instanceof SmithyFile smithyFile)) {
if (!(projectAndFile.file() instanceof IdlFile smithyFile)) {
return completedFuture(null);
}

Project project = projectAndFile.project();
List<Location> locations = new DefinitionHandler(project, smithyFile).handle(params);
return completedFuture(Either.forLeft(locations));
var handler = new DefinitionHandler(project, smithyFile);
return CompletableFuture.supplyAsync(() -> Either.forLeft(handler.handle(params)));
}

@Override
Expand All @@ -725,15 +685,15 @@ public CompletableFuture<Hover> hover(HoverParams params) {
return completedFuture(null);
}

if (!(projectAndFile.file() instanceof SmithyFile smithyFile)) {
if (!(projectAndFile.file() instanceof IdlFile smithyFile)) {
return completedFuture(null);
}

Project project = projectAndFile.project();

// TODO: Abstract away passing minimum severity
Hover hover = new HoverHandler(project, smithyFile).handle(params, minimumSeverity);
return completedFuture(hover);
var handler = new HoverHandler(project, smithyFile, minimumSeverity);
return CompletableFuture.supplyAsync(() -> handler.handle(params));
}

@Override
Expand Down Expand Up @@ -772,99 +732,16 @@ public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormatting

private void sendFileDiagnosticsForManagedDocuments() {
for (String managedDocumentUri : state.managedUris()) {
state.lifecycleManager().putOrComposeTask(managedDocumentUri, sendFileDiagnostics(managedDocumentUri));
ProjectAndFile projectAndFile = state.findProjectAndFile(managedDocumentUri);
state.lifecycleManager().putOrComposeTask(managedDocumentUri, sendFileDiagnostics(projectAndFile));
}
}

private CompletableFuture<Void> sendFileDiagnostics(String uri) {
private CompletableFuture<Void> sendFileDiagnostics(ProjectAndFile projectAndFile) {
return CompletableFuture.runAsync(() -> {
List<Diagnostic> diagnostics = getFileDiagnostics(uri);
PublishDiagnosticsParams publishDiagnosticsParams = new PublishDiagnosticsParams(uri, diagnostics);
List<Diagnostic> diagnostics = SmithyDiagnostics.getFileDiagnostics(projectAndFile, minimumSeverity);
var publishDiagnosticsParams = new PublishDiagnosticsParams(projectAndFile.uri(), diagnostics);
client.publishDiagnostics(publishDiagnosticsParams);
});
}

List<Diagnostic> getFileDiagnostics(String uri) {
if (LspAdapter.isJarFile(uri) || LspAdapter.isSmithyJarFile(uri)) {
// Don't send diagnostics to jar files since they can't be edited
// and diagnostics could be misleading.
return Collections.emptyList();
}

ProjectAndFile projectAndFile = state.findProjectAndFile(uri);
if (projectAndFile == null) {
client.unknownFileError(uri, "diagnostics");
return List.of();
}

if (!(projectAndFile.file() instanceof SmithyFile smithyFile)) {
return List.of();
}

Project project = projectAndFile.project();
String path = LspAdapter.toPath(uri);

List<Diagnostic> diagnostics = project.modelResult().getValidationEvents().stream()
.filter(validationEvent -> validationEvent.getSeverity().compareTo(minimumSeverity) >= 0)
.filter(validationEvent -> validationEvent.getSourceLocation().getFilename().equals(path))
.map(validationEvent -> toDiagnostic(validationEvent, smithyFile))
.collect(Collectors.toCollection(ArrayList::new));

Diagnostic versionDiagnostic = SmithyDiagnostics.versionDiagnostic(smithyFile);
if (versionDiagnostic != null) {
diagnostics.add(versionDiagnostic);
}

if (state.isDetached(uri)) {
diagnostics.add(SmithyDiagnostics.detachedDiagnostic(smithyFile));
}

return diagnostics;
}

private static Diagnostic toDiagnostic(ValidationEvent validationEvent, SmithyFile smithyFile) {
DiagnosticSeverity severity = toDiagnosticSeverity(validationEvent.getSeverity());
SourceLocation sourceLocation = validationEvent.getSourceLocation();
Range range = determineRange(validationEvent, sourceLocation, smithyFile);
String message = validationEvent.getId() + ": " + validationEvent.getMessage();
return new Diagnostic(range, message, severity, "Smithy");
}

private static Range determineRange(ValidationEvent validationEvent,
SourceLocation sourceLocation,
SmithyFile smithyFile) {
final Range defaultRange = LspAdapter.lineOffset(LspAdapter.toPosition(sourceLocation));

if (smithyFile == null) {
return defaultRange;
}

DocumentParser parser = DocumentParser.forDocument(smithyFile.document());

// Case where we have shapes present
if (validationEvent.getShapeId().isPresent()) {
// Event is (probably) on a member target
if (validationEvent.containsId("Target")) {
DocumentShape documentShape = smithyFile.documentShapesByStartPosition()
.get(LspAdapter.toPosition(sourceLocation));
if (documentShape != null && documentShape.hasMemberTarget()) {
return documentShape.targetReference().range();
}
} else {
// Check if the event location is on a trait application
return Objects.requireNonNullElse(parser.traitIdRange(sourceLocation), defaultRange);
}
}

return Objects.requireNonNullElse(parser.findContiguousRange(sourceLocation), defaultRange);
}

private static DiagnosticSeverity toDiagnosticSeverity(Severity severity) {
return switch (severity) {
case ERROR, DANGER -> DiagnosticSeverity.Error;
case WARNING -> DiagnosticSeverity.Warning;
case NOTE -> DiagnosticSeverity.Information;
default -> DiagnosticSeverity.Hint;
};
}
}
Loading
Loading