initial re-work of async behavior of language server, looking at the text document service first

This commit is contained in:
Martin Lippert
2021-02-06 08:49:05 +01:00
parent 6e0bb81cbe
commit 8238c1dc90
26 changed files with 463 additions and 450 deletions

View File

@@ -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<LocationLink> 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()));

View File

@@ -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<CompletionList> 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) {

View File

@@ -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) {

View File

@@ -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<LocationLink> 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;

View File

@@ -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());

View File

@@ -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());

View File

@@ -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<String, TrackedDocument> documents = new HashMap<>();
private ListenerList<TextDocumentContentChange> documentChangeListeners = new ListenerList<>();
private ListenerList<TextDocument> documentCloseListeners = new ListenerList<>();
private ListenerList<TextDocument> documentOpenListeners = new ListenerList<>();
private final SimpleLanguageServer server;
private final LanguageServerProperties props;
private final ConcurrentMap<String, TrackedDocument> documents = new ConcurrentHashMap<>();
private final ConcurrentMap<String, TextDocument> documentSnapshots = new ConcurrentHashMap<>();
private CompletionHandler completionHandler = null;
private CompletionResolveHandler completionResolveHandler = null;
private final ListenerList<TextDocumentContentChange> documentChangeListeners = new ListenerList<>();
private final ListenerList<TextDocument> documentCloseListeners = new ListenerList<>();
private final ListenerList<TextDocument> documentOpenListeners = new ListenerList<>();
private List<Consumer<TextDocumentSaveChange>> 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<Consumer<TextDocumentSaveChange>> 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<TextDocument> getAll() {
return documents.values().stream()
.map((td) -> td.getDocument())
public Collection<TextDocument> 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<TextDocumentContentChangeEvent> 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<TextDocumentContentChangeEvent> changes) {
documentChangeListeners.fire(new TextDocumentContentChange(doc, changes));
}
public void onDidOpen(Consumer<TextDocument> 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<? extends Location> NO_REFERENCES = ImmutableList.of();
@@ -304,8 +248,10 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE
@Override
public CompletableFuture<Either<List<CompletionItem>, 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::<List<CompletionItem>, CompletionList>forRight)
.toFuture();
@@ -316,13 +262,16 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE
@Override
public CompletableFuture<CompletionItem> 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> 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> signatureHelp(SignatureHelpParams signatureHelpParams) {
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(
DefinitionParams definitionParams) {
DefinitionHandler h = this.definitionHandler;
if (h != null) {
return async.invoke(() -> {
return CompletableFutures.computeAsync(cancelToken -> {
List<LocationLink> 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<List<? extends Location>> references(ReferenceParams params) {
return async.invoke(() -> {
ReferencesHandler h = this.referencesHandler;
if (h != null) {
List<? extends Location> list = h.handle(params);
return list != null && list.isEmpty() ? null : list;
return CompletableFutures.computeAsync(cancelToken -> {
List<? extends Location> list = h.handle(params);
return list != null && list.isEmpty() ? null : list;
});
}
else {
return CompletableFuture.completedFuture(ImmutableList.of());
}
return null;
});
}
@Override
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> 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<? extends DocumentSymbol> 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.<SymbolInformation, DocumentSymbol>forLeft(symbolInfo))
.collect(Collectors.toList());
}
}
return ImmutableList.of();
});
});
}
else {
return CompletableFuture.completedFuture(ImmutableList.of());
}
}
@Override
public CompletableFuture<List<Either<Command, CodeAction>>> 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<Either<Command,CodeAction>> list = doc.getQuickfixes().stream()
.filter((fix) -> fix.appliesTo(params.getRange(), params.getContext()))
.map(Quickfix::getCodeAction)
.map(command -> Either.<Command, CodeAction>forLeft(command))
.collect(CollectorUtil.toImmutableList());
return list;
.filter((fix) -> fix.appliesTo(params.getRange(), params.getContext()))
.map(Quickfix::getCodeAction)
.map(command -> Either.<Command, CodeAction>forLeft(command))
.collect(CollectorUtil.toImmutableList());
return CompletableFuture.completedFuture(list);
} else {
return ImmutableList.of();
return CompletableFuture.completedFuture(ImmutableList.of());
}
});
}
@Override
public CompletableFuture<List<? extends CodeLens>> 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<CodeLens> 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<TextDocumentSaveChange> l : documentSaveListeners) {
l.accept(new TextDocumentSaveChange(doc));
}
}
}
});
}
}
@Override
public CompletableFuture<List<? extends DocumentHighlight>> 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> signatureHelp(SignatureHelpParams signatureHelpParams) {
return CompletableFuture.completedFuture(null);
}
@@ -477,35 +501,14 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE
public CompletableFuture<WorkspaceEdit> 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<TextDocumentSaveChange> l : documentSaveListeners) {
l.accept(new TextDocumentSaveChange(doc));
}
}
}
}
});
}
//
//
//
public void publishDiagnostics(TextDocumentIdentifier docId, Collection<Diagnostic> 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<Quickfix<?>> 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<List<? extends DocumentHighlight>> 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;
}
}

View File

@@ -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<newVersion) {
if (version < newVersion) {
for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
apply(change);
}
@@ -290,7 +290,7 @@ public class TextDocument implements IDocument {
}
public TextDocumentIdentifier getId() {
if (uri!=null) {
if (uri != null) {
return new TextDocumentIdentifier(uri);
}
return null;

View File

@@ -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
@@ -56,7 +56,8 @@ public class YamlQuickfixes {
MISSING_PROP_FIX = r.register("MISSING_PROP_FIX", (Object _params) -> {
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(

View File

@@ -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<? extends SymbolInformation> handle(DocumentSymbolParams params) {
Builder<SymbolInformation> builder = ImmutableList.builder();
TextDocument doc = documents.getDocument(params.getTextDocument().getUri());
TextDocument doc = documents.getLatestSnapshot(params.getTextDocument().getUri());
if (doc != null) {
for (Entry<Node, YType> entry : astTypeCache.getNodeTypes(params.getTextDocument().getUri()).getTypes().entrySet()) {
if (definitionTypes.contains(entry.getValue())) {

View File

@@ -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<LanguageId, DefinitionHandler> 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);

View File

@@ -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<LocationLink> 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) {

View File

@@ -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);

View File

@@ -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<LocationLink> handle(DefinitionParams definitionParams) {
try {
TextDocument doc = documents.get(definitionParams);
TextDocument doc = documents.getLatestSnapshot(definitionParams);
TypeUtil typeUtil = params.typeUtilProvider.getTypeUtil(sourceLinks, doc);
FuzzyMap<PropertyInfo> index = params.indexProvider.getIndex(doc).getProperties();
int offset;

View File

@@ -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<JavaTypeHyperlinkProvider> typeHandlersOnly = Arrays.asList(javaTypeHyperlinkProvider);
List<PropertyNameHyperlinkProvider> propertyNameHandlers = Arrays.asList(propertyNameHyperlinkProvider);
@@ -143,7 +143,7 @@ public class XmlBeansConfigDefinitionHandler implements DefinitionHandler, Langu
public List<LocationLink> 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();

View File

@@ -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<LocationLink> 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) {

View File

@@ -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 {

View File

@@ -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<DocumentHighlight> 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 {

View File

@@ -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 {

View File

@@ -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<? extends Location> 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())) {

View File

@@ -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<String, AtomicReference<IJavaProject>> 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<IJavaProject>());
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<IJavaProject> 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]);
}
}

View File

@@ -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<URI, CompilationUnit> uriToCu;
private Cache<IJavaProject, Set<URI>> projectToDocs;
private Cache<IJavaProject, Tuple2<List<Classpath>, 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<URI, CompilationUnit> uriToCu;
private final Cache<IJavaProject, Set<URI>> projectToDocs;
private final Cache<IJavaProject, Tuple2<List<Classpath>, 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> T withCompilationUnit(IJavaProject project, URI uri, Function<CompilationUnit, T> 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<String, String> 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<Classpath> 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<URI> 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);
}
}

View File

@@ -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<IJavaProject> 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<SymbolInformation> 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());

View File

@@ -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();

View File

@@ -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);

View File

@@ -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);