diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/TextDocumentState.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/TextDocumentState.java index 89f889fe5..8d01c50c7 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/TextDocumentState.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/TextDocumentState.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; @@ -62,6 +63,7 @@ public class TextDocumentState { private final BiFunction> parser; private final ISourceLocation location; + private final ExecutorService exec; private final AtomicReference> current; private final AtomicReference<@Nullable Versioned> lastWithoutErrors; @@ -70,15 +72,17 @@ public class TextDocumentState { public TextDocumentState( BiFunction> parser, ISourceLocation location, - int initialVersion, String initialContent, long initialTimestamp) { + int initialVersion, String initialContent, long initialTimestamp, + ExecutorService exec) { this.parser = parser; this.location = location; + this.lastWithoutErrors = new AtomicReference<>(); + this.last = new AtomicReference<>(); + this.exec = exec; var u = new Update(initialVersion, initialContent, initialTimestamp); this.current = new AtomicReference<>(new Versioned<>(initialVersion, u)); - this.lastWithoutErrors = new AtomicReference<>(); - this.last = new AtomicReference<>(); } public ISourceLocation getLocation() { @@ -200,28 +204,35 @@ public CompletableFuture>> getDiagnosticsAs private void parse() { try { parser.apply(location, content) - .whenComplete((ITree t, Throwable e) -> { - if (e instanceof CompletionException && e.getCause() != null) { - e = e.getCause(); - } - var diagnosticsList = toDiagnosticsList(t, e); // `t` and `e` are nullable - - // Complete future to get the tree - if (t == null) { - treeAsync.completeExceptionally(e); - } else { - var tree = new Versioned<>(version, t, timestamp); - Versioned.replaceIfNewer(last, tree); - if (diagnosticsList.isEmpty()) { - Versioned.replaceIfNewer(lastWithoutErrors, tree); + .whenCompleteAsync((ITree t, Throwable e) -> { + try { + if (e instanceof CompletionException && e.getCause() != null) { + e = e.getCause(); + } + var diagnosticsList = toDiagnosticsList(t, e); // `t` and `e` are nullable + + // Complete future to get the tree + if (t == null) { + treeAsync.completeExceptionally(e); + } else { + var tree = new Versioned<>(version, t, timestamp); + Versioned.replaceIfNewer(last, tree); + if (diagnosticsList.isEmpty()) { + Versioned.replaceIfNewer(lastWithoutErrors, tree); + } + treeAsync.complete(tree); } - treeAsync.complete(tree); - } - // Complete future to get diagnostics - var diagnostics = new Versioned<>(version, diagnosticsList); - diagnosticsAsync.complete(diagnostics); - }); + // Complete future to get diagnostics + var diagnostics = new Versioned<>(version, diagnosticsList); + diagnosticsAsync.complete(diagnostics); + } catch (Exception exc) { + // The action of `whenCompleteAsync` shouldn't throw an exception (see JavaDoc): if it + // unexpectedly does, then it is almost surely a bug, but the exception is swallowed, so it + // is very hard to debug. The try/catch block and logger call aim to make it easier. + logger.error("Unexpected exception after parsing", exc); + } + }, exec); } catch (NoContributionException e) { logger.debug("Ignoring missing parser for {}", location, e); treeAsync.completeOnTimeout(new Versioned<>(version, IRascalValueFactory.getInstance().character(0), timestamp), 60, TimeUnit.SECONDS); @@ -255,6 +266,6 @@ public long getLastModified() { public TextDocumentState changeParser(BiFunction> parsing) { var c = getCurrentContent(); - return new TextDocumentState(parsing, this.location, c.version(), c.get(), getLastModified()); + return new TextDocumentState(parsing, this.location, c.version(), c.get(), getLastModified(), exec); } } 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 c024c43ee..97e85b6e6 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 @@ -713,7 +713,7 @@ private ParametricFileFacts facts(ISourceLocation doc) { private TextDocumentState open(TextDocumentItem doc, long timestamp) { return files.computeIfAbsent(Locations.toLoc(doc), - l -> new TextDocumentState(contributions(l)::parsing, l, doc.getVersion(), doc.getText(), timestamp)); + l -> new TextDocumentState(contributions(l)::parsing, l, doc.getVersion(), doc.getText(), timestamp, exec)); } private TextDocumentState getFile(ISourceLocation loc) { diff --git a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java index e581a3273..a13d62b58 100644 --- a/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java +++ b/rascal-lsp/src/main/java/org/rascalmpl/vscode/lsp/rascal/RascalTextDocumentService.java @@ -557,7 +557,7 @@ private static T last(List l) { private TextDocumentState open(TextDocumentItem doc, long timestamp) { return documents.computeIfAbsent(Locations.toLoc(doc), - l -> new TextDocumentState(availableRascalServices()::parseSourceFile, l, doc.getVersion(), doc.getText(), timestamp)); + l -> new TextDocumentState(availableRascalServices()::parseSourceFile, l, doc.getVersion(), doc.getText(), timestamp, exec)); } private TextDocumentState getFile(TextDocumentIdentifier doc) {