switched completion handling to use CompletableFuture instead of reactor Mono and added cancel handling
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2018 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
|
||||
@@ -14,14 +14,13 @@ package org.springframework.ide.vscode.commons.languageserver.completion;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
|
||||
/**
|
||||
* Interface that needs to be implemented by a 'completion engine' which can be easily
|
||||
* wired-up to provide completions for a Vscode language server.
|
||||
*/
|
||||
public interface VscodeCompletionEngine {
|
||||
Mono<CompletionList> getCompletions(TextDocumentPositionParams params);
|
||||
CompletionItem resolveCompletion(CompletionItem unresolved);
|
||||
CompletionList getCompletions(CancelChecker cancelToken, TextDocumentPositionParams params);
|
||||
CompletionItem resolveCompletion(CancelChecker cancelToken, CompletionItem unresolved);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -28,6 +29,7 @@ import org.eclipse.lsp4j.MarkupKind;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.TextReplace;
|
||||
@@ -43,9 +45,6 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
/**
|
||||
* Adapts a {@link ICompletionEngine}, wrapping it, to implement {@link VscodeCompletionEngine}
|
||||
*/
|
||||
@@ -77,7 +76,7 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
return id;
|
||||
}
|
||||
|
||||
public synchronized void resolveNow(CompletionItem unresolved) {
|
||||
public synchronized void resolveNow(CancelChecker cancelToken, CompletionItem unresolved) {
|
||||
Object id = unresolved.getData();
|
||||
if (id!=null) {
|
||||
Consumer<CompletionItem> resolver = resolvers.get(id instanceof JsonPrimitive ? ((JsonPrimitive)id).getAsString() : id);
|
||||
@@ -122,39 +121,49 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<CompletionList> getCompletions(TextDocumentPositionParams params) {
|
||||
return getCompletionsMono(params);
|
||||
}
|
||||
|
||||
private Mono<CompletionList> getCompletionsMono(TextDocumentPositionParams params) {
|
||||
public CompletionList getCompletions(CancelChecker cancelToken, TextDocumentPositionParams params) {
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
|
||||
TextDocument doc = documents.getLatestSnapshot(params);
|
||||
if (doc != null) {
|
||||
|
||||
CompletionList list = new CompletionList();
|
||||
|
||||
return Mono.fromCallable(() -> {
|
||||
try {
|
||||
log.info("Starting completion handling");
|
||||
|
||||
if (resolver!=null) {
|
||||
//Assumes we don't have more than one completion request in flight from the client.
|
||||
// So when a new request arrives we can forget about the old unresolved items:
|
||||
resolver.clear();
|
||||
}
|
||||
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
//TODO: This callable is a 'big lump of work' so can't be canceled in pieces.
|
||||
// Should we push using of reactive streams down further and compose this all
|
||||
// using reactive style? If not then this is overkill could just as well use
|
||||
// only standard Java API such as Executor and CompletableFuture directly.
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
List<ICompletionProposal> completions = filter(engine.getCompletions(doc, offset));
|
||||
|
||||
// get completions
|
||||
Collection<ICompletionProposal> rawCompletions = engine.getCompletions(doc, offset);
|
||||
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
List<ICompletionProposal> completions = filter(rawCompletions);
|
||||
Collections.sort(completions, ScoreableProposal.COMPARATOR);
|
||||
|
||||
CompletionList list = new CompletionList();
|
||||
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
list.setIsIncomplete(false);
|
||||
List<CompletionItem> items = new ArrayList<>(completions.size());
|
||||
SortKeys sortkeys = new SortKeys();
|
||||
int count = 0;
|
||||
|
||||
for (ICompletionProposal c : completions) {
|
||||
count++;
|
||||
|
||||
if (maxCompletions > 0 && count>maxCompletions) {
|
||||
list.setIsIncomplete(true);
|
||||
break;
|
||||
@@ -165,21 +174,32 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
log.error("error computing completion", e);
|
||||
}
|
||||
}
|
||||
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
list.setItems(items);
|
||||
//This is a hack is no longer needed but keeping it as a reference:
|
||||
// See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=535823
|
||||
// Reason hack is not needed is because of the fix in: https://www.pivotaltracker.com/story/show/159667257
|
||||
|
||||
// if (LspClient.currentClient()==Client.ECLIPSE) {
|
||||
// list.setIsIncomplete(true);
|
||||
// }
|
||||
|
||||
// if (LspClient.currentClient()==Client.ECLIPSE) {
|
||||
// list.setIsIncomplete(true);
|
||||
// }
|
||||
return list;
|
||||
})
|
||||
.doOnNext(x -> log.info("Got {} completions", x.getItems().size()))
|
||||
.doAfterTerminate(() -> log.info("Completion handling terminated!"))
|
||||
.subscribeOn(Schedulers.elastic()); //!!! without this the mono will just be computed on the same thread that calls it.
|
||||
}
|
||||
catch (CancellationException e) {
|
||||
log.info("compututing completions cancellled", e);
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.info("error while compututing completions", e);
|
||||
}
|
||||
finally {
|
||||
log.info("Got {} completions", list.getItems().size());
|
||||
}
|
||||
}
|
||||
return Mono.just(SimpleTextDocumentService.NO_COMPLETIONS);
|
||||
|
||||
return SimpleTextDocumentService.NO_COMPLETIONS;
|
||||
}
|
||||
|
||||
private CompletionItem adaptItem(TextDocument doc, ICompletionProposal completion, SortKeys sortkeys) throws Exception {
|
||||
@@ -321,8 +341,8 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionItem resolveCompletion(CompletionItem unresolved) {
|
||||
resolver.resolveNow(unresolved);
|
||||
public CompletionItem resolveCompletion(CancelChecker cancelToken, CompletionItem unresolved) {
|
||||
resolver.resolveNow(cancelToken, unresolved);
|
||||
return unresolved;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2018 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
|
||||
@@ -13,10 +13,9 @@ package org.springframework.ide.vscode.commons.languageserver.util;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CompletionHandler {
|
||||
Mono<CompletionList> handle(TextDocumentPositionParams params);
|
||||
CompletionList handle(CancelChecker cancelToken, TextDocumentPositionParams params);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2018 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
|
||||
@@ -12,8 +12,9 @@
|
||||
package org.springframework.ide.vscode.commons.languageserver.util;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CompletionResolveHandler {
|
||||
CompletionItem handle(CompletionItem unresolved) throws Exception;
|
||||
CompletionItem handle(CancelChecker cancelToken, CompletionItem unresolved) throws Exception;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.languageserver.util;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
@@ -251,13 +252,13 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE
|
||||
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams position) {
|
||||
log.info("completion request arrived: " + position.getTextDocument().getUri());
|
||||
|
||||
CompletionHandler h = completionHandler;
|
||||
if (h != null) {
|
||||
return completionHandler.handle(position)
|
||||
.map(Either::<List<CompletionItem>, CompletionList>forRight)
|
||||
.toFuture();
|
||||
}
|
||||
return CompletableFuture.completedFuture(Either.forRight(NO_COMPLETIONS));
|
||||
return CompletableFutures.computeAsync(cancelToken -> {
|
||||
CompletionHandler h = completionHandler;
|
||||
if (h != null) {
|
||||
return Either.forRight(completionHandler.handle(cancelToken, position));
|
||||
}
|
||||
return Either.forRight(NO_COMPLETIONS);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -269,8 +270,10 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE
|
||||
CompletionResolveHandler h = completionResolveHandler;
|
||||
if (h != null) {
|
||||
log.info("Completion item resolve request starting {}", unresolved.getLabel());
|
||||
return h.handle(unresolved);
|
||||
return h.handle(cancelToken, unresolved);
|
||||
}
|
||||
} catch (CancellationException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.warn("exception resolving completion item", e);
|
||||
} finally {
|
||||
|
||||
@@ -45,8 +45,6 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
public class ConcourseLanguageServerInitializer {
|
||||
|
||||
@@ -137,21 +135,23 @@ public class ConcourseLanguageServerInitializer {
|
||||
// }
|
||||
// });
|
||||
|
||||
documents.onCompletion(params -> {
|
||||
documents.onCompletion((cancelToken, params) -> {
|
||||
TextDocument doc = documents.getLatestSnapshot(params);
|
||||
if (doc != null) {
|
||||
if (LanguageId.CONCOURSE_PIPELINE.equals(doc.getLanguageId())) {
|
||||
return forPipelines.completionEngine.getCompletions(params);
|
||||
return forPipelines.completionEngine.getCompletions(cancelToken, params);
|
||||
} else if (LanguageId.CONCOURSE_TASK.equals(doc.getLanguageId())) {
|
||||
return forTasks.completionEngine.getCompletions(params);
|
||||
return forTasks.completionEngine.getCompletions(cancelToken, params);
|
||||
}
|
||||
}
|
||||
return Mono.just(new CompletionList(false, ImmutableList.of()));
|
||||
return new CompletionList(false, ImmutableList.of());
|
||||
});
|
||||
documents.onCompletionResolve(item -> {
|
||||
server.completionResolver.resolveNow(item);
|
||||
|
||||
documents.onCompletionResolve((cancelToken, item) -> {
|
||||
server.completionResolver.resolveNow(cancelToken, item);
|
||||
return item;
|
||||
});
|
||||
|
||||
documents.onHover((cancelToken, params) -> {
|
||||
log.debug("Concourse hover handler starting");
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user