diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java index 047613ec..f2c40fc2 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/ParametricTextDocumentService.java @@ -93,8 +93,6 @@ import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClientAware; -import org.rascalmpl.exceptions.Throw; -import org.rascalmpl.parser.gtd.exception.ParseError; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.values.parsetrees.ITree; @@ -107,7 +105,6 @@ import org.rascalmpl.vscode.lsp.parametric.model.ParametricSummary.SummaryLookup; import org.rascalmpl.vscode.lsp.terminal.ITerminalIDEServer.LanguageParameter; import org.rascalmpl.vscode.lsp.util.CodeActions; -import org.rascalmpl.vscode.lsp.util.Diagnostics; import org.rascalmpl.vscode.lsp.util.FoldingRanges; import org.rascalmpl.vscode.lsp.util.DocumentSymbols; import org.rascalmpl.vscode.lsp.util.SemanticTokenizer; @@ -227,7 +224,8 @@ public void connect(LanguageClient client) { @Override public void didOpen(DidOpenTextDocumentParams params) { logger.debug("Did Open file: {}", params.getTextDocument()); - handleParsingErrors(open(params.getTextDocument())); + TextDocumentState file = open(params.getTextDocument()); + handleParsingErrors(file, file.getCurrentDiagnosticsAsync()); // No debounce triggerAnalyzer(params.getTextDocument(), Duration.ofMillis(800)); } @@ -279,37 +277,15 @@ private TextDocumentState updateContents(VersionedTextDocumentIdentifier doc, St TextDocumentState file = getFile(doc); logger.trace("New contents for {}", doc); file.update(doc.getVersion(), newContents); - handleParsingErrors(file, file.getCurrentTreeAsync()); // Warning: Might be a later version (when a concurrent update happened) + handleParsingErrors(file, file.getCurrentDiagnosticsAsync(Duration.ofMillis(800))); // Warning: Might be a later version (when a concurrent update happened) return file; } - private void handleParsingErrors(TextDocumentState file, CompletableFuture> futureTree) { - var version = file.getCurrentContent().version(); - futureTree.handle((tree, excp) -> { - Diagnostic newParseError = null; - if (excp instanceof CompletionException) { - excp = excp.getCause(); - } - - if (excp instanceof Throw) { - Throw thrown = (Throw) excp; - newParseError = Diagnostics.translateRascalParseError(thrown.getException(), columns); - } - else if (excp instanceof ParseError) { - newParseError = Diagnostics.translateDiagnostic((ParseError)excp, columns); - } - else if (excp != null) { - logger.error("Parsing crashed", excp); - newParseError = new Diagnostic( - new Range(new Position(0,0), new Position(0,1)), - "Parsing failed: " + excp.getMessage(), - DiagnosticSeverity.Error, - "Rascal Parser"); - } - logger.trace("Finished parsing tree, reporting new parse error: {} for: {}", newParseError, file.getLocation()); - facts(file.getLocation()).reportParseErrors(file.getLocation(), version, - newParseError == null ? Collections.emptyList() : Collections.singletonList(newParseError)); - return null; + private void handleParsingErrors(TextDocumentState file, CompletableFuture>> diagnosticsAsync) { + diagnosticsAsync.thenAccept(diagnostics -> { + List parseErrors = diagnostics.get(); + logger.trace("Finished parsing tree, reporting new parse errors: {} for: {}", parseErrors, file.getLocation()); + facts(file.getLocation()).reportParseErrors(file.getLocation(), diagnostics.version(), parseErrors); }); } @@ -382,12 +358,6 @@ private CodeLens locCommandTupleToCodeLense(String languageName, IValue v) { return new CodeLens(Locations.toRange(loc, columns), CodeActions.constructorToCommand(dedicatedLanguageName, languageName, command), null); } - - - private void handleParsingErrors(TextDocumentState file) { - handleParsingErrors(file, file.getCurrentTreeAsync()); - } - private static T last(List l) { return l.get(l.size() - 1); } diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricFileFacts.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricFileFacts.java index 113100fb..9b6575af 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricFileFacts.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/parametric/model/ParametricFileFacts.java @@ -319,20 +319,25 @@ private void sendDiagnostics() { logger.debug("Cannot send diagnostics since the client hasn't been registered yet"); return; } - var messages = Lists.union( - unwrap(parserDiagnostics), - unwrap(analyzerDiagnostics), - unwrap(builderDiagnostics)); - logger.trace("Sending diagnostics for {}. {} messages", file, messages.size()); + + // Read the atomic references once for the whole method, to ensure + // that published diagnostics correspond with logged versions + var fromParser = parserDiagnostics.get(); + var fromAnalyzer = analyzerDiagnostics.get(); + var fromBuilder = builderDiagnostics.get(); + + var diagnostics = Lists.union( + fromParser.get(), + fromAnalyzer.get(), + fromBuilder.get()); + + logger.trace( + "Sending {} diagnostic(s) for {} (parser: v{}; analyzer: v{}; builder: v{})", + diagnostics.size(), file, fromParser.version(), fromAnalyzer.version(), fromBuilder.version()); + client.publishDiagnostics(new PublishDiagnosticsParams( file.getURI().toString(), - messages)); - } - - private List unwrap(AtomicReference>> wrappedDiagnostics) { - return wrappedDiagnostics - .get() // Unwrap `AtomicReference` - .get(); // Unwrap `Versioned` + diagnostics)); } /** diff --git a/rascal-lsp/src/main/rascal/demo/lang/pico/LanguageServer.rsc b/rascal-lsp/src/main/rascal/demo/lang/pico/LanguageServer.rsc index 4cfd4fb2..c58a4809 100644 --- a/rascal-lsp/src/main/rascal/demo/lang/pico/LanguageServer.rsc +++ b/rascal-lsp/src/main/rascal/demo/lang/pico/LanguageServer.rsc @@ -36,6 +36,7 @@ module demo::lang::pico::LanguageServer import util::LanguageServer; import util::IDEServices; import ParseTree; +import util::ErrorRecovery; import util::Reflective; import lang::pico::\syntax::Main; @@ -46,7 +47,7 @@ Here we group all services such that the LSP server can link them with the ((Language)) definition later. } set[LanguageService] picoLanguageServer() = { - parsing(parser(#start[Program]), usesSpecialCaseHighlighting = false), + parsing(parser(#start[Program], allowRecovery=true, allowAmbiguity=false), usesSpecialCasingHighlighting = false), documentSymbol(picoDocumentSymbolService), codeLens(picoCodeLenseService), execution(picoExecutionService), @@ -62,7 +63,7 @@ such that quicky loaded features can be made available while slower to load tools come in later. } set[LanguageService] picoLanguageServerSlowSummary() = { - parsing(parser(#start[Program]), usesSpecialCaseHighlighting = false), + parsing(parser(#start[Program], allowRecovery=true, allowAmbiguity=false), usesSpecialCaseHighlighting = false), analysis(picoAnalysisService, providesImplementations = false), build(picoBuildService) }; @@ -74,7 +75,7 @@ symbol search in the editor. } list[DocumentSymbol] picoDocumentSymbolService(start[Program] input) = [symbol("", DocumentSymbolKind::\file(), input.src, children=[ - *[symbol("", \variable(), var.src) | /IdType var := input] + *[symbol("", \variable(), var.src) | /IdType var := input, !hasErrors(var)] ])]; @synopsis{The analyzer maps pico syntax trees to error messages and references} @@ -94,7 +95,7 @@ Summary picoSummaryService(loc l, start[Program] input, PicoSummarizerMode mode) Summary s = summary(l); // definitions of variables - rel[str, loc] defs = {<"", var.src> | /IdType var := input}; + rel[str, loc] defs = {<"", var.src> | /IdType var := input, !hasErrors(var)}; // uses of identifiers rel[loc, str] uses = {"> | /Id id := input};