From 8238c1dc90dced8f6ced13924ca047b0d0e7a1be Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Sat, 6 Feb 2021 08:49:05 +0100 Subject: [PATCH] initial re-work of async behavior of language server, looking at the text document service first --- .../ide/vscode/bosh/BoshDefintionFinder.java | 7 +- .../VscodeCompletionEngineAdapter.java | 8 +- .../CompositeLanguageServerComponents.java | 8 +- .../definition/SimpleDefinitionFinder.java | 5 +- .../hover/VscodeHoverEngineAdapter.java | 5 +- .../util/SimpleLanguageServer.java | 20 +- .../util/SimpleTextDocumentService.java | 518 +++++++++--------- .../commons/util/text/TextDocument.java | 6 +- .../commons/yaml/quickfix/YamlQuickfixes.java | 8 +- .../reconcile/TypeBasedYamlSymbolHandler.java | 5 +- .../starter/LanguageServerAutoConf.java | 6 +- .../concourse/ConcourseDefinitionFinder.java | 4 +- .../ConcourseLanguageServerInitializer.java | 10 +- .../app/PropertiesJavaDefinitionHandler.java | 4 +- .../app/XmlBeansConfigDefinitionHandler.java | 6 +- .../YamlPropertiesJavaDefinitionHandler.java | 4 +- .../java/handlers/BootJavaCodeLensEngine.java | 6 +- .../BootJavaDocumentHighlightEngine.java | 9 +- .../java/handlers/BootJavaHoverProvider.java | 7 +- .../handlers/BootJavaReferencesHandler.java | 7 +- .../v2/SpringProcessLiveHoverUpdater.java | 36 +- .../boot/java/utils/CompilationUnitCache.java | 185 +++---- .../hyperlinks/BeanRefHyperlinkProvider.java | 26 +- .../boot/yaml/quickfix/AppYamlQuickfixes.java | 5 +- .../ValueSpelExpressionValidationTest.java | 4 +- .../test/XMLSpelExpressionValidationTest.java | 4 +- 26 files changed, 463 insertions(+), 450 deletions(-) diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDefintionFinder.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDefintionFinder.java index d5a860146..0196ce3b2 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDefintionFinder.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDefintionFinder.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020 Pivotal, Inc. + * Copyright (c) 2017, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -75,8 +75,9 @@ public class BoshDefintionFinder extends SimpleDefinitionFinder { @Override public List handle(DefinitionParams params) { try { - TextDocument doc = server.getTextDocumentService().get(params.getTextDocument().getUri()); - if (doc!=null) { + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params); + + if (doc != null) { YamlFileAST ast = asts.getSafeAst(doc, false); if (ast!=null) { Node refNode = ast.findNode(doc.toOffset(params.getPosition())); diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java index 64123f046..8892a90cd 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2019 Pivotal, Inc. + * Copyright (c) 2016, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -128,8 +128,10 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine { private Mono getCompletionsMono(TextDocumentPositionParams params) { SimpleTextDocumentService documents = server.getTextDocumentService(); - if (documents.get(params) != null) { - TextDocument doc = documents.getDocumentSnapshot(params.getTextDocument()); + + TextDocument doc = documents.getLatestSnapshot(params); + if (doc != null) { + return Mono.fromCallable(() -> { log.info("Starting completion handling"); if (resolver!=null) { diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/CompositeLanguageServerComponents.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/CompositeLanguageServerComponents.java index 8c4ba9666..862f0cd94 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/CompositeLanguageServerComponents.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/CompositeLanguageServerComponents.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2020 Pivotal, Inc. + * Copyright (c) 2018, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -11,9 +11,7 @@ package org.springframework.ide.vscode.commons.languageserver.composable; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; @@ -21,8 +19,6 @@ import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.HoverParams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.commons.languageserver.completion.CompositeCompletionEngine; -import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler; @@ -86,7 +82,7 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen this.hoverHandler = new HoverHandler() { @Override public Hover handle(HoverParams params) { - TextDocument doc = server.getTextDocumentService().get(params.getTextDocument().getUri()); + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri()); LanguageId language = doc.getLanguageId(); LanguageServerComponents subComponents = componentsByLanguageId.get(language); if (subComponents!=null) { diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/definition/SimpleDefinitionFinder.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/definition/SimpleDefinitionFinder.java index 62e2de763..e5fbb393c 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/definition/SimpleDefinitionFinder.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/definition/SimpleDefinitionFinder.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020 Pivotal, Inc. + * Copyright (c) 2017, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -41,7 +41,8 @@ public class SimpleDefinitionFinder implements DefinitionHandler { @Override public List handle(DefinitionParams params) { try { - TextDocument doc = server.getTextDocumentService().get(params.getTextDocument().getUri()); + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params.getTextDocument().getUri()); + if (doc != null) { int offset = doc.toOffset(params.getPosition()); int start = offset; diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java index 05605febd..b9558460e 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2020 Pivotal, Inc. + * Copyright (c) 2016, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -60,7 +60,8 @@ public class VscodeHoverEngineAdapter implements HoverHandler { public Hover handle(HoverParams params) { try { SimpleTextDocumentService documents = server.getTextDocumentService(); - TextDocument doc = documents.get(params.getTextDocument().getUri()); + TextDocument doc = documents.getLatestSnapshot(params.getTextDocument().getUri()); + if (doc != null) { int offset = doc.toOffset(params.getPosition()); diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index bc045ecc2..07820ebf1 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2020 Pivotal, Inc. + * Copyright (c) 2016, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -534,13 +534,13 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC public void validateWith(TextDocumentIdentifier docId, IReconcileEngine engine) { SimpleTextDocumentService documents = getTextDocumentService(); - TextDocument document = documents.getDocument(docId.getUri()); - if (document == null) { + TextDocument doc = documents.getLatestSnapshot(docId.getUri()); + if (doc == null) { log.debug("Reconcile skipped due to document doesn't exist anymore {}", docId.getUri()); return; } - int requestedVersion = document.getVersion(); + int requestedVersion = doc.getVersion(); VersionedTextDocumentIdentifier request = new VersionedTextDocumentIdentifier(docId.getUri(), requestedVersion); log.debug("Reconcile requested {} - {}", request.getUri(), request.getVersion()); if (!queuedReconcileRequests.add(request)) { @@ -557,13 +557,13 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC queuedReconcileRequests.remove(request); log.debug("Reconcile starting {} - {}", request.getUri(), request.getVersion()); - TextDocument doc = documents.getDocument(docId.getUri()).copy(); +// TextDocument doc = documents.getDocument(docId.getUri()).copy(); - if (doc == null) { - log.debug("Reconcile aborted due to document doesn't exist {} - {}", request.getUri(), request.getVersion()); - //Do not bother reconciling if document doesn't exist anymore (got closed in the meantime) - return; - } +// if (doc == null) { +// log.debug("Reconcile aborted due to document doesn't exist {} - {}", request.getUri(), request.getVersion()); +// //Do not bother reconciling if document doesn't exist anymore (got closed in the meantime) +// return; +// } if (requestedVersion != doc.getVersion()) { log.debug("Reconcile aborted due to document being already stale {} - {}", request.getUri(), request.getVersion()); diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java index 15bfa9c51..3c95438b8 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2020 Pivotal, Inc. + * Copyright (c) 2016, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,13 +10,12 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.languageserver.util; -import java.time.Duration; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -46,7 +45,6 @@ import org.eclipse.lsp4j.HoverParams; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.LocationLink; import org.eclipse.lsp4j.PublishDiagnosticsParams; -import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.ReferenceParams; import org.eclipse.lsp4j.RenameParams; import org.eclipse.lsp4j.SignatureHelp; @@ -59,6 +57,7 @@ import org.eclipse.lsp4j.TextDocumentPositionParams; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceEdit; +import org.eclipse.lsp4j.jsonrpc.CompletableFutures; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.TextDocumentService; @@ -67,182 +66,132 @@ import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerProperties; import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix; import org.springframework.ide.vscode.commons.util.Assert; -import org.springframework.ide.vscode.commons.util.AsyncRunner; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.CollectorUtil; -import org.springframework.ide.vscode.commons.util.ExceptionUtil; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.collect.ImmutableList; -import reactor.core.publisher.Mono; - public class SimpleTextDocumentService implements TextDocumentService, DocumentEventListenerManager { private static Logger log = LoggerFactory.getLogger(SimpleTextDocumentService.class); - final private SimpleLanguageServer server; - final private LanguageServerProperties props; - private Map documents = new HashMap<>(); - private ListenerList documentChangeListeners = new ListenerList<>(); - private ListenerList documentCloseListeners = new ListenerList<>(); - private ListenerList documentOpenListeners = new ListenerList<>(); + private final SimpleLanguageServer server; + private final LanguageServerProperties props; + + private final ConcurrentMap documents = new ConcurrentHashMap<>(); + private final ConcurrentMap documentSnapshots = new ConcurrentHashMap<>(); - private CompletionHandler completionHandler = null; - private CompletionResolveHandler completionResolveHandler = null; + private final ListenerList documentChangeListeners = new ListenerList<>(); + private final ListenerList documentCloseListeners = new ListenerList<>(); + private final ListenerList documentOpenListeners = new ListenerList<>(); + private List> documentSaveListeners = ImmutableList.of(); - private HoverHandler hoverHandler = null; + private CompletionHandler completionHandler; + private CompletionResolveHandler completionResolveHandler; + private HoverHandler hoverHandler; private DefinitionHandler definitionHandler; private ReferencesHandler referencesHandler; - private DocumentSymbolHandler documentSymbolHandler; private DocumentHighlightHandler documentHighlightHandler; - private CodeLensHandler codeLensHandler; private CodeLensResolveHandler codeLensResolveHandler; - private List> documentSaveListeners = ImmutableList.of(); - private AsyncRunner async; - - public SimpleTextDocumentService(SimpleLanguageServer server, LanguageServerProperties props) { this.server = server; this.props = props; - this.async = server.getAsync(); - } - - public synchronized void onHover(HoverHandler h) { - Assert.isNull("A hover handler is already set, multiple handlers not supported yet", hoverHandler); - this.hoverHandler = h; - } - - public synchronized void onCodeLens(CodeLensHandler h) { - Assert.isNull("A code lens handler is already set, multiple handlers not supported yet", codeLensHandler); - this.codeLensHandler = h; - } - - public synchronized void onCodeLensResolve(CodeLensResolveHandler h) { - Assert.isNull("A code lens resolve handler is already set, multiple handlers not supported yet", codeLensResolveHandler); - this.codeLensResolveHandler = h; - } - - public synchronized void onDocumentSymbol(DocumentSymbolHandler h) { - Assert.isNull("A DocumentSymbolHandler is already set, multiple handlers not supported yet", documentSymbolHandler); - this.documentSymbolHandler = h; - } - - public synchronized void onDocumentHighlight(DocumentHighlightHandler h) { - Assert.isNull("A DocumentHighlightHandler is already set, multiple handlers not supported yet", documentHighlightHandler); - this.documentHighlightHandler = h; - } - - public synchronized void onCompletion(CompletionHandler h) { - Assert.isNull("A completion handler is already set, multiple handlers not supported yet", completionHandler); - this.completionHandler = h; - } - - public synchronized void onCompletionResolve(CompletionResolveHandler h) { - Assert.isNull("A completionResolveHandler handler is already set, multiple handlers not supported yet", completionResolveHandler); - this.completionResolveHandler = h; - } - - public synchronized void onDefinition(DefinitionHandler h) { - Assert.isNull("A defintion handler is already set, multiple handlers not supported yet", definitionHandler); - this.definitionHandler = h; - } - - public synchronized void onReferences(ReferencesHandler h) { - Assert.isNull("A references handler is already set, multiple handlers not supported yet", referencesHandler); - this.referencesHandler = h; } /** * Gets all documents this service is tracking, generally these are the documents that have been opened / changed, * and not yet closed. */ - public synchronized Collection getAll() { - return documents.values().stream() - .map((td) -> td.getDocument()) + public Collection getAll() { + return documentSnapshots.values().stream() .collect(Collectors.toList()); } @Override - public final void didChange(DidChangeTextDocumentParams params) { - async.execute(() -> { + public void didOpen(DidOpenTextDocumentParams params) { + log.info("change arrived: " + params.getTextDocument().getVersion()); + + TextDocumentItem docId = params.getTextDocument(); + + String url = docId.getUri(); + LanguageId languageId = LanguageId.of(docId.getLanguageId()); + int version = docId.getVersion(); + + if (url != null) { + + String text = params.getTextDocument().getText(); + TrackedDocument td = createDocument(url, languageId, version, text).open(); + + log.debug("Opened " + td.getOpenCount() + " times: " + url); + TextDocument doc = td.getDocument(); + + TextDocument snapshot = doc.copy(); + documentSnapshots.put(url, snapshot); + + documentOpenListeners.fire(snapshot); + + TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent(text); + TextDocumentContentChange evt = new TextDocumentContentChange(snapshot, ImmutableList.of(change)); + + documentChangeListeners.fire(evt); + } + } + + @Override + public void didChange(DidChangeTextDocumentParams params) { + long start = System.currentTimeMillis(); + + log.info("change arrived: " + params.getTextDocument().getVersion()); + try { VersionedTextDocumentIdentifier docId = params.getTextDocument(); String url = docId.getUri(); -// Log.debug("didChange: "+url); + if (url != null) { - TextDocument doc = getDocument(url); + TextDocument doc = getInternalDocument(url); + if (doc != null) { List changes = params.getContentChanges(); doc.apply(params); - didChangeContent(doc, changes); + + TextDocument snapshot = doc.copy(); + documentSnapshots.put(url, snapshot); + documentChangeListeners.fire(new TextDocumentContentChange(snapshot, changes)); } } } catch (BadLocationException e) { log.error("", e); } - }); - } - - @Override - public void didOpen(DidOpenTextDocumentParams params) { - async.execute(() -> { - TextDocumentItem docId = params.getTextDocument(); - String url = docId.getUri(); - //Log.info("didOpen: "+params.getTextDocument().getUri()); - LanguageId languageId = LanguageId.of(docId.getLanguageId()); - int version = docId.getVersion(); - if (url != null) { - - String text = params.getTextDocument().getText(); - TrackedDocument td = createDocument(url, languageId, version, text).open(); - log.debug("Opened " + td.getOpenCount() + " times: " + url); - TextDocument doc = td.getDocument(); - - documentOpenListeners.fire(doc); - - TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent() { - @Override - public Range getRange() { - return null; - } - - @Override - public Integer getRangeLength() { - return null; - } - - @Override - public String getText() { - return text; - } - }; - TextDocumentContentChange evt = new TextDocumentContentChange(doc, ImmutableList.of(change)); - documentChangeListeners.fire(evt); - } - }); + + long end = System.currentTimeMillis(); + log.info("change message work done in " + (end - start) + "ms"); } @Override public void didClose(DidCloseTextDocumentParams params) { - async.execute(() -> { - //Log.info("didClose: "+params.getTextDocument().getUri()); String url = params.getTextDocument().getUri(); - if (url!=null) { + + if (url != null) { + TrackedDocument doc = documents.get(url); if (doc != null) { + if (doc.close()) { + documents.remove(url); + TextDocument lastSnapshot = documentSnapshots.remove(url); + log.info("Closed: "+url); //Clear diagnostics when a file is closed. This makes the errors disapear when the language is changed for // a document (this resulst in a dicClose even as being sent to the language server if that changes make the // document go 'out of scope'. publishDiagnostics(params.getTextDocument(), ImmutableList.of()); - documentCloseListeners.fire(doc.getDocument()); - documents.remove(url); + + documentCloseListeners.fire(lastSnapshot); } else { log.warn("Close event ignored! Assuming document still open because openCount = "+doc.getOpenCount()); } @@ -250,11 +199,6 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE log.warn("Document closed, but it didn't exist! Close event ignored"); } } - }); - } - - void didChangeContent(TextDocument doc, List changes) { - documentChangeListeners.fire(new TextDocumentContentChange(doc, changes)); } public void onDidOpen(Consumer l) { @@ -277,24 +221,24 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE documentSaveListeners = builder.build(); } - public synchronized TextDocument getDocument(String url) { + public TextDocument getLatestSnapshot(String url) { + return documentSnapshots.get(url); + } + + public TextDocument getLatestSnapshot(TextDocumentPositionParams params) { + return getLatestSnapshot(params.getTextDocument().getUri()); + } + + private TextDocument getInternalDocument(String url) { TrackedDocument doc = documents.get(url); return doc != null ? doc.getDocument() : null; } - - private synchronized TrackedDocument createDocument(String url, LanguageId languageId, int version, String text) { - TrackedDocument existingDoc = documents.get(url); - - if (existingDoc != null) { - log.warn("Creating document ["+url+"] but it already exists. Reusing existing!"); - return existingDoc; - } - - TrackedDocument doc = new TrackedDocument(new TextDocument(url, languageId, version, text)); - documents.put(url, doc); - return doc; + + private TrackedDocument createDocument(final String url, final LanguageId languageId, final int version, final String text) { + return documents.computeIfAbsent(url, key -> new TrackedDocument(new TextDocument(url, languageId, version, text))); } + public final static CompletionList NO_COMPLETIONS = new CompletionList(false, Collections.emptyList()); public final static Hover NO_HOVER = new Hover(ImmutableList.of(), null); public final static List NO_REFERENCES = ImmutableList.of(); @@ -304,8 +248,10 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE @Override public CompletableFuture, CompletionList>> completion(CompletionParams position) { + log.info("completion request arrived: " + position.getTextDocument().getUri()); + CompletionHandler h = completionHandler; - if (h!=null) { + if (h != null) { return completionHandler.handle(position) .map(Either::, CompletionList>forRight) .toFuture(); @@ -316,13 +262,16 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE @Override public CompletableFuture resolveCompletionItem(CompletionItem unresolved) { log.info("Completion item resolve request received: {}", unresolved.getLabel()); - return async.invoke(() -> { + + return CompletableFutures.computeAsync(cancelToken -> { try { CompletionResolveHandler h = completionResolveHandler; - if (h!=null) { + if (h != null) { log.info("Completion item resolve request starting {}", unresolved.getLabel()); return h.handle(unresolved); } + } catch (Exception e) { + log.warn("exception resolving completion item", e); } finally { log.info("Completion item resolve request terminated."); } @@ -333,10 +282,18 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE @Override public CompletableFuture hover(HoverParams hoverParams) { log.debug("hover requested for {}", hoverParams.getPosition()); - long timeout = props.getHoverTimeout(); - return timeout <= 0 ? async.invoke(() -> computeHover(hoverParams)) : async.invoke(Duration.ofMillis(timeout), () -> computeHover(hoverParams), Mono.fromRunnable(() -> { - log.error("Hover Request handler timed out after {} ms.", timeout); - })); + + return CompletableFutures.computeAsync(cancelToken -> { + return computeHover(hoverParams); + }); + + + // TODO: timeout still necessary ????? + +// long timeout = props.getHoverTimeout(); +// return timeout <= 0 ? async.invoke(() -> computeHover(hoverParams)) : async.invoke(Duration.ofMillis(timeout), () -> computeHover(hoverParams), Mono.fromRunnable(() -> { +// log.error("Hover Request handler timed out after {} ms.", timeout); +// })); } private Hover computeHover(HoverParams hoverParams) { @@ -353,20 +310,15 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE } } - @Override - public CompletableFuture signatureHelp(SignatureHelpParams signatureHelpParams) { - return CompletableFuture.completedFuture(null); - } - @Override public CompletableFuture, List>> definition( DefinitionParams definitionParams) { DefinitionHandler h = this.definitionHandler; if (h != null) { - return async.invoke(() -> { + return CompletableFutures.computeAsync(cancelToken -> { List locations = h.handle(definitionParams); - if (locations==null) { + if (locations == null) { // vscode client does not like to receive null result. See: https://github.com/spring-projects/sts4/issues/309 locations = ImmutableList.of(); } @@ -382,27 +334,42 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE } }); } - return CompletableFuture.completedFuture(Either.forLeft(ImmutableList.of())); + else { + return CompletableFuture.completedFuture(Either.forLeft(ImmutableList.of())); + } } @Override public CompletableFuture> references(ReferenceParams params) { - return async.invoke(() -> { ReferencesHandler h = this.referencesHandler; if (h != null) { - List list = h.handle(params); - return list != null && list.isEmpty() ? null : list; + + return CompletableFutures.computeAsync(cancelToken -> { + List list = h.handle(params); + return list != null && list.isEmpty() ? null : list; + }); + } + else { + return CompletableFuture.completedFuture(ImmutableList.of()); } - return null; - }); } @Override public CompletableFuture>> documentSymbol(DocumentSymbolParams params) { - return async.invoke(() -> { - DocumentSymbolHandler h = this.documentSymbolHandler; - if (h!=null) { - server.waitForReconcile(); + DocumentSymbolHandler h = this.documentSymbolHandler; + if (h != null) { + + return CompletableFutures.computeAsync(cancelToken -> { + cancelToken.checkCanceled(); + + try { + server.waitForReconcile(); + } catch (Exception e) { + log.warn("error while waiting for reconcile", e); + } + + cancelToken.checkCanceled(); + if (server.hasHierarchicalDocumentSymbolSupport() && h instanceof HierarchicalDocumentSymbolHandler) { List r = ((HierarchicalDocumentSymbolHandler)h).handleHierarchic(params); //handle it when symbolHandler is sloppy and returns null instead of empty list. @@ -418,33 +385,40 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE : r.stream().map(symbolInfo -> Either.forLeft(symbolInfo)) .collect(Collectors.toList()); } - } - return ImmutableList.of(); - }); + }); + } + else { + return CompletableFuture.completedFuture(ImmutableList.of()); + } } @Override public CompletableFuture>> codeAction(CodeActionParams params) { - return async.invoke(() -> { + // this doesn't happen async, because it accesses the internal documents structure + // and therefore needs to be executed as part of the main LSP message queue + TrackedDocument doc = documents.get(params.getTextDocument().getUri()); - if (doc!=null) { + + if (doc != null) { ImmutableList> list = doc.getQuickfixes().stream() - .filter((fix) -> fix.appliesTo(params.getRange(), params.getContext())) - .map(Quickfix::getCodeAction) - .map(command -> Either.forLeft(command)) - .collect(CollectorUtil.toImmutableList()); - return list; + .filter((fix) -> fix.appliesTo(params.getRange(), params.getContext())) + .map(Quickfix::getCodeAction) + .map(command -> Either.forLeft(command)) + .collect(CollectorUtil.toImmutableList()); + return CompletableFuture.completedFuture(list); } else { - return ImmutableList.of(); + return CompletableFuture.completedFuture(ImmutableList.of()); } - }); } @Override public CompletableFuture> codeLens(CodeLensParams params) { CodeLensHandler handler = this.codeLensHandler; + if (handler != null) { - return async.invoke(() -> handler.handle(params)); + return CompletableFutures.computeAsync(cancelToken -> { + return handler.handle(params); + }); } return CompletableFuture.completedFuture(Collections.emptyList()); } @@ -453,8 +427,58 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE public CompletableFuture resolveCodeLens(CodeLens unresolved) { CodeLensResolveHandler handler = this.codeLensResolveHandler; if (handler != null) { - return async.invoke(() -> handler.handle(unresolved)); + + return CompletableFutures.computeAsync(cancelToken -> { + return handler.handle(unresolved); + }); + } + else { + return CompletableFuture.completedFuture(null); + } + } + + @Override + public void didSave(DidSaveTextDocumentParams params) { + // Workaround for PT 147263283, where error markers in STS are lost on document save. + // STS 3.9.0 does not use the LSP4E editor for edit manifest.yml, which correctly retains error markers after save. + // Instead, because the LSP4E editor is missing support for hovers and completions, STS 3.9.0 uses its own manifest editor + // which extends the YEdit editor. This YEdit editor has a problem, where on save, all error markers are deleted. + // When STS uses the LSP4E editor and no longer needs its own YEdit-based editor, the issue with error markers disappearing + // on save should not be a problem anymore, and the workaround below will no longer be needed. + if (documentSaveListeners != null) { + CompletableFuture.runAsync(() -> { + TextDocumentIdentifier docId = params.getTextDocument(); + String url = docId.getUri(); + log.debug("didSave: "+url); + if (url != null) { + TextDocument doc = getLatestSnapshot(url); + if (doc != null) { + for (Consumer l : documentSaveListeners) { + l.accept(new TextDocumentSaveChange(doc)); + } + } + } + }); + } + } + + @Override + public CompletableFuture> documentHighlight(DocumentHighlightParams highlightParams) { + DocumentHighlightHandler handler = this.documentHighlightHandler; + if (handler != null) { + return CompletableFutures.computeAsync(cancelToken -> { + return handler.handle(highlightParams); + + }); + } + else { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + } + + @Override + public CompletableFuture signatureHelp(SignatureHelpParams signatureHelpParams) { return CompletableFuture.completedFuture(null); } @@ -477,35 +501,14 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE public CompletableFuture rename(RenameParams params) { return CompletableFuture.completedFuture(null); } - - @Override - public void didSave(DidSaveTextDocumentParams params) { - // Workaround for PT 147263283, where error markers in STS are lost on document save. - // STS 3.9.0 does not use the LSP4E editor for edit manifest.yml, which correctly retains error markers after save. - // Instead, because the LSP4E editor is missing support for hovers and completions, STS 3.9.0 uses its own manifest editor - // which extends the YEdit editor. This YEdit editor has a problem, where on save, all error markers are deleted. - // When STS uses the LSP4E editor and no longer needs its own YEdit-based editor, the issue with error markers disappearing - // on save should not be a problem anymore, and the workaround below will no longer be needed. - async.execute(() -> { - if (documentSaveListeners != null) { - TextDocumentIdentifier docId = params.getTextDocument(); - String url = docId.getUri(); - log.debug("didSave: "+url); - if (url != null) { - TextDocument doc = getDocument(url); - if (doc != null) { - for (Consumer l : documentSaveListeners) { - l.accept(new TextDocumentSaveChange(doc)); - } - } - } - } - }); - } + + // + // + // public void publishDiagnostics(TextDocumentIdentifier docId, Collection diagnostics) { LanguageClient client = server.getClient(); - if (client!=null && diagnostics!=null) { + if (client != null && diagnostics != null) { PublishDiagnosticsParams params = new PublishDiagnosticsParams(); params.setUri(docId.getUri()); params.setDiagnostics(ImmutableList.copyOf(diagnostics)); @@ -515,67 +518,82 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE public void setQuickfixes(TextDocumentIdentifier docId, List> quickfixes) { TrackedDocument td = documents.get(docId.getUri()); - if (td!=null) { + if (td != null) { td.setQuickfixes(quickfixes); } } - public synchronized TextDocument get(TextDocumentPositionParams params) { - return get(params.getTextDocument().getUri()); + // + // + // + + public synchronized void onHover(HoverHandler h) { + Assert.isNull("A hover handler is already set, multiple handlers not supported yet", hoverHandler); + this.hoverHandler = h; } - public synchronized TextDocument get(String uri) { - TrackedDocument td = documents.get(uri); - return td == null ? null : td.getDocument(); - } - - @Override - public CompletableFuture> documentHighlight(DocumentHighlightParams highlightParams) { - return async.invoke(() -> { - DocumentHighlightHandler handler = this.documentHighlightHandler; - if (handler != null) { - return handler.handle(highlightParams); - } - return NO_HIGHLIGHTS; - }); - } - - public boolean hasDefinitionHandler() { - return definitionHandler!=null; - } - - public boolean hasReferencesHandler() { - return this.referencesHandler!=null; - } - - public boolean hasDocumentSymbolHandler() { - return this.documentSymbolHandler!=null; - } - - public boolean hasDocumentHighlightHandler() { - return this.documentHighlightHandler!=null; + public synchronized void onCodeLens(CodeLensHandler h) { + Assert.isNull("A code lens handler is already set, multiple handlers not supported yet", codeLensHandler); + this.codeLensHandler = h; } public boolean hasCodeLensHandler() { return this.codeLensHandler != null; } + public synchronized void onCodeLensResolve(CodeLensResolveHandler h) { + Assert.isNull("A code lens resolve handler is already set, multiple handlers not supported yet", codeLensResolveHandler); + this.codeLensResolveHandler = h; + } + public boolean hasCodeLensResolveProvider() { return this.codeLensResolveHandler != null; } - public TextDocument getDocumentSnapshot(TextDocumentIdentifier textDocumentIdentifier) { - try { - return async.invoke(() -> { - TextDocument doc = get(textDocumentIdentifier.getUri()); - if (doc!=null) { - return doc.copy(); - } - return null; - }).get(); - } catch (Exception e) { - throw ExceptionUtil.unchecked(e); - } + public synchronized void onDocumentSymbol(DocumentSymbolHandler h) { + Assert.isNull("A DocumentSymbolHandler is already set, multiple handlers not supported yet", documentSymbolHandler); + this.documentSymbolHandler = h; + } + + public boolean hasDocumentSymbolHandler() { + return this.documentSymbolHandler != null; + } + + public synchronized void onDocumentHighlight(DocumentHighlightHandler h) { + Assert.isNull("A DocumentHighlightHandler is already set, multiple handlers not supported yet", documentHighlightHandler); + this.documentHighlightHandler = h; + } + + public boolean hasDocumentHighlightHandler() { + return this.documentHighlightHandler != null; + } + + public synchronized void onCompletion(CompletionHandler h) { + Assert.isNull("A completion handler is already set, multiple handlers not supported yet", completionHandler); + this.completionHandler = h; + } + + public synchronized void onCompletionResolve(CompletionResolveHandler h) { + Assert.isNull("A completionResolveHandler handler is already set, multiple handlers not supported yet", completionResolveHandler); + this.completionResolveHandler = h; + } + + public synchronized void onDefinition(DefinitionHandler h) { + Assert.isNull("A defintion handler is already set, multiple handlers not supported yet", definitionHandler); + this.definitionHandler = h; + } + + public boolean hasDefinitionHandler() { + return definitionHandler != null; + } + + public synchronized void onReferences(ReferencesHandler h) { + Assert.isNull("A references handler is already set, multiple handlers not supported yet", referencesHandler); + this.referencesHandler = h; + } + + public boolean hasReferencesHandler() { + return this.referencesHandler != null; } } diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java index 11b723dbf..f2eaac2df 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/TextDocument.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2017 Pivotal, Inc. + * Copyright (c) 2016, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -87,7 +87,7 @@ public class TextDocument implements IDocument { public synchronized void apply(DidChangeTextDocumentParams params) throws BadLocationException { int newVersion = params.getTextDocument().getVersion(); - if (version { MissingPropertiesData params = gson.fromJson((JsonElement)_params, MissingPropertiesData.class); try { - TextDocument _doc = textDocumentService.getDocument(params.getUri()); + TextDocument _doc = textDocumentService.getLatestSnapshot(params.getUri()); + if (_doc != null) { YamlDocument doc = new YamlDocument(_doc, structureProvider); SNode root = doc.getStructure(); @@ -95,7 +96,8 @@ public class YamlQuickfixes { SIMPLE_TEXT_EDIT = r.register("SIMPLE_TEXT_EDIT", (_params) -> { try { ReplaceStringData params = gson.fromJson((JsonElement)_params, ReplaceStringData.class); - TextDocument _doc = textDocumentService.getDocument(params.getUri()); + TextDocument _doc = textDocumentService.getLatestSnapshot(params.getUri()); + if (_doc != null) { return new QuickfixEdit( new WorkspaceEdit( diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/TypeBasedYamlSymbolHandler.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/TypeBasedYamlSymbolHandler.java index ece2d77b9..5170cd230 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/TypeBasedYamlSymbolHandler.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/TypeBasedYamlSymbolHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020 Pivotal, Inc. + * Copyright (c) 2017, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -64,7 +64,8 @@ public class TypeBasedYamlSymbolHandler implements DocumentSymbolHandler { @Override public List handle(DocumentSymbolParams params) { Builder builder = ImmutableList.builder(); - TextDocument doc = documents.getDocument(params.getTextDocument().getUri()); + + TextDocument doc = documents.getLatestSnapshot(params.getTextDocument().getUri()); if (doc != null) { for (Entry entry : astTypeCache.getNodeTypes(params.getTextDocument().getUri()).getTypes().entrySet()) { if (definitionTypes.contains(entry.getValue())) { diff --git a/headless-services/commons/language-server-starter/src/main/java/org/springframework/ide/vscode/languageserver/starter/LanguageServerAutoConf.java b/headless-services/commons/language-server-starter/src/main/java/org/springframework/ide/vscode/languageserver/starter/LanguageServerAutoConf.java index a3c649314..36f6e3361 100644 --- a/headless-services/commons/language-server-starter/src/main/java/org/springframework/ide/vscode/languageserver/starter/LanguageServerAutoConf.java +++ b/headless-services/commons/language-server-starter/src/main/java/org/springframework/ide/vscode/languageserver/starter/LanguageServerAutoConf.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2019 Pivotal, Inc. + * Copyright (c) 2018, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -86,9 +86,11 @@ public class LanguageServerAutoConf { handlers.put(l, h); } } + ImmutableMap immutableMap = ImmutableMap.copyOf(handlers); return () -> documents.onDefinition((position) -> { - TextDocument doc = documents.get(position.getTextDocument().getUri()); + TextDocument doc = documents.getLatestSnapshot(position); + if (doc != null) { LanguageId language = doc.getLanguageId(); DefinitionHandler handler = immutableMap.get(language); diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java index c60f7ff85..a89f5d1a3 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -92,7 +92,7 @@ public class ConcourseDefinitionFinder extends SimpleDefinitionFinder { @Override public List handle(DefinitionParams params) { try { - TextDocument doc = server.getTextDocumentService().get(params); + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(params); if (doc!=null) { YamlFileAST ast = asts.getSafeAst(doc, false); if (ast!=null) { diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServerInitializer.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServerInitializer.java index a23b1974d..0d8dc3cae 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServerInitializer.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServerInitializer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016 Pivotal, Inc. + * Copyright (c) 2016, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -142,8 +142,8 @@ public class ConcourseLanguageServerInitializer { // }); documents.onCompletion(params -> { - TextDocument doc = documents.get(params); - if (doc!=null) { + TextDocument doc = documents.getLatestSnapshot(params); + if (doc != null) { if (LanguageId.CONCOURSE_PIPELINE.equals(doc.getLanguageId())) { return forPipelines.completionEngine.getCompletions(params); } else if (LanguageId.CONCOURSE_TASK.equals(doc.getLanguageId())) { @@ -159,8 +159,8 @@ public class ConcourseLanguageServerInitializer { documents.onHover(params -> { log.debug("Concourse hover handler starting"); try { - TextDocument doc = documents.get(params); - if (doc!=null) { + TextDocument doc = documents.getLatestSnapshot(params); + if (doc != null) { LanguageId languageId = doc.getLanguageId(); if (LanguageId.CONCOURSE_PIPELINE.equals(doc.getLanguageId())) { return forPipelines.hoverEngine.handle(params); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/PropertiesJavaDefinitionHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/PropertiesJavaDefinitionHandler.java index 13c4214ee..6da836a3f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/PropertiesJavaDefinitionHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/PropertiesJavaDefinitionHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2020 Pivotal, Inc. + * Copyright (c) 2018, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -63,7 +63,7 @@ public class PropertiesJavaDefinitionHandler implements DefinitionHandler, Langu @Override public List handle(DefinitionParams definitionParams) { try { - TextDocument doc = documents.get(definitionParams); + TextDocument doc = documents.getLatestSnapshot(definitionParams); TypeUtil typeUtil = params.typeUtilProvider.getTypeUtil(sourceLinks, doc); FuzzyMap index = params.indexProvider.getIndex(doc).getProperties(); int offset; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/XmlBeansConfigDefinitionHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/XmlBeansConfigDefinitionHandler.java index 9dd1c8a12..7687edc51 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/XmlBeansConfigDefinitionHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/XmlBeansConfigDefinitionHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Pivotal, Inc. + * Copyright (c) 2019, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -99,7 +99,7 @@ public class XmlBeansConfigDefinitionHandler implements DefinitionHandler, Langu JavaTypeHyperlinkProvider javaTypeHyperlinkProvider = new JavaTypeHyperlinkProvider(projectFinder, locationProvider); PropertyNameHyperlinkProvider propertyNameHyperlinkProvider = new PropertyNameHyperlinkProvider(projectFinder, locationProvider); - BeanRefHyperlinkProvider beanRefHyperlinkProvider = new BeanRefHyperlinkProvider(projectFinder, symbolIndex, documents); + BeanRefHyperlinkProvider beanRefHyperlinkProvider = new BeanRefHyperlinkProvider(projectFinder, symbolIndex); List typeHandlersOnly = Arrays.asList(javaTypeHyperlinkProvider); List propertyNameHandlers = Arrays.asList(propertyNameHyperlinkProvider); @@ -143,7 +143,7 @@ public class XmlBeansConfigDefinitionHandler implements DefinitionHandler, Langu public List handle(DefinitionParams params) { try { if (config.isSpringXMLSupportEnabled() && config.areXmlHyperlinksEnabled()) { - TextDocument doc = documents.get(params); + TextDocument doc = documents.getLatestSnapshot(params); if (doc != null) { String content = doc.get(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/YamlPropertiesJavaDefinitionHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/YamlPropertiesJavaDefinitionHandler.java index 9a3abbe6e..48238b12d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/YamlPropertiesJavaDefinitionHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/YamlPropertiesJavaDefinitionHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2020 Pivotal, Inc. + * Copyright (c) 2018, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -70,7 +70,7 @@ public class YamlPropertiesJavaDefinitionHandler implements DefinitionHandler, L @Override public List handle(DefinitionParams definitionParams) { try { - TextDocument doc = documents.get(definitionParams); + TextDocument doc = documents.getLatestSnapshot(definitionParams); int offset = doc.toOffset(definitionParams.getPosition()); YamlFileAST ast = getAst(doc); if (ast != null) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeLensEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeLensEngine.java index abcd6968e..e24c610a3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeLensEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeLensEngine.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2018 Pivotal, Inc. + * Copyright (c) 2017, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -40,8 +40,8 @@ public class BootJavaCodeLensEngine implements CodeLensHandler { SimpleTextDocumentService documents = server.getTextDocumentService(); String docURI = params.getTextDocument().getUri(); - if (documents.get(docURI) != null) { - TextDocument doc = documents.get(docURI).copy(); + TextDocument doc = documents.getLatestSnapshot(docURI); + if (doc != null) { // Spring Boot LS get events from boot properties files as well, so filter them out if (server.getInterestingLanguages().contains(doc.getLanguageId())) { try { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaDocumentHighlightEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaDocumentHighlightEngine.java index c69985059..e93b466a3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaDocumentHighlightEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaDocumentHighlightEngine.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2020 Pivotal, Inc. + * Copyright (c) 2018, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -42,10 +42,9 @@ public class BootJavaDocumentHighlightEngine implements DocumentHighlightHandler @Override public List handle(DocumentHighlightParams params) { SimpleTextDocumentService documents = server.getTextDocumentService(); - String docURI = params.getTextDocument().getUri(); - - if (documents.get(docURI) != null) { - TextDocument doc = documents.get(docURI).copy(); + TextDocument doc = documents.getLatestSnapshot(params); + + if (doc != null) { // Spring Boot LS get events from boot properties files as well, so filter them out if (doc != null && server.getInterestingLanguages().contains(doc.getLanguageId())) { try { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java index 0627ad8bf..3da23626b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaHoverProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020 Pivotal, Inc. + * Copyright (c) 2017, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -73,8 +73,9 @@ public class BootJavaHoverProvider implements HoverHandler { @Override public Hover handle(HoverParams params) { SimpleTextDocumentService documents = server.getTextDocumentService(); - if (documents.get(params) != null) { - TextDocument doc = documents.get(params).copy(); + TextDocument doc = documents.getLatestSnapshot(params); + + if (doc != null) { // Spring Boot LS get events from boot properties files as well, so filter them out if (server.getInterestingLanguages().contains(doc.getLanguageId())) { try { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java index 474dd5b0a..bcd601ec8 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020 Pivotal, Inc. + * Copyright (c) 2017, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -53,8 +53,9 @@ public class BootJavaReferencesHandler implements ReferencesHandler { @Override public List handle(ReferenceParams params) { - SimpleTextDocumentService documents = server.getTextDocumentService(); - TextDocument doc = documents.get(params).copy(); + SimpleTextDocumentService documents = server.getTextDocumentService(); + TextDocument doc = documents.getLatestSnapshot(params); + if (doc != null) { // Spring Boot LS get events from boot properties files as well, so filter them out if (server.getInterestingLanguages().contains(doc.getLanguageId())) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveHoverUpdater.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveHoverUpdater.java index 350008eb1..82d0676ea 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveHoverUpdater.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveHoverUpdater.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2019 Pivotal, Inc. + * Copyright (c) 2017, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -37,10 +37,10 @@ public class SpringProcessLiveHoverUpdater { private final SimpleLanguageServer server; private final BootJavaHoverProvider hoverProvider; + private final JavaProjectFinder projectFinder; - private boolean highlightsEnabled = true; - private JavaProjectFinder projectFinder; private final Map> watchedDocs; + private boolean highlightsEnabled = true; public SpringProcessLiveHoverUpdater( SimpleLanguageServer server, @@ -56,11 +56,7 @@ public class SpringProcessLiveHoverUpdater { server.getTextDocumentService().onDidChangeContent(params -> { TextDocument doc = params.getDocument(); if (BootJavaLanguageServerComponents.LANGUAGES.contains(doc.getLanguageId())) { - try { - watchDocument(doc.getUri()); - } catch (Throwable t) { - log.error("", t); - } + watchDocument(doc.getUri()); } }); @@ -81,7 +77,14 @@ public class SpringProcessLiveHoverUpdater { public void watchDocument(String docURI) { this.watchedDocs.putIfAbsent(docURI, new AtomicReference()); - updateDoc(docURI); + + CompletableFuture.runAsync(() -> { + try { + updateDoc(docURI); + } catch (Throwable t) { + log.error("", t); + } + }); } public void unwatchDocument(String docURI) { @@ -89,6 +92,7 @@ public class SpringProcessLiveHoverUpdater { cleanupLiveHints(docURI); } + // runs async private void updateDoc(String docURI) { try { IJavaProject project = getCachedProject(docURI); @@ -99,6 +103,7 @@ public class SpringProcessLiveHoverUpdater { } } + // runs async private void update() { if (this.watchedDocs.size() > 0) { try { @@ -112,10 +117,11 @@ public class SpringProcessLiveHoverUpdater { } } + // runs async private void update(String docURI, IJavaProject project) { if (highlightsEnabled) { try { - TextDocument doc = this.server.getTextDocumentService().get(docURI); + TextDocument doc = this.server.getTextDocumentService().getLatestSnapshot(docURI); if (doc != null) { CodeLens[] infos = this.hoverProvider.getLiveHoverHints(doc, project); publishLiveHints(docURI, infos); @@ -126,6 +132,7 @@ public class SpringProcessLiveHoverUpdater { } } + // runs async private IJavaProject getCachedProject(String docURI) { AtomicReference reference = this.watchedDocs.get(docURI); if (reference != null) { @@ -141,8 +148,9 @@ public class SpringProcessLiveHoverUpdater { return null; } + // runs async private IJavaProject identifyProject(String docURI) { - TextDocument doc = this.server.getTextDocumentService().get(docURI); + TextDocument doc = this.server.getTextDocumentService().getLatestSnapshot(docURI); if (doc != null) { return projectFinder.find(doc.getId()).orElse(null); } @@ -151,8 +159,9 @@ public class SpringProcessLiveHoverUpdater { } } + // runs sync or async private void publishLiveHints(String docURI, CodeLens[] codeLenses) { - TextDocument doc = server.getTextDocumentService().get(docURI); + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docURI); if (doc != null) { int version = doc.getVersion(); VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(docURI, version); @@ -160,8 +169,9 @@ public class SpringProcessLiveHoverUpdater { } } + // runs sync or async private void cleanupLiveHints(String docURI) { publishLiveHints(docURI, new CodeLens[0]); } - + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java index 16823c289..9fdadd903 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2020 Pivotal, Inc. + * Copyright (c) 2017, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -18,9 +18,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import java.util.function.Function; import java.util.stream.Stream; @@ -45,13 +42,11 @@ import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFin import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; -import org.springframework.ide.vscode.commons.util.AsyncRunner; import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import reactor.core.scheduler.Schedulers; import reactor.util.function.Tuple2; import reactor.util.function.Tuples; @@ -62,95 +57,94 @@ public final class CompilationUnitCache implements DocumentContentProvider { private static final long CU_ACCESS_EXPIRATION = 1; private JavaProjectFinder projectFinder; private ProjectObserver projectObserver; - private Cache uriToCu; - private Cache> projectToDocs; - private Cache, INameEnvironmentWithProgress>> lookupEnvCache; - private ProjectObserver.Listener projectListener; - private SimpleTextDocumentService documents; - private AsyncRunner async; + + private final ProjectObserver.Listener projectListener; + private final SimpleTextDocumentService documentService; +// private AsyncRunner async; - private ReadLock readLock; - private WriteLock writeLock; + private final Cache uriToCu; + private final Cache> projectToDocs; + private final Cache, INameEnvironmentWithProgress>> lookupEnvCache; + +// private ReadLock readLock; +// private WriteLock writeLock; public CompilationUnitCache(JavaProjectFinder projectFinder, SimpleLanguageServer server, ProjectObserver projectObserver) { this.projectFinder = projectFinder; this.projectObserver = projectObserver; - this.lookupEnvCache = CacheBuilder.newBuilder().build(); // PT 154618835 - Avoid retaining the CU in the cache as it consumes memory if it hasn't been // accessed after some time - uriToCu = CacheBuilder.newBuilder() + this.uriToCu = CacheBuilder.newBuilder() .expireAfterWrite(CU_ACCESS_EXPIRATION, TimeUnit.MINUTES) .build(); - projectToDocs = CacheBuilder.newBuilder().build(); + this.projectToDocs = CacheBuilder.newBuilder().build(); + this.lookupEnvCache = CacheBuilder.newBuilder().build(); - ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - readLock = lock.readLock(); - writeLock = lock.writeLock(); +// ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); +// this.readLock = lock.readLock(); +// this.writeLock = lock.writeLock(); - this.documents = server == null ? null : server.getTextDocumentService(); - this.async = server == null ? new AsyncRunner(Schedulers.single()) : server.getAsync(); + this.documentService = server == null ? null : server.getTextDocumentService(); +// this.async = server == null ? new AsyncRunner(Schedulers.single()) : server.getAsync(); - if (documents != null) { - documents.onDidChangeContent(doc -> invalidateCuForJavaFile(doc.getDocument().getId().getUri())); - documents.onDidClose(doc -> invalidateCuForJavaFile(doc.getId().getUri())); + // IMPORTANT ===> these notifications arrive within the lsp message loop, so reactions to them have to be fast + // and not be blocked by waiting for anything + if (documentService != null) { + documentService.onDidChangeContent(doc -> invalidateCuForJavaFile(doc.getDocument().getId().getUri())); + documentService.onDidClose(doc -> invalidateCuForJavaFile(doc.getId().getUri())); + } + + for (IJavaProject project : projectFinder.all()) { + logger.info("CU Cache: initial lookup env creation for project <{}>", project.getElementName()); + loadLookupEnvTuple(project); } - async.execute(() -> { - writeLock.lock(); - try { - for (IJavaProject project : projectFinder.all()) { - loadLookupEnvTuple(project); - } - } finally { - writeLock.unlock(); - } - }); projectListener = new ProjectObserver.Listener() { @Override public void deleted(IJavaProject project) { logger.info("CU Cache: deleted project {}", project.getElementName()); - async.execute(() -> { - writeLock.lock(); - try { +// async.execute(() -> { +// writeLock.lock(); +// try { invalidateProject(project); - } finally { - writeLock.unlock(); - } - }); +// } finally { +// writeLock.unlock(); +// } +// }); } @Override public void created(IJavaProject project) { logger.info("CU Cache: created project {}", project.getElementName()); - async.execute(() -> { - writeLock.lock(); - try { +// async.execute(() -> { +// writeLock.lock(); +// try { invalidateProject(project); // Load the new cache the value right away loadLookupEnvTuple(project); - } finally { - writeLock.unlock(); - } - }); +// } finally { +// writeLock.unlock(); +// } +// }); } @Override public void changed(IJavaProject project) { logger.info("CU Cache: changed project {}", project.getElementName()); - async.execute(() -> { - writeLock.lock(); - try { +// async.execute(() -> { +// writeLock.lock(); +// try { invalidateProject(project); // Load the new cache the value right away loadLookupEnvTuple(project); - } finally { - writeLock.unlock(); - } - }); +// } finally { +// writeLock.unlock(); +// } +// }); } }; @@ -182,9 +176,10 @@ public final class CompilationUnitCache implements DocumentContentProvider { } public T withCompilationUnit(IJavaProject project, URI uri, Function requestor) { + logger.info("CU Cache: work item for doc {}", uri.toString()); + if (project != null) { - readLock.lock(); CompilationUnit cu = null; try { @@ -193,20 +188,23 @@ public final class CompilationUnitCache implements DocumentContentProvider { String utiStr = uri.toString(); String unitName = utiStr.substring(utiStr.lastIndexOf("/")); CompilationUnit cUnit = parse2(fetchContent(uri).toCharArray(), utiStr, unitName, lookupEnvTuple.getT1(), lookupEnvTuple.getT2()); - projectToDocs.get(project, () -> new HashSet<>()).add(uri); + + logger.info("CU Cache: created new AST for {}", uri.toString()); + return cUnit; }); + if (cu != null) { projectToDocs.get(project, () -> new HashSet<>()).add(uri); } + } catch (Exception e) { logger.error("", e); - } finally { - readLock.unlock(); } if (cu != null) { try { + logger.info("CU Cache: sync start on AST for {}", uri.toString()); synchronized (cu.getAST()) { return requestor.apply(cu); } @@ -214,6 +212,9 @@ public final class CompilationUnitCache implements DocumentContentProvider { catch (Exception e) { logger.error("", e); } + finally { + logger.info("CU Cache: sync end on AST for {}", uri.toString()); + } } } @@ -221,51 +222,6 @@ public final class CompilationUnitCache implements DocumentContentProvider { } - private void invalidateCuForJavaFile(String uriStr) { - URI uri = URI.create(uriStr); - writeLock.lock(); - try { - uriToCu.invalidate(uri); - } finally { - writeLock.unlock(); - } - } - -// public static CompilationUnit parse(TextDocument document, IJavaProject project) throws Exception { -// String[] classpathEntries = getClasspathEntries(project); -// String docURI = document.getUri(); -// String unitName = docURI.substring(docURI.lastIndexOf("/")); -// char[] source = document.get(0, document.getLength()).toCharArray(); -// return parse(source, docURI, unitName, classpathEntries); -// } -// -// public CompilationUnit parse(String uri, char[] source, IJavaProject project) throws Exception { -// String[] classpathEntries = getClasspathEntries(project); -// String unitName = uri.substring(uri.lastIndexOf("/")); -// return parse(source, uri, unitName, classpathEntries); -// } -// -// public static CompilationUnit parse(char[] source, String docURI, String unitName, String[] classpathEntries) throws Exception { -// ASTParser parser = ASTParser.newParser(AST.JLS11); -// Map options = JavaCore.getOptions(); -// JavaCore.setComplianceOptions(JavaCore.VERSION_11, options); -// parser.setCompilerOptions(options); -// parser.setKind(ASTParser.K_COMPILATION_UNIT); -// parser.setStatementsRecovery(true); -// parser.setBindingsRecovery(true); -// parser.setResolveBindings(true); -// -// String[] sourceEntries = new String[] {}; -// parser.setEnvironment(classpathEntries, sourceEntries, null, false); -// -// parser.setUnitName(unitName); -// parser.setSource(source); -// -// CompilationUnit cu = (CompilationUnit) parser.createAST(null); -// -// return cu; -// } - public static CompilationUnit parse2(char[] source, String docURI, String unitName, IJavaProject project) throws Exception { List classpaths = createClasspath(getClasspathEntries(project)); return parse2(source, docURI, unitName, classpaths, null); @@ -294,7 +250,7 @@ public final class CompilationUnitCache implements DocumentContentProvider { needToResolveBindings = false; } - CompilationUnit cu = CUResolver.convert(unit, source, AST.JLS11, options, needToResolveBindings, DefaultWorkingCopyOwner.PRIMARY, flags); + CompilationUnit cu = CUResolver.convert(unit, source, AST.JLS14, options, needToResolveBindings, DefaultWorkingCopyOwner.PRIMARY, flags); return cu; } @@ -331,7 +287,16 @@ public final class CompilationUnitCache implements DocumentContentProvider { } } + private void invalidateCuForJavaFile(String uriStr) { + logger.info("CU Cache: invalidate AST for {}", uriStr); + + URI uri = URI.create(uriStr); + uriToCu.invalidate(uri); + } + private void invalidateProject(IJavaProject project) { + logger.info("CU Cache: invalidate project <{}>", project.getElementName()); + Set docUris = projectToDocs.getIfPresent(project); if (docUris != null) { uriToCu.invalidateAll(docUris); @@ -342,13 +307,13 @@ public final class CompilationUnitCache implements DocumentContentProvider { @Override public String fetchContent(URI uri) throws Exception { - if (documents != null) { - TextDocument document = documents.get(uri.toString()); + if (documentService != null) { + TextDocument document = documentService.getLatestSnapshot(uri.toString()); if (document != null) { - return document.get(0, document.getLength()); + return document.get(); } } return IOUtils.toString(uri); - } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/hyperlinks/BeanRefHyperlinkProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/hyperlinks/BeanRefHyperlinkProvider.java index af5c208c7..85fe606d8 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/hyperlinks/BeanRefHyperlinkProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/hyperlinks/BeanRefHyperlinkProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Pivotal, Inc. + * Copyright (c) 2019, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -24,7 +24,6 @@ import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformati import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** @@ -34,12 +33,10 @@ public class BeanRefHyperlinkProvider implements XMLHyperlinkProvider { private final JavaProjectFinder projectFinder; private final SpringSymbolIndex symbolIndex; - private final SimpleTextDocumentService documents; - public BeanRefHyperlinkProvider(JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex, SimpleTextDocumentService documents) { + public BeanRefHyperlinkProvider(JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex) { this.projectFinder = projectFinder; this.symbolIndex = symbolIndex; - this.documents = documents; } @Override @@ -47,11 +44,26 @@ public class BeanRefHyperlinkProvider implements XMLHyperlinkProvider { Optional foundProject = this.projectFinder.find(doc.getId()); if (foundProject.isPresent()) { final IJavaProject project = foundProject.get(); + String projectLocation = project.getLocationUri() != null ? project.getLocationUri().toString() : ""; + + // make sure the project and the symbol location share the same prefix "file:///" + // looks like project locations are containing a "file:/" only + if (!projectLocation.startsWith("file:///")) { + projectLocation = "file:///" + projectLocation.substring("file:/".length()); + } + + // make sure that only exact project locations are matched + if (!projectLocation.endsWith("/")) { + projectLocation = projectLocation + "/"; + } + List symbols = symbolIndex.getSymbols(data -> symbolsFilter(data, attributeAt.getValue())).collect(Collectors.toList()); if (!symbols.isEmpty()) { for (SymbolInformation symbol : symbols) { Location location = symbol.getLocation(); - if (project == documents.get(location.getUri())) { + String uri = location.getUri(); + + if (uri != null && uri.startsWith(projectLocation)) { return location; } } @@ -63,7 +75,7 @@ public class BeanRefHyperlinkProvider implements XMLHyperlinkProvider { private boolean symbolsFilter(EnhancedSymbolInformation data, String beanId) { SymbolAddOnInformation[] additionalInformation = data.getAdditionalInformation(); - if (additionalInformation != null) { + if (beanId != null && additionalInformation != null) { for (SymbolAddOnInformation info : additionalInformation) { if (info instanceof BeansSymbolAddOnInformation) { return beanId.equals(((BeansSymbolAddOnInformation)info).getBeanID()); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/AppYamlQuickfixes.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/AppYamlQuickfixes.java index d199776cd..5acec42c0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/AppYamlQuickfixes.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/quickfix/AppYamlQuickfixes.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Pivotal, Inc. + * Copyright (c) 2019, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -63,7 +63,8 @@ public class AppYamlQuickfixes { DEPRECATED_PROPERTY = r.register("DEPRECATED_YAML_PROPERTY", (Object _params) -> { DeprecatedPropertyData params = gson.fromJson((JsonElement)_params, DeprecatedPropertyData.class); try { - TextDocument _doc = textDocumentService.getDocument(params.getUri()); + TextDocument _doc = textDocumentService.getLatestSnapshot(params.getUri()); + if (_doc != null) { YamlDocument doc = new YamlDocument(_doc, structureProvider); SNode root = doc.getStructure(); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java index 257b85c3f..649a62e25 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020 Pivotal, Inc. + * Copyright (c) 2020, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -316,7 +316,7 @@ public class ValueSpelExpressionValidationTest { server.getTextDocumentService().didOpen(openParams); server.getAsync().waitForAll(); - TextDocument doc = server.getTextDocumentService().get(docUri); + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docUri); int position = content.indexOf(selectedAnnotation); doc.replace(position, selectedAnnotation.length(), annotationStatementBeforeTest); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/XMLSpelExpressionValidationTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/XMLSpelExpressionValidationTest.java index 6cf0ed83b..c1121a4a0 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/XMLSpelExpressionValidationTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/XMLSpelExpressionValidationTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020 Pivotal, Inc. + * Copyright (c) 2020, 2021 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -141,7 +141,7 @@ public class XMLSpelExpressionValidationTest { server.getTextDocumentService().didOpen(openParams); server.getAsync().waitForAll(); - TextDocument doc = server.getTextDocumentService().get(docUri); + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docUri); int position = content.indexOf(selectedAnnotation); doc.replace(position, selectedAnnotation.length(), annotationStatementBeforeTest);