From f110e0a455100f969a5fc6107780d08def26ea26 Mon Sep 17 00:00:00 2001 From: Andrew Lamb <38555688+andrewL-avlq@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:28:29 +0000 Subject: [PATCH] Indicate when content assist proposals are incomplete. Add a place-holder proposal that indicates when the list of content assist proposals is incomplete. --- .../completion/AbstractCompletionTest.java | 4 +-- .../completion/IncompleteCompletionTest.java | 25 ++++++++++++++ .../completion/LSContentAssistProcessor.java | 33 ++++++++++++++++--- .../src/org/eclipse/lsp4e/ui/Messages.java | 2 ++ .../org/eclipse/lsp4e/ui/messages.properties | 2 ++ 5 files changed, 60 insertions(+), 6 deletions(-) diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/AbstractCompletionTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/AbstractCompletionTest.java index 10bdfbe9c..ee54d8e1e 100644 --- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/AbstractCompletionTest.java +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/AbstractCompletionTest.java @@ -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; @@ -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) { diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/IncompleteCompletionTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/IncompleteCompletionTest.java index 49f507164..8916d595f 100644 --- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/IncompleteCompletionTest.java +++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/completion/IncompleteCompletionTest.java @@ -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; @@ -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 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()); + } } diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSContentAssistProcessor.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSContentAssistProcessor.java index 0d8cbd2fe..a59482ba1 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSContentAssistProcessor.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/completion/LSContentAssistProcessor.java @@ -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; @@ -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; @@ -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 proposalComparator = new LSCompletionProposalComparator(); @@ -112,6 +119,7 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int } List proposals = Collections.synchronizedList(new ArrayList<>()); + AtomicBoolean anyIncomplete = new AtomicBoolean(false); try { // Cancel the previous LSP requests 'textDocument/completions' and completionLanguageServersFuture this.cancellationSupport.cancel(); @@ -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; @@ -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; } @@ -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; @@ -221,7 +247,7 @@ private void initiateLanguageServers() { } } private static List toProposals(IDocument document, - int offset, Either, CompletionList> completionList, LanguageServerWrapper languageServerWrapper, CancelChecker cancelChecker) { + int offset, Either, CompletionList> completionList, LanguageServerWrapper languageServerWrapper, CancelChecker cancelChecker, boolean isIncomplete) { if (completionList == null) { return Collections.emptyList(); } @@ -229,7 +255,6 @@ private static List toProposals(IDocument document, cancelChecker.checkCanceled(); CompletionItemDefaults defaults = completionList.map(o -> null, CompletionList::getItemDefaults); List 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, diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/Messages.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/Messages.java index 6453dd75f..74d707766 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/Messages.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/Messages.java @@ -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; diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/messages.properties b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/messages.properties index f967dc352..aea562e3c 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/messages.properties +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/messages.properties @@ -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: