From 23c4550667c34d9b85ab264a7991ed44d474ef68 Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Sat, 28 Oct 2023 11:37:03 +0200 Subject: [PATCH] Improve chain completions - simplify the code by reusing CompletionProposalRequestor to create items - add support for javadoc for chain completions --- .../ChainCompletionProposalComputer.java | 215 +++++++----------- ...CompletionProposalDescriptionProvider.java | 8 + ...CompletionProposalReplacementProvider.java | 27 ++- .../CompletionProposalRequestor.java | 4 + .../internal/handlers/CompletionHandler.java | 9 +- .../handlers/CompletionResolveHandler.java | 20 +- .../handlers/CompletionHandlerChainTest.java | 88 ++++++- 7 files changed, 211 insertions(+), 160 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/ChainCompletionProposalComputer.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/ChainCompletionProposalComputer.java index 3fa9309faf..e1ba2dc720 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/ChainCompletionProposalComputer.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/ChainCompletionProposalComputer.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -29,11 +28,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.CompletionContext; import org.eclipse.jdt.core.CompletionProposal; import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; @@ -45,12 +43,8 @@ import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; -import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; import org.eclipse.jdt.core.manipulation.JavaManipulation; import org.eclipse.jdt.core.manipulation.SharedASTProviderCore; -import org.eclipse.jdt.internal.core.manipulation.StubUtility; -import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; import org.eclipse.jdt.internal.corext.dom.IASTSharedValues; import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; import org.eclipse.jdt.internal.corext.template.java.SignatureUtil; @@ -62,12 +56,6 @@ import org.eclipse.jdt.internal.ui.text.ChainFinder; import org.eclipse.jdt.internal.ui.text.ChainType; import org.eclipse.jdt.ls.core.internal.JDTUtils; -import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; -import org.eclipse.jdt.ls.core.internal.TextEditConverter; -import org.eclipse.lsp4j.CompletionItem; -import org.eclipse.lsp4j.CompletionItemKind; -import org.eclipse.lsp4j.CompletionItemLabelDetails; -import org.eclipse.lsp4j.InsertTextFormat; import org.eclipse.lsp4j.TextEdit; public class ChainCompletionProposalComputer { @@ -92,14 +80,13 @@ public ChainCompletionProposalComputer(ICompilationUnit cu, CompletionProposalRe this.snippetStringSupported = snippetStringSupported; } - public List computeCompletionProposals() { - if (!shouldPerformCompletionOnExpectedType()) { - return Collections.emptyList(); + public void computeCompletionProposals() { + if (shouldPerformCompletionOnExpectedType()) { + executeCallChainSearch(); } - return executeCallChainSearch(); } - private List executeCallChainSearch() { + private void executeCallChainSearch() { final int maxChains = Integer.parseInt(JavaManipulation.getPreference("recommenders.chain.max_chains", cu.getJavaProject())); final int minDepth = Integer.parseInt(JavaManipulation.getPreference("recommenders.chain.min_chain_length", cu.getJavaProject())); final int maxDepth = Integer.parseInt(JavaManipulation.getPreference("recommenders.chain.max_chain_length", cu.getJavaProject())); @@ -144,20 +131,19 @@ private List executeCallChainSearch() { List found = new ArrayList<>(); found.addAll(mainFinder.getChains()); found.addAll(contextFinder.getChains()); - return buildCompletionProposals(found); + buildCompletionProposals(found); } - private List buildCompletionProposals(final List chains) { - final List proposals = new LinkedList<>(); - + private void buildCompletionProposals(final List chains) { for (final Chain chain : chains) { try { - proposals.add(create(chain)); + var completionProposal = createCompletionProposal(chain); + + coll.addAdditionalProposal(completionProposal); } catch (JavaModelException e) { // ignore } } - return proposals; } private boolean findEntrypoints(List expectedTypes, IJavaProject project) { @@ -263,31 +249,77 @@ private boolean matchesExpectedPrefix(final IJavaElement element) { return String.valueOf(element.getElementName()).startsWith(prefix); } - private CompletionItem create(final Chain chain) throws JavaModelException { - final String insert = createInsertText(chain, chain.getExpectedDimensions()); - final CompletionItem ci = new CompletionItem(); + // given Collection.emptyList() return Collection.emptyList + // args[i].lines().toList() return args[i].lines().toList + private char[] getQualifiedMethodName(String name) { + var nonSnippetName = removeSnippetString(name); + int index = nonSnippetName.lastIndexOf('('); + if (index > 0) { + return nonSnippetName.substring(0, index).toCharArray(); + } + return nonSnippetName.toCharArray(); + } + + private String removeSnippetString(String name) { + return name.replaceAll("\\[\\$\\{.*\\}\\]", "[i]"); + } - ci.setTextEditText(insert); - ci.setInsertText(getQualifiedMethodName(insert)); - ci.setInsertTextFormat(snippetStringSupported ? InsertTextFormat.Snippet : InsertTextFormat.PlainText); - ci.setKind(CompletionItemKind.Method); - setLabelDetails(chain, ci); + private CompletionProposal createCompletionProposal(final Chain chain) throws JavaModelException { + final var edge = chain.getElements().get(chain.getElements().size() - 1); + final var insertText = createInsertText(chain, chain.getExpectedDimensions()); + final var root = chain.getElements().get(0); + CompletionProposal cp = null; + + switch (edge.getElementType()) { + case FIELD: { + cp = CompletionProposal.create(CompletionProposal.FIELD_REF, coll.getContext().getOffset()); + var field = ((IField) edge.getElement()); + cp.setName(getQualifiedMethodName(insertText)); + cp.setSignature(field.getTypeSignature().toCharArray()); + cp.setDeclarationSignature(Signature.createTypeSignature(field.getDeclaringType().getFullyQualifiedName(), true).toCharArray()); + cp.setCompletion(insertText.toCharArray()); + cp.setReplaceRange(coll.getContext().getOffset(), coll.getContext().getOffset() + insertText.length()); + if(coll.getContext().getToken() != null && coll.getContext().getToken().length > 0) { + cp.setTokenRange(coll.getContext().getTokenStart(), coll.getContext().getTokenEnd()); + } + break; + } + case METHOD: { + cp = CompletionProposal.create(CompletionProposal.METHOD_REF, coll.getContext().getOffset()); + var method = ((IMethod) edge.getElement()); + cp.setName(getQualifiedMethodName(insertText)); + cp.setSignature(toGenericSignature(method)); + cp.setDeclarationSignature(Signature.createTypeSignature(method.getDeclaringType().getFullyQualifiedName(), true).toCharArray()); + cp.setCompletion(insertText.toCharArray()); + cp.setReplaceRange(coll.getContext().getOffset(), coll.getContext().getOffset() + insertText.length()); + cp.setParameterNames(toCharArray(method.getParameterNames())); + if(coll.getContext().getToken() != null && coll.getContext().getToken().length > 0) { + cp.setTokenRange(coll.getContext().getTokenStart(), coll.getContext().getTokenEnd()); + } + break; + } + default: + } - ChainElement root = chain.getElements().get(0); - if (root.getElementType() == ElementType.TYPE) { - ci.setAdditionalTextEdits(addImport(((IType) root.getElement()).getFullyQualifiedName())); + if (cp != null && root.getElementType() == ElementType.TYPE) { + var type = ((IType) root.getElement()); + var importCompletion = CompletionProposal.create(CompletionProposal.TYPE_REF, 0); + importCompletion.setSignature(Signature.createTypeSignature(type.getFullyQualifiedName(), true).toCharArray()); + importCompletion.setCompletion(type.getFullyQualifiedName().toCharArray()); + cp.setRequiredProposals(new CompletionProposal[] {importCompletion}); } - return ci; + return cp; } - // given Collection.emptyList() - // return Collection.emptyList - private String getQualifiedMethodName(String name) { - int index = name.indexOf('('); - if (index > 0) { - return name.substring(0, index); + private char[] toGenericSignature(IMethod method) throws JavaModelException { + return Signature.createMethodSignature(method.getParameterTypes(), method.getReturnType()).toCharArray(); + } + private char[][] toCharArray(String[] parameterNames) { + var result = new char[parameterNames.length][]; + for (int i = 0; i < parameterNames.length; i++) { + result[i] = parameterNames[i].toCharArray(); } - return name; + return result; } private String createInsertText(final Chain chain, final int expectedDimension) throws JavaModelException { @@ -314,62 +346,6 @@ private String createInsertText(final Chain chain, final int expectedDimension) return sb.toString(); } - private void setLabelDetails(final Chain chain, final CompletionItem item) throws JavaModelException { - final CompletionItemLabelDetails details = new CompletionItemLabelDetails(); - - ChainElement last = chain.getElements().get(chain.getElements().size() - 1); - String lastDetails = ""; - switch (last.getElementType()) { - case FIELD: - case TYPE: - case LOCAL_VARIABLE: - item.setLabel(last.getElement().getElementName()); - details.setDescription(last.getReturnType().toString()); - break; - case METHOD: - final IMethod method = (IMethod) last.getElement(); - final String returnTypeSig = method.getReturnType(); - final String signatureQualifier = Signature.getSignatureQualifier(returnTypeSig); - String[] signatureComps = null; - if (signatureQualifier != null && !signatureQualifier.isBlank()) { - signatureComps = new String[2]; - signatureComps[0] = signatureQualifier; - signatureComps[1] = Signature.getSignatureSimpleName(returnTypeSig); - } else { - signatureComps = new String[1]; - signatureComps[0] = Signature.getSignatureSimpleName(returnTypeSig); - } - - details.setDescription(Signature.toQualifiedName(signatureComps)); - lastDetails = "(%s)".formatted(Stream.of(method.getParameterNames()).collect(Collectors.joining(","))); - break; - default: - } - - List receivers = chain.getElements().subList(0, chain.getElements().size() - 1); - StringBuilder receiversString = new StringBuilder(64); - receiversString.append(" - "); - for (final ChainElement edge : receivers) { - switch (edge.getElementType()) { - case FIELD: - case TYPE: - case LOCAL_VARIABLE: - appendVariableString(edge, receiversString); - break; - case METHOD: - final IMethod method = (IMethod) edge.getElement(); - receiversString.append(method.getElementName()); - receiversString.append("(%s)".formatted(Stream.of(method.getParameterNames()).collect(Collectors.joining(",")))); - break; - default: - } - receiversString.append("."); - } - details.setDetail(receiversString.append(last.getElement().getElementName()).append(lastDetails).toString()); - item.setLabelDetails(details); - item.setLabel(last.getElement().getElementName().concat(lastDetails)); - } - private static void appendVariableString(final ChainElement edge, final StringBuilder sb) { if (edge.requiresThisForQualification() && sb.length() == 0) { sb.append("this."); @@ -387,7 +363,7 @@ private void appendParameters(final StringBuilder sb, final IMethod method, fina return (index > -1) ? n.substring(0, index) : n; }).toArray(String[]::new); } - sb.append(Stream.of(parameterNames).map(n -> "${%s:%s}".formatted(counter.getAndIncrement(), n)).collect(Collectors.joining(", "))); + sb.append(Stream.of(parameterNames).collect(Collectors.joining(", "))); } sb.append(")"); } @@ -396,7 +372,7 @@ private void appendArrayDimensions(final StringBuilder sb, final int dimension, for (int i = dimension; i-- > expectedDimension;) { sb.append("["); if (appendVariables) { - sb.append("${%s:%s}".formatted(counter.getAndIncrement(), "i")); + sb.append("${").append(dimension).append(":i}"); } sb.append("]"); } @@ -432,43 +408,6 @@ private List computeContextEntrypoint(List expectedType return results; } - private List addImport(String type) { - if (additionalEdits.containsKey(type)) { - return additionalEdits.get(type); - } - - try { - boolean qualified = type.indexOf('.') != -1; - if (!qualified) { - return Collections.emptyList(); - } - - CompilationUnit root = getASTRoot(); - ImportRewrite importRewrite; - if (root == null) { - importRewrite = StubUtility.createImportRewrite(cu, true); - } else { - importRewrite = StubUtility.createImportRewrite(root, true); - } - - ImportRewriteContext context; - if (root == null) { - context = null; - } else { - context = new ContextSensitiveImportRewriteContext(root, coll.getContext().getOffset(), importRewrite); - } - - importRewrite.addImport(type, context); - List edits = this.additionalEdits.getOrDefault(type, new ArrayList<>()); - TextEditConverter converter = new TextEditConverter(cu, importRewrite.rewriteImports(new NullProgressMonitor())); - edits.addAll(converter.convert()); - this.additionalEdits.put(type, edits); - return edits; - } catch (CoreException e) { - JavaLanguageServerPlugin.log(e); - return Collections.emptyList(); - } - } // The following needs to move to jdt ui core manipulation public static List resolveBindingsForExpectedTypes(final IJavaProject proj, final CompletionContext ctx) { diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalDescriptionProvider.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalDescriptionProvider.java index 495fe2a6da..5da26233c1 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalDescriptionProvider.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalDescriptionProvider.java @@ -22,6 +22,7 @@ import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.codeassist.CompletionEngine; import org.eclipse.jdt.internal.corext.template.java.SignatureUtil; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; @@ -372,6 +373,13 @@ private void createMethodProposalLabel(CompletionProposal methodProposal, Comple } catch (Exception e) { JavaLanguageServerPlugin.logException(e.getMessage(), e); } + } else { + // if this is a chain completion proposal, we need to set the filter text to the method name instead of the full completion text. + var chainName = methodProposal.getName(); + var index = CharOperation.lastIndexOf('.', chainName); + if(index > -1) { + item.setFilterText(String.valueOf(CharOperation.subarray(chainName, index + 1, chainName.length))); + } } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalReplacementProvider.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalReplacementProvider.java index 30e673b5b7..2c04e1730c 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalReplacementProvider.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalReplacementProvider.java @@ -209,7 +209,6 @@ private String getTextEditText(CompletionProposal proposal, CompletionItem item, if (!completionBuffer.isEmpty()) { return completionBuffer.toString(); } - String defaultText = getDefaultTextEditText(item); int start = proposal.getReplaceStart(); int end = proposal.getReplaceEnd(); @@ -614,7 +613,18 @@ private void appendMethodNameReplacement(StringBuilder buffer, CompletionProposa if (proposal.getKind() != CompletionProposal.CONSTRUCTOR_INVOCATION) { String str = new String(proposal.getName()); if (client.isCompletionSnippetsSupported()) { - str = CompletionUtils.sanitizeCompletion(str); + var coreCompletion = new String(proposal.getCompletion()); + if(coreCompletion.contains(".")) { + // add support for chain completion's chain arguments such as array index's or method arguments + int lparenIndex = coreCompletion.lastIndexOf('('); + if(lparenIndex > -1) { + str = coreCompletion.substring(0, lparenIndex); + } else { + str = coreCompletion; + } + } else { + str = CompletionUtils.sanitizeCompletion(str); + } } buffer.append(str); } @@ -656,6 +666,7 @@ private void appendGuessingCompletion(StringBuilder buffer, CompletionProposal p JavaLanguageServerPlugin.logException(e.getMessage(), e); } } + final var placeholderOffset = placeholderOffset(buffer); for (int i= 0; i < count; i++) { if (i != 0) { buffer.append(COMMA); @@ -673,13 +684,23 @@ private void appendGuessingCompletion(StringBuilder buffer, CompletionProposal p argument = replace.toCharArray(); } buffer.append("${"); - buffer.append(Integer.toString(i+1)); + buffer.append(Integer.toString(i + placeholderOffset)); buffer.append(":"); buffer.append(argument); buffer.append("}"); } } + private int placeholderOffset(StringBuilder buffer) { + int count = 1; // start with offset 1 + int index = 0; + while ((index = buffer.indexOf("${", index)) != -1) { + count++; + index++; + } + return count; + } + private String[] guessParameters(char[][] parameterNames, CompletionProposal proposal) throws JavaModelException { int count = parameterNames.length; String[] result = new String[count]; diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalRequestor.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalRequestor.java index 6ea6bb1053..67db051d03 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalRequestor.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/CompletionProposalRequestor.java @@ -592,6 +592,10 @@ public List getProposals() { return proposals; } + public void addAdditionalProposal(CompletionProposal proposals) { + this.proposals.add(proposals); + } + /** * copied from * org.eclipse.jdt.ui.text.java.CompletionProposalCollector.isFiltered(CompletionProposal) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandler.java index 9cdcc08b09..2610ec8abc 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandler.java @@ -288,15 +288,16 @@ public boolean isCanceled() { } else { ModelBasedCompletionEngine.codeComplete(unit, offset, collector, DefaultWorkingCopyOwner.PRIMARY, subMonitor); } + // chain completions are added into collector while computing, so we need me compute before adding completion items to proposals. + if (manager.getPreferences().isChainCompletionEnabled() && params.getContext().getTriggerKind() != CompletionTriggerKind.TriggerCharacter) { + ChainCompletionProposalComputer chain = new ChainCompletionProposalComputer(unit, collector, this.isSnippetStringSupported()); + chain.computeCompletionProposals(); + } proposals.addAll(collector.getCompletionItems()); if (isSnippetStringSupported() && !UNSUPPORTED_RESOURCES.contains(unit.getResource().getName())) { proposals.addAll(SnippetCompletionProposal.getSnippets(unit, collector, subMonitor)); } proposals.addAll(new JavadocCompletionProposal().getProposals(unit, offset, collector, subMonitor)); - if (manager.getPreferences().isChainCompletionEnabled() && params.getContext().getTriggerKind() != CompletionTriggerKind.TriggerCharacter) { - ChainCompletionProposalComputer chain = new ChainCompletionProposalComputer(unit, collector, this.isSnippetStringSupported()); - proposals.addAll(chain.computeCompletionProposals()); - } } catch (OperationCanceledException e) { monitor.setCanceled(true); } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionResolveHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionResolveHandler.java index 1a07e4ae14..966f97e0f0 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionResolveHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionResolveHandler.java @@ -128,7 +128,7 @@ public CompletionItem resolve(CompletionItem param, IProgressMonitor monitor) { if (manager.getClientPreferences().isCompletionResolveDocumentSupport()) { param.setDocumentation(SnippetUtils.beautifyDocument(content)); } - + if (manager.getPreferences().isCompletionLazyResolveTextEditEnabled()) { SnippetCompletionProposal.setTextEdit(ctx, unit, param, content); } @@ -156,7 +156,7 @@ public CompletionItem resolve(CompletionItem param, IProgressMonitor monitor) { PostfixTemplateEngine.setAdditionalTextEdit(param, unit, postfixContext, range, template); } } - + param.setData(null); } catch (JavaModelException e) { JavaLanguageServerPlugin.logException(e.getMessage(), e); @@ -211,6 +211,7 @@ public CompletionItem resolve(CompletionItem param, IProgressMonitor monitor) { try { IType type = unit.getJavaProject().findType(typeName); if (type != null && proposal.getName() != null) { + String name = String.valueOf(proposal.getName()); String[] paramSigs = CharOperation.NO_STRINGS; if (proposal instanceof InternalCompletionProposal internalProposal) { Binding binding = internalProposal.getBinding(); @@ -227,9 +228,22 @@ public CompletionItem resolve(CompletionItem param, IProgressMonitor monitor) { parameters[i] = getLowerBound(parameters[i]); } paramSigs = parameters; + } else if(proposal.getKind() == CompletionProposal.METHOD_REF || + proposal.getKind() == CompletionProposal.FIELD_REF) { + // this is mainly for chain completion handling + int dotIndex = name.lastIndexOf('.'); + if(dotIndex > -1) { + name = name.substring(dotIndex + 1); + if(proposal.getKind() == CompletionProposal.METHOD_REF) { + String[] parameters = Signature.getParameterTypes(String.valueOf(fix83600(proposal.getSignature()))); + for (int i = 0; i < parameters.length; i++) { + parameters[i] = getLowerBound(parameters[i]); + } + paramSigs = parameters; + } + } } } - String name = String.valueOf(proposal.getName()); IMethod method = type.getMethod(name, paramSigs); IMethod[] methods = type.findMethods(method); if (methods != null && methods.length > 0) { diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerChainTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerChainTest.java index af07552fc3..fda4e29d52 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerChainTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerChainTest.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; import java.util.List; @@ -109,15 +110,14 @@ public static void main(String[] args) { CompletionItem completionItem = completionItems.get(0); assertNotNull(completionItem); - assertEquals("Completion getTextEditText", "Collectors.toList()", completionItem.getTextEditText()); - assertNotNull(completionItem.getLabelDetails()); - assertEquals("Completion detail.description", "java.util.stream.Collector>", completionItem.getLabelDetails().getDescription()); - assertEquals("Completion detail.detail", " - Collectors.toList()", completionItem.getLabelDetails().getDetail()); + assertEquals("Completion getTextEditText", "Collectors.toList()", completionItem.getTextEdit().getLeft().getNewText()); + assertNotNull(completionItem.getLabel()); + assertEquals("Completion Label", "Collectors.toList() : Collector>", completionItem.getLabel()); + assertEquals("Completion Details", "java.util.stream.Collectors.Collectors.toList() : Collector>", completionItem.getDetail()); assertNotNull(completionItem.getAdditionalTextEdits()); - assertEquals("Additional edits count", 1, completionItem.getAdditionalTextEdits().size()); - assertNotNull(completionItem.getAdditionalTextEdits().get(0)); - assertEquals("Import", "import java.util.stream.Collectors;\n", completionItem.getAdditionalTextEdits().get(0).getNewText()); - assertEquals("Completion Label", "toList()", completionItem.getLabel()); + assertEquals("Additional edits count", 2, completionItem.getAdditionalTextEdits().size()); + assertNotNull(completionItem.getAdditionalTextEdits().get(1)); + assertEquals("Import", "import java.util.stream.Collectors;\n", completionItem.getAdditionalTextEdits().get(1).getNewText()); } @Test @@ -140,12 +140,12 @@ public static void main(String[] args) { CompletionItem completionItem = completionItems.get(0); assertNotNull(completionItem); - assertEquals("Completion getTextEditText", "Collections.emptyList()", completionItem.getTextEditText()); + assertEquals("Completion getTextEditText", "Collections.emptyList()", completionItem.getTextEdit().getLeft().getNewText()); assertNotNull(completionItem.getAdditionalTextEdits()); - assertEquals("Additional edits count", 1, completionItem.getAdditionalTextEdits().size()); - assertNotNull(completionItem.getAdditionalTextEdits().get(0)); - assertEquals("Import", "import java.util.Collections;\n", completionItem.getAdditionalTextEdits().get(0).getNewText()); + assertEquals("Additional edits count", 2, completionItem.getAdditionalTextEdits().size()); + assertNotNull(completionItem.getAdditionalTextEdits().get(1)); + assertEquals("Import", "import java.util.Collections;\n", completionItem.getAdditionalTextEdits().get(1).getNewText()); } @Test @@ -186,6 +186,70 @@ public static void main(String[] args) { assertEquals("emptyList completion count", 0, completionItems.size()); } + @Test + public void testChainCompletionsOnChainsFromVisibleVariables() throws Exception { + //@formatter:off + ICompilationUnit unit = getWorkingCopy( + "src/java/Foo.java", + """ + import java.util.List; + public class Foo { + public class Stream { + public List toList() { + return null; + } + } + + public static void main(String[] args) { + Stream stream = new Stream(); + Stream[] streams = new Stream[0]; + List names = + } + } + """); + //@formatter:on + CompletionList list = requestCompletions(unit, "names ="); + var item = list.getItems().stream().filter(i -> i.getLabel().startsWith("stream.")).findFirst(); + assertTrue("completion", item.isPresent()); + assertEquals("completion label", "stream.toList() : List", item.get().getLabel()); + assertEquals("completion edit text", "stream.toList()", item.get().getTextEdit().getLeft().getNewText()); + + item = list.getItems().stream().filter(i -> i.getLabel().startsWith("streams[i].")).findFirst(); + assertTrue("array completion", item.isPresent()); + assertEquals("array completion label", "streams[i].toList() : List", item.get().getLabel()); + assertEquals("array completion edit text", "streams[${1:i}].toList()", item.get().getTextEdit().getLeft().getNewText()); + + } + + @Test + public void testChainCompletionsOnChainsCorrectSnippetPlaceholders() throws Exception { + //@formatter:off + ICompilationUnit unit = getWorkingCopy( + "src/java/Foo.java", + """ + import java.util.List; + public class Foo { + public class Stream { + public List toList(int size) { + return null; + } + } + + public static void main(String[] args) { + Stream stream = new Stream(); + Stream[] streams = new Stream[0]; + List names = + } + } + """); + //@formatter:on + CompletionList list = requestCompletions(unit, "names ="); + var item = list.getItems().stream().filter(i -> i.getLabel().startsWith("streams[i].")).findFirst(); + assertTrue("completion", item.isPresent()); + assertEquals("completion label", "streams[i].toList(int size) : List", item.get().getLabel()); + assertEquals("completion edit text", "streams[${1:i}].toList(${2:size})", item.get().getTextEdit().getLeft().getNewText()); + } + @Test public void testChainCompletionsOnPrimitiveVariableExpectNoCompletions() throws Exception { //@formatter:off