Skip to content

Commit

Permalink
Indicate when content assist proposals are incomplete.
Browse files Browse the repository at this point in the history
Add a place-holder proposal that indicates when the list of content
assist proposals is incomplete.
  • Loading branch information
andrewL-avlq authored and mickaelistria committed Jan 31, 2024
1 parent d533c57 commit f110e0a
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 6 deletions.
Expand Up @@ -11,7 +11,7 @@
*******************************************************************************/
package org.eclipse.lsp4e.test.completion;

import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -45,7 +45,7 @@ public abstract class AbstractCompletionTest {
@Before
public void setUp() throws CoreException {
project = TestUtils.createProject("CompletionTest" + System.currentTimeMillis());
contentAssistProcessor = new LSContentAssistProcessor();
contentAssistProcessor = new LSContentAssistProcessor(true, false);
}

protected CompletionItem createCompletionItem(String label, CompletionItemKind kind) {
Expand Down
Expand Up @@ -42,6 +42,7 @@
import org.eclipse.lsp4e.LanguageServersRegistry.LanguageServerDefinition;
import org.eclipse.lsp4e.LanguageServiceAccessor;
import org.eclipse.lsp4e.operations.completion.LSCompletionProposal;
import org.eclipse.lsp4e.operations.completion.LSContentAssistProcessor;
import org.eclipse.lsp4e.test.utils.TestUtils;
import org.eclipse.lsp4e.tests.mock.MockLanguageServer;
import org.eclipse.lsp4e.ui.UI;
Expand Down Expand Up @@ -656,4 +657,28 @@ public void testAdditionalInformationWithDocumentation() throws Exception {
String addInfo = completionProposal.getAdditionalProposalInfo(new NullProgressMonitor()); // check no exception is sent
assertFalse(addInfo.isEmpty());
}

@Test
public void testIncompleteIndication() throws CoreException {
List<CompletionItem> items = new ArrayList<>();
items.add(createCompletionItem("FirstClass", CompletionItemKind.Class));
MockLanguageServer.INSTANCE.setCompletionList(new CompletionList(true, items));

IFile testFile = TestUtils.createUniqueTestFile(project, "");
ITextViewer viewer = TestUtils.openTextViewer(testFile);

// without incomplete indication
ICompletionProposal[] proposals = contentAssistProcessor.computeCompletionProposals(viewer, 0);
assertEquals(1, proposals.length);

// with incomplete indication
LSContentAssistProcessor incompleIndicatingProcessor = new LSContentAssistProcessor(true, true);
ICompletionProposal[] proposalsWithIncompleteProposal = incompleIndicatingProcessor.computeCompletionProposals(viewer, 0);
assertEquals(2, proposalsWithIncompleteProposal.length);

// compare both proposal lists
assertEquals("FirstClass", proposals[0].getDisplayString());
assertEquals("FirstClass", proposalsWithIncompleteProposal[0].getDisplayString());
assertEquals("➕ Continue typing for more proposals...", proposalsWithIncompleteProposal[1].getDisplayString());
}
}
Expand Up @@ -27,6 +27,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.annotation.NonNull;
Expand Down Expand Up @@ -76,6 +77,7 @@ public class LSContentAssistProcessor implements IContentAssistProcessor {
private CompletableFuture<@NonNull List<@NonNull Void>> contextInformationLanguageServersFuture;
private final Object contextTriggerCharsSemaphore = new Object();
private char[] contextTriggerChars = new char[0];
private final boolean incompleteAsCompletionItem;

// The cancellation support used to cancel previous LSP requests 'textDocument/completion' when completion is retriggered
private CancellationSupport cancellationSupport;
Expand All @@ -85,8 +87,13 @@ public LSContentAssistProcessor() {
}

public LSContentAssistProcessor(boolean errorAsCompletionItem) {
this(errorAsCompletionItem, true);
}

public LSContentAssistProcessor(boolean errorAsCompletionItem, boolean incompleteAsCompletionItem) {
this.errorAsCompletionItem = errorAsCompletionItem;
this.cancellationSupport = new CancellationSupport();
this.incompleteAsCompletionItem = incompleteAsCompletionItem;
}

private final Comparator<LSCompletionProposal> proposalComparator = new LSCompletionProposalComparator();
Expand All @@ -112,6 +119,7 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int
}

List<ICompletionProposal> proposals = Collections.synchronizedList(new ArrayList<>());
AtomicBoolean anyIncomplete = new AtomicBoolean(false);
try {
// Cancel the previous LSP requests 'textDocument/completions' and completionLanguageServersFuture
this.cancellationSupport.cancel();
Expand All @@ -123,8 +131,13 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int
this.completionLanguageServersFuture = LanguageServers.forDocument(document)
.withFilter(capabilities -> capabilities.getCompletionProvider() != null) //
.collectAll((w, ls) -> cancellationSupport.execute(ls.getTextDocumentService().completion(param)) //
.thenAccept(completion -> proposals
.addAll(toProposals(document, offset, completion, w, cancellationSupport))));
.thenAccept(completion -> {
boolean isIncomplete = completion != null && completion.isRight() ? completion.getRight().isIncomplete() : false;
proposals.addAll(toProposals(document, offset, completion, w, cancellationSupport, isIncomplete));
if (isIncomplete) {
anyIncomplete.set(true);
}
}));
cancellationSupport.execute(completionLanguageServersFuture);
this.cancellationSupport = cancellationSupport;

Expand Down Expand Up @@ -154,6 +167,12 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int
}
}
Arrays.sort(completeProposals, proposalComparator);
ICompletionProposal[] incompleteProposal = createIncompleProposal(offset, anyIncomplete.get());
if (incompleteProposal.length > 0) {
ICompletionProposal[] incompleteProposals = Arrays.copyOf(completeProposals, completeProposals.length + incompleteProposal.length, ICompletionProposal[].class);
System.arraycopy(incompleteProposal, 0, incompleteProposals, completeProposals.length, incompleteProposal.length);
return incompleteProposals;
}
return completeProposals;
}

Expand All @@ -170,6 +189,13 @@ private String createErrorMessage(int offset, Exception ex) {
return Messages.completionError + " : " + ex.getMessage(); //$NON-NLS-1$
}

private ICompletionProposal[] createIncompleProposal(int offset, boolean incomplete) {
if (incompleteAsCompletionItem && incomplete) {
return new ICompletionProposal[] {new CompletionProposal("", offset, 0, 0, null, Messages.completionIncomplete, null, Messages.continueIncomplete)}; //$NON-NLS-1$
}
return new ICompletionProposal[0];
}

private void initiateLanguageServers(@NonNull IDocument document) {
if (currentDocument != document) {
this.currentDocument = document;
Expand Down Expand Up @@ -221,15 +247,14 @@ private void initiateLanguageServers() {
}
}
private static List<ICompletionProposal> toProposals(IDocument document,
int offset, Either<List<CompletionItem>, CompletionList> completionList, LanguageServerWrapper languageServerWrapper, CancelChecker cancelChecker) {
int offset, Either<List<CompletionItem>, CompletionList> completionList, LanguageServerWrapper languageServerWrapper, CancelChecker cancelChecker, boolean isIncomplete) {
if (completionList == null) {
return Collections.emptyList();
}
//Stop the compute of ICompletionProposal if the completion has been cancelled
cancelChecker.checkCanceled();
CompletionItemDefaults defaults = completionList.map(o -> null, CompletionList::getItemDefaults);
List<CompletionItem> items = completionList.isLeft() ? completionList.getLeft() : completionList.getRight().getItems();
boolean isIncomplete = completionList.isRight() ? completionList.getRight().isIncomplete() : false;
return items.stream() //
.filter(Objects::nonNull)
.map(item -> new LSCompletionProposal(document, offset, item, defaults,
Expand Down
2 changes: 2 additions & 0 deletions org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/Messages.java
Expand Up @@ -70,6 +70,8 @@ public final class Messages extends NLS {
public static String rename_empty_message;
public static String rename_invalidated;
public static String completionError;
public static String completionIncomplete;
public static String continueIncomplete;
public static String linkWithEditor_label;
public static String linkWithEditor_description;
public static String linkWithEditor_tooltip;
Expand Down
Expand Up @@ -60,6 +60,8 @@ updateCodelensMenu_job=Update CodeLens menu
outline_computingSymbols=Computing symbols...
notImplemented=Not implemented
completionError=Error while computing completion
completionIncomplete=\u2795 Continue typing for more proposals...
continueIncomplete=This proposal list is incomplete. Continue typing to get more proposals.

rename_title=Rename
rename_label=New name:
Expand Down

0 comments on commit f110e0a

Please sign in to comment.