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 5e7c46b7f0..4a9ffafaa6 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 @@ -13,6 +13,7 @@ package org.eclipse.jdt.ls.core.internal.contentassist; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,6 +44,7 @@ import org.eclipse.jdt.ls.core.internal.handlers.CompletionResolveHandler; import org.eclipse.jdt.ls.core.internal.handlers.CompletionResponse; import org.eclipse.jdt.ls.core.internal.handlers.CompletionResponses; +import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager; import org.eclipse.jface.text.Region; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItemKind; @@ -57,6 +59,54 @@ public final class CompletionProposalRequestor extends CompletionRequestor { private CompletionResponse response; private boolean fIsTestCodeExcluded; private CompletionContext context; + private boolean isComplete = true; + private PreferenceManager preferenceManager; + + static class ProposalComparator implements Comparator { + + private Map completionCache; + + ProposalComparator(int cacheSize) { + completionCache = new HashMap<>(cacheSize + 1, 1f);//avoid resizing the cache + } + + @Override + public int compare(CompletionProposal p1, CompletionProposal p2) { + int res = p2.getRelevance() - p1.getRelevance(); + if (res == 0) { + res = p1.getKind() - p2.getKind(); + } + if (res == 0) { + char[] completion1 = getCompletion(p1); + char[] completion2 = getCompletion(p2); + + int p1Length = completion1.length; + int p2Length = completion2.length; + for (int i = 0; i < p1Length; i++) { + if (i >= p2Length) { + return -1; + } + res = Character.compare(completion1[i], completion2[i]); + if (res != 0) { + return res; + } + } + res = p2Length - p1Length; + } + return res; + } + + private char[] getCompletion(CompletionProposal cp) { + // Implementation of CompletionProposal#getCompletion() can be non-trivial, + // so we cache the results to speed things up + return completionCache.computeIfAbsent(cp, p -> p.getCompletion()); + } + + }; + + public boolean isComplete() { + return isComplete; + } // Update SUPPORTED_KINDS when mapKind changes // @formatter:off @@ -75,8 +125,9 @@ public final class CompletionProposalRequestor extends CompletionRequestor { CompletionItemKind.Text); // @formatter:on - public CompletionProposalRequestor(ICompilationUnit aUnit, int offset) { + public CompletionProposalRequestor(ICompilationUnit aUnit, int offset, PreferenceManager preferenceManager) { this.unit = aUnit; + this.preferenceManager = preferenceManager; response = new CompletionResponse(); response.setOffset(offset); fIsTestCodeExcluded = !isTestSource(unit.getJavaProject(), unit); @@ -124,11 +175,27 @@ public void accept(CompletionProposal proposal) { } public List getCompletionItems() { - response.setProposals(proposals); - CompletionResponses.store(response); + //Sort the results by relevance 1st + proposals.sort(new ProposalComparator(proposals.size())); List completionItems = new ArrayList<>(proposals.size()); - for (int i = 0; i < proposals.size(); i++) { - completionItems.add(toCompletionItem(proposals.get(i), i)); + int maxCompletions = preferenceManager.getPreferences().getMaxCompletionResults(); + int limit = Math.min(proposals.size(), maxCompletions); + if (proposals.size() > maxCompletions) { + //we keep receiving completions past our capacity so that makes the whole result incomplete + isComplete = false; + response.setProposals(proposals.subList(0, limit)); + } else { + response.setProposals(proposals); + } + CompletionResponses.store(response); + + //Let's compute replacement texts for the most relevant results only + CompletionProposalReplacementProvider proposalProvider = new CompletionProposalReplacementProvider(unit, getContext(), response.getOffset(), preferenceManager.getClientPreferences()); + for (int i = 0; i < limit; i++) { + CompletionProposal proposal = proposals.get(i); + CompletionItem item = toCompletionItem(proposal, i); + proposalProvider.updateReplacement(proposal, item, '\0'); + completionItems.add(item); } return completionItems; } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SortTextHelper.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SortTextHelper.java index 2f7ea89644..e435707477 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SortTextHelper.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/contentassist/SortTextHelper.java @@ -21,7 +21,7 @@ * */ public final class SortTextHelper { - private static final int CEILING = 999_999_999; + public static final int CEILING = 999_999_999; public static final int MAX_RELEVANCE_VALUE = 99_999_999; 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 9d7880f7cc..24a5ef52fa 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 @@ -15,11 +15,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Set; -import com.google.common.collect.Sets; - +import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.ProgressMonitorWrapper; @@ -32,6 +32,7 @@ import org.eclipse.jdt.ls.core.internal.contentassist.CompletionProposalRequestor; import org.eclipse.jdt.ls.core.internal.contentassist.JavadocCompletionProposal; import org.eclipse.jdt.ls.core.internal.contentassist.SnippetCompletionProposal; +import org.eclipse.jdt.ls.core.internal.contentassist.SortTextHelper; import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionList; @@ -39,17 +40,40 @@ import org.eclipse.lsp4j.CompletionParams; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import com.google.common.collect.Sets; + public class CompletionHandler{ public final static CompletionOptions DEFAULT_COMPLETION_OPTIONS = new CompletionOptions(Boolean.TRUE, Arrays.asList(".", "@", "#", "*")); private static final Set UNSUPPORTED_RESOURCES = Sets.newHashSet("module-info.java", "package-info.java"); + static final Comparator PROPOSAL_COMPARATOR = new Comparator() { + + private final String DEFAULT_SORT_TEXT = String.valueOf(SortTextHelper.MAX_RELEVANCE_VALUE); + + @Override + public int compare(CompletionItem o1, CompletionItem o2) { + return getSortText(o1).compareTo(getSortText(o2)); + } + + private String getSortText(CompletionItem ci) { + return StringUtils.defaultString(ci.getSortText(), DEFAULT_SORT_TEXT); + } + + }; + + private PreferenceManager manager; + + public CompletionHandler(PreferenceManager manager) { + this.manager = manager; + } + Either, CompletionList> completion(CompletionParams position, IProgressMonitor monitor) { - List completionItems = null; + CompletionList $ = null; try { ICompilationUnit unit = JDTUtils.resolveCompilationUnit(position.getTextDocument().getUri()); - completionItems = this.computeContentAssist(unit, + $ = this.computeContentAssist(unit, position.getPosition().getLine(), position.getPosition().getCharacter(), monitor); } catch (OperationCanceledException ignorable) { @@ -59,27 +83,30 @@ Either, CompletionList> completion(CompletionParams positio JavaLanguageServerPlugin.logException("Problem with codeComplete for " + position.getTextDocument().getUri(), e); monitor.setCanceled(true); } - CompletionList $ = new CompletionList(); + if ($ == null) { + $ = new CompletionList(); + } + if ($.getItems() == null) { + $.setItems(Collections.emptyList()); + } if (monitor.isCanceled()) { $.setIsIncomplete(true); - completionItems = null; JavaLanguageServerPlugin.logInfo("Completion request cancelled"); } else { JavaLanguageServerPlugin.logInfo("Completion request completed"); } - $.setItems(completionItems == null ? Collections.emptyList() : completionItems); return Either.forRight($); } - private List computeContentAssist(ICompilationUnit unit, int line, int column, IProgressMonitor monitor) throws JavaModelException { + private CompletionList computeContentAssist(ICompilationUnit unit, int line, int column, IProgressMonitor monitor) throws JavaModelException { CompletionResponses.clear(); if (unit == null) { - return Collections.emptyList(); + return null; } List proposals = new ArrayList<>(); final int offset = JsonRpcHelpers.toOffset(unit.getBuffer(), line, column); - CompletionProposalRequestor collector = new CompletionProposalRequestor(unit, offset); + CompletionProposalRequestor collector = new CompletionProposalRequestor(unit, offset, manager); // Allow completions for unresolved types - since 3.3 collector.setAllowsRequiredProposals(CompletionProposal.FIELD_REF, CompletionProposal.TYPE_REF, true); collector.setAllowsRequiredProposals(CompletionProposal.FIELD_REF, CompletionProposal.TYPE_IMPORT, true); @@ -95,7 +122,6 @@ private List computeContentAssist(ICompilationUnit unit, int lin collector.setAllowsRequiredProposals(CompletionProposal.ANONYMOUS_CLASS_DECLARATION, CompletionProposal.TYPE_REF, true); collector.setAllowsRequiredProposals(CompletionProposal.TYPE_REF, CompletionProposal.TYPE_REF, true); - collector.setFavoriteReferences(getFavoriteStaticMembers()); if (offset >-1 && !monitor.isCanceled()) { @@ -128,7 +154,10 @@ public boolean isCanceled() { } } } - return proposals; + proposals.sort(PROPOSAL_COMPARATOR); + CompletionList list = new CompletionList(proposals); + list.setIsIncomplete(!collector.isComplete()); + return list; } private String[] getFavoriteStaticMembers() { 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 ba9076e5e1..ddd8509d5b 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 @@ -32,7 +32,6 @@ import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.JSONUtility; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; -import org.eclipse.jdt.ls.core.internal.contentassist.CompletionProposalReplacementProvider; import org.eclipse.jdt.ls.core.internal.contentassist.CompletionProposalRequestor; import org.eclipse.jdt.ls.core.internal.javadoc.JavadocContentAccess; import org.eclipse.jdt.ls.core.internal.javadoc.JavadocContentAccess2; @@ -94,11 +93,6 @@ public CompletionItem resolve(CompletionItem param, IProgressMonitor monitor) { if (unit == null) { throw new IllegalStateException(NLS.bind("Unable to match Compilation Unit from {0} ", uri)); } - CompletionProposalReplacementProvider proposalProvider = new CompletionProposalReplacementProvider(unit, - completionResponse.getContext(), - completionResponse.getOffset(), - this.manager.getClientPreferences()); - proposalProvider.updateReplacement(completionResponse.getProposals().get(proposalId), param, '\0'); if (monitor.isCanceled()) { param.setData(null); return param; diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java index 9ffaacf46e..f63db46f83 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java @@ -472,7 +472,7 @@ public CompletableFuture executeCommand(ExecuteCommandParams params) { @Override public CompletableFuture, CompletionList>> completion(CompletionParams position) { logInfo(">> document/completion"); - CompletionHandler handler = new CompletionHandler(); + CompletionHandler handler = new CompletionHandler(preferenceManager); final IProgressMonitor[] monitors = new IProgressMonitor[1]; CompletableFuture, CompletionList>> result = computeAsync((monitor) -> { monitors[0] = monitor; diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java index ff1283216f..9aa357c0e3 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java @@ -192,6 +192,13 @@ public class Preferences { public static final String JAVA_COMPLETION_FAVORITE_MEMBERS_KEY = "java.completion.favoriteStaticMembers"; public static final List JAVA_COMPLETION_FAVORITE_MEMBERS_DEFAULT; + /** + * Preference key for maximum number of completion results to be returned. + * Defaults to 50. + */ + public static final String JAVA_COMPLETION_MAX_RESULTS_KEY = "java.completion.maxResults"; + public static final int JAVA_COMPLETION_MAX_RESULTS_DEFAULT = 50; + /** * A named preference that controls if the Java code assist only inserts * completions. When set to true, code completion overwrites the current text. @@ -361,8 +368,8 @@ public class Preferences { private String formatterProfileName; private Collection rootPaths; private Collection triggerFiles; - private int parallelBuildsCount; + private int maxCompletionResults; static { JAVA_IMPORT_EXCLUSIONS_DEFAULT = new LinkedList<>(); @@ -473,6 +480,7 @@ public Preferences() { importOrder = JAVA_IMPORT_ORDER_DEFAULT; filteredTypes = JAVA_COMPLETION_FILTERED_TYPES_DEFAULT; parallelBuildsCount = PreferenceInitializer.PREF_MAX_CONCURRENT_BUILDS_DEFAULT; + maxCompletionResults = JAVA_COMPLETION_MAX_RESULTS_DEFAULT; } /** @@ -612,6 +620,9 @@ public static Preferences createFrom(Map configuration) { maxConcurrentBuilds = maxConcurrentBuilds >= 1 ? maxConcurrentBuilds : 1; prefs.setMaxBuildCount(maxConcurrentBuilds); + int maxCompletions = getInt(configuration, JAVA_COMPLETION_MAX_RESULTS_KEY, JAVA_COMPLETION_MAX_RESULTS_DEFAULT); + prefs.setMaxCompletionResults(maxCompletions); + return prefs; } @@ -1057,7 +1068,29 @@ public boolean isJavaFormatOnTypeEnabled() { return javaFormatOnTypeEnabled; } - public void setJavaFormatOnTypeEnabled(boolean javaFormatOnTypeEnabled) { + public Preferences setJavaFormatOnTypeEnabled(boolean javaFormatOnTypeEnabled) { this.javaFormatOnTypeEnabled = javaFormatOnTypeEnabled; + return this; + } + + public int getMaxCompletionResults() { + return maxCompletionResults; + } + + /** + * Sets the maximum number of completion results (excluding snippets and Javadoc + * proposals). If maxCompletions is set to 0 or lower, then the completion limit + * is considered disabled, which could certainly severly impact performance in a + * negative way. + * + * @param maxCompletions + */ + public Preferences setMaxCompletionResults(int maxCompletions) { + if (maxCompletions < 1) { + this.maxCompletionResults = Integer.MAX_VALUE; + } else { + this.maxCompletionResults = maxCompletions; + } + return this; } } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java index dd11055561..41db67fdb6 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CompletionHandlerTest.java @@ -12,7 +12,6 @@ *******************************************************************************/ package org.eclipse.jdt.ls.core.internal.handlers; - import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.eclipse.jdt.ls.core.internal.Lsp4jAssertions.assertPosition; import static org.eclipse.jdt.ls.core.internal.Lsp4jAssertions.assertTextEdit; @@ -51,6 +50,7 @@ import org.eclipse.jdt.ls.core.internal.contentassist.JavadocCompletionProposal; import org.eclipse.jdt.ls.core.internal.preferences.ClientPreferences; import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager; +import org.eclipse.jdt.ls.core.internal.preferences.Preferences; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItemKind; import org.eclipse.lsp4j.CompletionList; @@ -230,8 +230,8 @@ public void testCompletion_object() throws Exception{ assertTrue(isNotBlank(item.getLabel())); assertNotNull(item.getKind() ); assertTrue(isNotBlank(item.getSortText())); - //text edits are not set during calls to "completion" - assertNull(item.getTextEdit()); + //text edits are set during calls to "completion" + assertNotNull(item.getTextEdit()); assertTrue(isNotBlank(item.getInsertText())); assertNotNull(item.getFilterText()); assertFalse(item.getFilterText().contains(" ")); @@ -331,11 +331,7 @@ public void testCompletion_import_package() throws JavaModelException{ assertEquals("(package) java.sql", item.getDetail()); assertEquals(CompletionItemKind.Module, item.getKind() ); assertEquals("999999215", item.getSortText()); - assertNull(item.getTextEdit()); - - - CompletionItem resolvedItem = server.resolveCompletionItem(item).join(); - assertNotNull(resolvedItem); + assertNotNull(item.getTextEdit()); TextEdit te = item.getTextEdit(); assertNotNull(te); assertEquals("java.sql.*;",te.getNewText()); @@ -480,39 +476,41 @@ public void testCompletion_import_static() throws JavaModelException{ assertNotNull(list); assertEquals(9, list.getItems().size()); - //// .SECONDS - enum value - CompletionItem secondsFieldItem = list.getItems().get(0); + //// .DAYS - enum value + CompletionItem daysFieldItem = list.getItems().get(0); // Check completion item - assertEquals("SECONDS", secondsFieldItem.getInsertText()); - assertEquals("SECONDS : TimeUnit", secondsFieldItem.getLabel()); - assertEquals(CompletionItemKind.EnumMember, secondsFieldItem.getKind()); - assertEquals("999999210", secondsFieldItem.getSortText()); - assertNull(secondsFieldItem.getTextEdit()); - - assertNotNull(server.resolveCompletionItem(secondsFieldItem).join()); - TextEdit teSeconds = secondsFieldItem.getTextEdit(); - assertNotNull(teSeconds); - assertEquals("SECONDS;", teSeconds.getNewText()); - assertNotNull(teSeconds.getRange()); - Range secondsRange = teSeconds.getRange(); + assertEquals("DAYS", daysFieldItem.getInsertText()); + assertEquals("DAYS : TimeUnit", daysFieldItem.getLabel()); + assertEquals(CompletionItemKind.EnumMember, daysFieldItem.getKind()); + assertEquals("999999210", daysFieldItem.getSortText()); + + TextEdit teDays = daysFieldItem.getTextEdit(); + assertNotNull(teDays); + assertEquals("DAYS;", teDays.getNewText()); + assertNotNull(teDays.getRange()); + Range secondsRange = teDays.getRange(); assertEquals(0, secondsRange.getStart().getLine()); assertEquals(44, secondsRange.getStart().getCharacter()); assertEquals(0, secondsRange.getEnd().getLine()); + //Check other fields are listed alphabetically + assertEquals("HOURS;", list.getItems().get(1).getTextEdit().getNewText()); + assertEquals("MICROSECONDS;", list.getItems().get(2).getTextEdit().getNewText()); + assertEquals("MILLISECONDS;", list.getItems().get(3).getTextEdit().getNewText()); + assertEquals("MINUTES;", list.getItems().get(4).getTextEdit().getNewText()); + assertEquals("NANOSECONDS;", list.getItems().get(5).getTextEdit().getNewText()); + assertEquals("SECONDS;", list.getItems().get(6).getTextEdit().getNewText()); + //// .values() - static method CompletionItem valuesMethodItem = list.getItems().get(7); // Check completion item - assertEquals("values", valuesMethodItem.getInsertText()); - assertEquals("values() : TimeUnit[]", valuesMethodItem.getLabel()); + assertEquals("valueOf", valuesMethodItem.getInsertText()); + assertEquals("valueOf(String) : TimeUnit", valuesMethodItem.getLabel()); assertEquals(CompletionItemKind.Module, valuesMethodItem.getKind()); assertEquals("999999211", valuesMethodItem.getSortText()); - assertNull(valuesMethodItem.getTextEdit()); - - - assertNotNull(server.resolveCompletionItem(valuesMethodItem).join()); TextEdit teValues = valuesMethodItem.getTextEdit(); assertNotNull(teValues); - assertEquals("values;", teValues.getNewText()); + assertEquals("valueOf;", teValues.getNewText()); assertNotNull(teValues.getRange()); Range valuesRange = teValues.getRange(); assertEquals(0, valuesRange.getStart().getLine()); @@ -548,13 +546,11 @@ public void testCompletion_method_withLSPV2() throws JavaModelException{ assertEquals("put", ci.getInsertText()); assertEquals(CompletionItemKind.Method, ci.getKind()); assertEquals("999999019", ci.getSortText()); - assertNull(ci.getTextEdit()); - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); - assertTextEdit(5, 4, 6, "put", resolvedItem.getTextEdit()); - assertNotNull(resolvedItem.getAdditionalTextEdits()); - List edits = resolvedItem.getAdditionalTextEdits(); + assertNotNull(ci.getTextEdit()); + assertTextEdit(5, 4, 6, "put", ci.getTextEdit()); + assertNotNull(ci.getAdditionalTextEdits()); + List edits = ci.getAdditionalTextEdits(); assertEquals(2, edits.size()); } @@ -584,18 +580,15 @@ public void testCompletion_method_withLSPV3() throws JavaModelException{ assertTrue(ci.getDetail().matches("java.util.HashMap.put\\(String \\w+, String \\w+\\) : String")); assertEquals(CompletionItemKind.Method, ci.getKind()); assertEquals("999999019", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); try { - assertTextEdit(5, 4, 6, "put(${1:key}, ${2:value})", resolvedItem.getTextEdit()); + assertTextEdit(5, 4, 6, "put(${1:key}, ${2:value})", ci.getTextEdit()); } catch (ComparisonFailure e) { //In case the JDK has no sources - assertTextEdit(5, 4, 6, "put(${1:arg0}, ${2:arg1})", resolvedItem.getTextEdit()); + assertTextEdit(5, 4, 6, "put(${1:arg0}, ${2:arg1})", ci.getTextEdit()); } - assertNotNull(resolvedItem.getAdditionalTextEdits()); - List edits = resolvedItem.getAdditionalTextEdits(); + assertNotNull(ci.getAdditionalTextEdits()); + List edits = ci.getAdditionalTextEdits(); assertEquals(2, edits.size()); } @@ -634,11 +627,8 @@ private void testCompletion_method_guessMethodArguments(boolean guessMethodArgum assertEquals("test", ci.getInsertText()); assertEquals(CompletionItemKind.Method, ci.getKind()); assertEquals("999999163", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); - assertTextEdit(5, 2, 5, expected, resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); + assertTextEdit(5, 2, 5, expected, ci.getTextEdit()); } finally { JavaLanguageServerPlugin.getPreferencesManager().getPreferences().setGuessMethodArguments(oldGuessMethodArguments); } @@ -669,11 +659,8 @@ public void testCompletion_method_guessMethodArguments2() throws JavaModelExcept assertEquals("test", ci.getInsertText()); assertEquals(CompletionItemKind.Method, ci.getKind()); assertEquals("999999163", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); - assertTextEdit(4, 2, 5, "test(${1:str}, ${2:0});", resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); + assertTextEdit(4, 2, 5, "test(${1:str}, ${2:0});", ci.getTextEdit()); } finally { JavaLanguageServerPlugin.getPreferencesManager().getPreferences().setGuessMethodArguments(oldGuessMethodArguments); } @@ -705,11 +692,8 @@ public void testCompletion_method_guessMethodArguments3() throws JavaModelExcept assertEquals("test", ci.getInsertText()); assertEquals(CompletionItemKind.Method, ci.getKind()); assertEquals("999999163", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); - assertTextEdit(5, 2, 5, "test(${1:one}, ${2:two});", resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); + assertTextEdit(5, 2, 5, "test(${1:one}, ${2:two});", ci.getTextEdit()); } finally { JavaLanguageServerPlugin.getPreferencesManager().getPreferences().setGuessMethodArguments(oldGuessMethodArguments); } @@ -740,11 +724,8 @@ public void testCompletion_method_guessMethodArgumentsConstructor() throws JavaM assertEquals("A", ci.getInsertText()); assertEquals(CompletionItemKind.Constructor, ci.getKind()); assertEquals("999999051", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); - assertTextEdit(3, 6, 7, "A(${1:str})", resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); + assertTextEdit(3, 6, 7, "A(${1:str})", ci.getTextEdit()); } finally { JavaLanguageServerPlugin.getPreferencesManager().getPreferences().setGuessMethodArguments(oldGuessMethodArguments); } @@ -780,12 +761,8 @@ public void testCompletion_field() throws JavaModelException{ assertEquals(CompletionItemKind.Field, item.getKind()); assertEquals("myTestString", item.getInsertText()); assertEquals("Foo.myTestString : String", item.getDetail()); - assertNull(item.getAdditionalTextEdits()); - assertNull(item.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(item).join(); - assertNotNull(resolvedItem.getTextEdit()); - assertTextEdit(4,8,15,"myTestString",resolvedItem.getTextEdit()); + assertNotNull(item.getTextEdit()); + assertTextEdit(4, 8, 15, "myTestString", item.getTextEdit()); //Not checking the range end character } @@ -809,12 +786,8 @@ public void testCompletion_import_type() throws JavaModelException{ CompletionItem item = list.getItems().get(0); assertEquals(CompletionItemKind.Interface, item.getKind()); assertEquals("Map", item.getInsertText()); - assertNull(item.getAdditionalTextEdits()); - assertNull(item.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(item).join(); - assertNotNull(resolvedItem.getTextEdit()); - assertTextEdit(3,3,15,"java.util.Map",resolvedItem.getTextEdit()); + assertNotNull(item.getTextEdit()); + assertTextEdit(3, 3, 15, "java.util.Map", item.getTextEdit()); //Not checking the range end character } @@ -1428,8 +1401,6 @@ private void testCompletion_classMethodOverride(String projectName, boolean supp assertFalse("No override proposals", filtered.isEmpty()); CompletionItem oride = filtered.get(0); assertEquals("toString", oride.getInsertText()); - assertNull(oride.getTextEdit()); - oride = server.resolveCompletionItem(oride).join(); assertNotNull(oride.getTextEdit()); String text = oride.getTextEdit().getNewText(); StringBuilder expectedText = new StringBuilder(); @@ -1474,8 +1445,6 @@ private void testCompletion_interfaceMethodOverride(String projectName, boolean assertFalse("No override proposals", filtered.isEmpty()); CompletionItem oride = filtered.get(0); assertEquals("run", oride.getInsertText()); - assertNull(oride.getTextEdit()); - oride = server.resolveCompletionItem(oride).join(); assertNotNull(oride.getTextEdit()); String text = oride.getTextEdit().getNewText(); StringBuilder expectedText = new StringBuilder(); @@ -1514,8 +1483,6 @@ public void testCompletion_methodOverrideWithParams() throws Exception { assertEquals("No override proposals", filtered.size(), 1); CompletionItem oride = filtered.get(0); assertEquals("getParent", oride.getInsertText()); - assertNull(oride.getTextEdit()); - oride = server.resolveCompletionItem(oride).join(); assertNotNull(oride.getTextEdit()); String text = oride.getTextEdit().getNewText(); @@ -1551,8 +1518,6 @@ public void testCompletion_methodOverrideWithException() throws Exception { assertEquals("No override proposals", filtered.size(), 1); CompletionItem oride = filtered.get(0); assertEquals("deleteSomething", oride.getInsertText()); - assertNull(oride.getTextEdit()); - oride = server.resolveCompletionItem(oride).join(); assertNotNull(oride.getTextEdit()); String text = oride.getTextEdit().getNewText(); @@ -1617,16 +1582,13 @@ public void testCompletion_getter() throws Exception { assertEquals("getStrField", ci.getInsertText()); assertEquals(CompletionItemKind.Method, ci.getKind()); assertEquals("999999979", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); assertTextEdit(2, 4, 7, "/**\n" + " * @return the strField\n" + " */\n" + "public String getStrField() {\n" + " return strField;\n" + - "}", resolvedItem.getTextEdit()); + "}", ci.getTextEdit()); } @Test @@ -1652,16 +1614,13 @@ public void testCompletion_booleangetter() throws Exception { assertEquals("isBoolField", ci.getInsertText()); assertEquals(CompletionItemKind.Method, ci.getKind()); assertEquals("999999979", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); assertTextEdit(2, 4, 6, "/**\n" + " * @return the boolField\n" + " */\n" + "public boolean isBoolField() {\n" + " return boolField;\n" + - "}", resolvedItem.getTextEdit()); + "}", ci.getTextEdit()); } @Test @@ -1686,16 +1645,13 @@ public void testCompletion_setter() throws Exception { assertEquals("setStrField", ci.getInsertText()); assertEquals(CompletionItemKind.Method, ci.getKind()); assertEquals("999999979", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); assertTextEdit(2, 4, 7, "/**\n" + " * @param strField the strField to set\n" + " */\n" + "public void setStrField(String strField) {\n" + " this.strField = strField;\n" + - "}", resolvedItem.getTextEdit()); + "}", ci.getTextEdit()); } @Test @@ -1723,17 +1679,14 @@ public void testCompletion_AnonymousType() throws Exception { assertEquals(CompletionItemKind.Constructor, ci.getKind()); assertEquals("java.Foo.IFoo", ci.getDetail()); assertEquals("999998684", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); assertTextEdit(2, 23, 23, "IFoo(){\n" + "\n" + " @Override\n" + " public String getName() {\n" + " ${0:// TODO Auto-generated method stub\n\t\t\treturn null;}\n" + " }\n" + - "};", resolvedItem.getTextEdit()); + "};", ci.getTextEdit()); } @Test @@ -1761,10 +1714,7 @@ public void testCompletion_AnonymousTypeMoreMethods() throws Exception { assertEquals("Foo.IFoo", ci.getInsertText()); assertEquals(CompletionItemKind.Constructor, ci.getKind()); assertEquals("999998684", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); assertTextEdit(2, 23, 23, "IFoo(){\n" + "\n @Override\n" + " public void setName(String name) {\n" + @@ -1775,7 +1725,7 @@ public void testCompletion_AnonymousTypeMoreMethods() throws Exception { " // TODO Auto-generated method stub\n" + " return null;\n" + " }\n" + - "};", resolvedItem.getTextEdit()); + "};", ci.getTextEdit()); } @Test @@ -1799,17 +1749,14 @@ public void testCompletion_AnonymousDeclarationType() throws Exception { assertEquals("Runnable", ci.getInsertText()); assertEquals(CompletionItemKind.Class, ci.getKind()); assertEquals("999999372", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); assertTextEdit(2, 20, 22, "(){\n" + "\n" + " @Override\n" + " public void run() {\n" + " ${0:// TODO Auto-generated method stub\n\t\t}\n" + " }\n" + - "}", resolvedItem.getTextEdit()); + "}", ci.getTextEdit()); } @Test @@ -1833,17 +1780,14 @@ public void testCompletion_AnonymousDeclarationType2() throws Exception { assertEquals("Runnable", ci.getInsertText()); assertEquals(CompletionItemKind.Class, ci.getKind()); assertEquals("999999372", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); assertTextEdit(2, 20, 24, "(){\n" + "\n" + " @Override\n" + " public void run() {\n" + " ${0:// TODO Auto-generated method stub\n\t\t}\n" + " }\n" + - "}", resolvedItem.getTextEdit()); + "}", ci.getTextEdit()); } @Test @@ -1869,17 +1813,14 @@ public void testCompletion_AnonymousDeclarationType3() throws Exception { assertEquals("Runnable", ci.getInsertText()); assertEquals(CompletionItemKind.Class, ci.getKind()); assertEquals("999999372", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); assertTextEdit(2, 32, 33, "(){\n" + "\n" + " @Override\n" + " public void run() {\n" + " ${0:// TODO Auto-generated method stub\n\t\t}\n" + " }\n" + - "}", resolvedItem.getTextEdit()); + "}", ci.getTextEdit()); } @Test @@ -1906,17 +1847,14 @@ public void testCompletion_AnonymousDeclarationType4() throws Exception { assertEquals("Runnable", ci.getInsertText()); assertEquals(CompletionItemKind.Class, ci.getKind()); assertEquals("999999372", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); assertTextEdit(2, 32, 33, "(){\n" + "\n" + " @Override\n" + " public void run() {\n" + " ${0:// TODO Auto-generated method stub\n\t\t}\n" + " }\n" + - "}", resolvedItem.getTextEdit()); + "}", ci.getTextEdit()); } @Test @@ -1938,17 +1876,14 @@ public void testCompletion_AnonymousDeclarationType5() throws Exception { assertEquals("Runnable", ci.getInsertText()); assertEquals(CompletionItemKind.Class, ci.getKind()); assertEquals("999999372", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); assertTextEdit(2, 33, 33, "(){\n" + "\n" + " @Override\n" + " public void run() {\n" + " ${0:// TODO Auto-generated method stub\n\t\t}\n" + " }\n" + - "}", resolvedItem.getTextEdit()); + "}", ci.getTextEdit()); } @Test @@ -1974,7 +1909,7 @@ public void testCompletion_type() throws Exception { assertEquals("ArrayList - java.util", ci.getLabel()); assertEquals("java.util.ArrayList", ci.getDetail()); assertEquals("999999148", ci.getSortText()); - assertNull(ci.getTextEdit()); + assertNotNull(ci.getTextEdit()); } @Test @@ -1998,11 +1933,8 @@ public void testCompletion_type() throws Exception { assertEquals("Foo$Bar", ci.getInsertText()); assertEquals(CompletionItemKind.Constructor, ci.getKind()); assertEquals("999999115", ci.getSortText()); - assertNull(ci.getTextEdit()); - - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); - assertTextEdit(2, 12, 15, "Foo\\$Bar()", resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); + assertTextEdit(2, 12, 15, "Foo\\$Bar()", ci.getTextEdit()); } @Test @@ -2027,11 +1959,9 @@ public void testCompletion_type() throws Exception { assertEquals("Foo$Bar", ci.getInsertText()); assertEquals(CompletionItemKind.Constructor, ci.getKind()); assertEquals("999999115", ci.getSortText()); - assertNull(ci.getTextEdit()); - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); - assertTextEdit(2, 12, 15, "Foo$Bar", resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); + assertTextEdit(2, 12, 15, "Foo$Bar", ci.getTextEdit()); } @Test @@ -2120,11 +2050,9 @@ public void testCompletion_overwrite() throws Exception { assertEquals("testInt", ci.getInsertText()); assertEquals(CompletionItemKind.Field, ci.getKind()); assertEquals("999998554", ci.getSortText()); - assertNull(ci.getTextEdit()); - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); + assertNotNull(ci.getTextEdit()); List edits = new ArrayList<>(); - edits.add(resolvedItem.getTextEdit()); + edits.add(ci.getTextEdit()); String returned = TextEditUtil.apply(unit, edits); //@formatter:off String expected = @@ -2147,36 +2075,34 @@ public void testCompletion_overwrite() throws Exception { public void testCompletion_insert() throws Exception { ICompilationUnit unit = getCompletionOverwriteReplaceUnit(); int[] loc = findCompletionLocation(unit, "method(t."); - CompletionList list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight(); - assertNotNull(list); - CompletionItem ci = list.getItems().stream().filter(item -> item.getLabel().startsWith("testInt : int")).findFirst().orElse(null); - assertNotNull(ci); - assertEquals("testInt", ci.getInsertText()); - assertEquals(CompletionItemKind.Field, ci.getKind()); - assertEquals("999998554", ci.getSortText()); - assertNull(ci.getTextEdit()); try { JavaLanguageServerPlugin.getPreferencesManager().getPreferences().setCompletionOverwrite(false); - CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); - assertNotNull(resolvedItem.getTextEdit()); - List edits = new ArrayList<>(); - edits.add(resolvedItem.getTextEdit()); - String returned = TextEditUtil.apply(unit, edits); - //@formatter:off - String expected = - "package foo.bar;\n\n" + - "public class BaseTest {\n" + - " public int testInt;\n\n" + - " public boolean method(int x, int y, int z) {\n" + - " return true;\n" + - " } \n\n" + - " public void update() {\n" + - " BaseTest t = new BaseTest();\n" + - " t.method(t.testIntthis.testInt, this.testInt);\n" + - " }\n" + - "}\n"; - //@formatter:on - assertEquals(returned, expected); + CompletionList list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight(); + assertNotNull(list); + CompletionItem ci = list.getItems().stream().filter(item -> item.getLabel().startsWith("testInt : int")).findFirst().orElse(null); + assertNotNull(ci); + assertEquals("testInt", ci.getInsertText()); + assertEquals(CompletionItemKind.Field, ci.getKind()); + assertEquals("999998554", ci.getSortText()); + assertNotNull(ci.getTextEdit()); + List edits = new ArrayList<>(); + edits.add(ci.getTextEdit()); + String returned = TextEditUtil.apply(unit, edits); + //@formatter:off + String expected = + "package foo.bar;\n\n" + + "public class BaseTest {\n" + + " public int testInt;\n\n" + + " public boolean method(int x, int y, int z) {\n" + + " return true;\n" + + " } \n\n" + + " public void update() {\n" + + " BaseTest t = new BaseTest();\n" + + " t.method(t.testIntthis.testInt, this.testInt);\n" + + " }\n" + + "}\n"; + //@formatter:on + assertEquals(returned, expected); } finally { JavaLanguageServerPlugin.getPreferencesManager().getPreferences().setCompletionOverwrite(true); } @@ -2383,6 +2309,7 @@ public void testStaticImports1() throws Exception { int[] loc = findCompletionLocation(unit, "fo"); CompletionList list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight(); assertNotNull(list); + assertFalse(list.isIncomplete()); assertTrue(list.getItems().size() > 0); assertTrue("no proposal for foo()", "foo() : void".equals(list.getItems().get(0).getLabel())); } finally { @@ -2390,6 +2317,58 @@ public void testStaticImports1() throws Exception { } } + @Test + public void testLimitCompletionResults() throws Exception { + int maxCompletionResults = PreferenceManager.getPrefs(null).getMaxCompletionResults(); + try { + ICompilationUnit unit = getWorkingCopy("src/test1/B.java", + //@formatter:off + "package test1;\n" + + "\n" + + "public class B {\n" + + " public void bar() {\n" + + " d\n" + + " }\n" + + "}\n"); + //@formatter:on + + int[] loc = findCompletionLocation(unit, "d"); + + //Completion should limit results to maxCompletionResults (excluding snippets) + CompletionList list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight(); + assertNotNull(list); + assertTrue(list.isIncomplete()); + List completionOnly = noSnippets(list.getItems()); + assertEquals(maxCompletionResults, completionOnly.size()); + assertTrue(completionOnly.get(0).getSortText().compareTo(completionOnly.get(completionOnly.size() - 1).getSortText()) < 0); + + //Set max results to 1 to double check + PreferenceManager.getPrefs(null).setMaxCompletionResults(1); + list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight(); + assertNotNull(list); + assertTrue(list.isIncomplete()); + completionOnly = noSnippets(list.getItems()); + assertEquals(1, completionOnly.size()); + + //when maxCompletionResults is set to 0, limit is disabled, completion should be complete + PreferenceManager.getPrefs(null).setMaxCompletionResults(0); + list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight(); + assertNotNull(list); + assertFalse(list.isIncomplete()); + completionOnly = noSnippets(list.getItems()); + assertTrue("Expected way than " + completionOnly.size(), completionOnly.size() > Preferences.JAVA_COMPLETION_MAX_RESULTS_DEFAULT); + assertTrue(completionOnly.get(0).getSortText().compareTo(completionOnly.get(completionOnly.size() - 1).getSortText()) < 0); + + } finally { + PreferenceManager.getPrefs(null).setMaxCompletionResults(maxCompletionResults); + } + } + + private List noSnippets(List items) { + return items.stream().filter(i -> !CompletionItemKind.Snippet.equals(i.getKind())).collect(Collectors.toList()); + } + + @Test public void testStaticImports2() throws Exception { PreferenceManager.getPrefs(null).setJavaCompletionFavoriteMembers(Collections.emptyList()); @@ -2600,16 +2579,18 @@ public void testCompletion_ConstantDefaultValue() throws JavaModelException { CompletionList list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight(); assertNotNull(list); assertEquals(3, list.getItems().size()); - CompletionItem ci = list.getItems().get(1); - assertEquals(CompletionItemKind.Constant, ci.getKind()); - assertEquals("TEST : double = 107.1921", ci.getLabel()); - ci = list.getItems().get(2); + CompletionItem ci = list.getItems().get(0); assertEquals(CompletionItemKind.Constant, ci.getKind()); assertEquals("ONE : int = 1", ci.getLabel()); CompletionItem resolvedItem = server.resolveCompletionItem(ci).join(); assertEquals(CompletionItemKind.Constant, resolvedItem.getKind()); String documentation = resolvedItem.getDocumentation().getLeft(); assertEquals("Value: 1", documentation); + + ci = list.getItems().get(1); + assertEquals(CompletionItemKind.Constant, ci.getKind()); + assertEquals("TEST : double = 107.1921", ci.getLabel()); + loc = findCompletionLocation(unit, "@IConstantDefault("); list = server.completion(JsonMessageHelper.getParams(createCompletionRequest(unit, loc[0], loc[1]))).join().getRight(); assertNotNull(list);