From 19d03b9aa1d6a342aca6d401182439ddf368d5a5 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Mon, 22 Jan 2024 15:20:57 -0500 Subject: [PATCH] Introduce completion item collapse for method overloads Signed-off-by: Hope Hadfield --- ...CompletionProposalDescriptionProvider.java | 2 +- ...CompletionProposalReplacementProvider.java | 4 +- .../CompletionProposalRequestor.java | 37 +++++++++++---- .../handlers/CompletionResolveHandler.java | 3 +- .../internal/preferences/Preferences.java | 16 +++++++ .../handlers/CompletionHandlerTest.java | 47 +++++++++++++++++++ 6 files changed, 96 insertions(+), 13 deletions(-) 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..be1edd4915 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 @@ -701,7 +701,7 @@ public void updateDescription(CompletionProposal proposal, CompletionItem item) // change the proposal to required type proposal when the // argument guessing is turned off and the proposal is a constructor. if (JavaLanguageServerPlugin.getPreferencesManager().getPreferences().getGuessMethodArgumentsMode() == - CompletionGuessMethodArgumentsMode.OFF) { + CompletionGuessMethodArgumentsMode.OFF || JavaLanguageServerPlugin.getPreferencesManager().getPreferences().isCollapseCompletionItemsEnabled()) { CompletionProposal requiredTypeProposal = CompletionProposalUtils.getRequiredTypeProposal(proposal); if (requiredTypeProposal != null) { proposal = requiredTypeProposal; 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..e1ff9b34e9 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,7 @@ 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(); @@ -627,7 +627,7 @@ private void appendGuessingCompletion(StringBuilder buffer, CompletionProposal p } CompletionGuessMethodArgumentsMode guessMethodArgumentsMode = JavaLanguageServerPlugin.getPreferencesManager().getPreferences().getGuessMethodArgumentsMode(); - if (guessMethodArgumentsMode == CompletionGuessMethodArgumentsMode.OFF) { + if (guessMethodArgumentsMode == CompletionGuessMethodArgumentsMode.OFF || JavaLanguageServerPlugin.getPreferencesManager().getPreferences().isCollapseCompletionItemsEnabled()) { buffer.append(CURSOR_POSITION); return; } 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 35e5f2f56e..fe70e3ee91 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 @@ -16,7 +16,6 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -57,6 +56,7 @@ import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItemDefaults; import org.eclipse.lsp4j.CompletionItemKind; +import org.eclipse.lsp4j.CompletionItemLabelDetails; import org.eclipse.lsp4j.CompletionItemTag; import org.eclipse.lsp4j.InsertReplaceRange; import org.eclipse.lsp4j.InsertTextMode; @@ -69,7 +69,7 @@ public final class CompletionProposalRequestor extends CompletionRequestor { private List proposals = new ArrayList<>(); // Cache to store all the types that has been collapsed, due to off mode of argument guessing. - private Set collapsedTypes = new HashSet<>(); + private Map collapsedTypes = new HashMap<>(); private final ICompilationUnit unit; private final String uri; // URI of this.unit, used in future "resolve" requests private CompletionProposalDescriptionProvider descriptionProvider; @@ -430,6 +430,19 @@ public CompletionItem toCompletionItem(CompletionProposal proposal, int index) { } else if (labelDetailsEnabled && $.getKind() == CompletionItemKind.Constructor && $.getLabelDetails() != null && $.getLabelDetails().getDetail() != null) { filterText = newText.concat($.getLabelDetails().getDetail()); } + if (proposal.getKind() == CompletionProposal.METHOD_REF + && collapsedTypes.containsKey(String.valueOf(proposal.getName())) && collapsedTypes.get(String.valueOf(proposal.getName())) > 1) { + if (labelDetailsEnabled) { + CompletionItemLabelDetails newLabelDetails = new CompletionItemLabelDetails(); + newLabelDetails.setDetail("(...)"); + newLabelDetails.setDescription(collapsedTypes.get(String.valueOf(proposal.getName())).toString() + " overloads"); + $.setLabelDetails(newLabelDetails); + $.setDetail(null); + } else { + $.setLabel(String.valueOf(proposal.getName()) + "(...)"); + $.setDetail(collapsedTypes.get(String.valueOf(proposal.getName())).toString() + " overloads"); + } + } if (range != null && !filterText.isEmpty()) { $.setFilterText(filterText); } else if (range != null && newText != null) { @@ -739,15 +752,21 @@ private boolean matchCase(CompletionProposal proposal) { * Check if the current completion proposal needs to be collapsed. */ private boolean needToCollapse(CompletionProposal proposal) { - if (preferenceManager.getPreferences().getGuessMethodArgumentsMode() != CompletionGuessMethodArgumentsMode.OFF) { - return false; - } + if (preferenceManager.getPreferences().isCollapseCompletionItemsEnabled() || preferenceManager.getPreferences().getGuessMethodArgumentsMode() == CompletionGuessMethodArgumentsMode.OFF) { - CompletionProposal requiredProposal = CompletionProposalUtils.getRequiredTypeProposal(proposal); - if (requiredProposal == null) { - return false; + if (proposal.getKind() == CompletionProposal.METHOD_REF && preferenceManager.getPreferences().isCollapseCompletionItemsEnabled()) { + return collapsedTypes.merge(String.valueOf(proposal.getName()), 1, Integer::sum) > 1; + } + + CompletionProposal requiredProposal = CompletionProposalUtils.getRequiredTypeProposal(proposal); + + if (requiredProposal == null) { + return false; + } + + return collapsedTypes.merge(String.valueOf(requiredProposal.getSignature()), 1, Integer::sum) > 1; } - return !collapsedTypes.add(String.valueOf(requiredProposal.getSignature())); + return false; } } 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 584a183b76..9f379eb8b2 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 @@ -182,7 +182,8 @@ public CompletionItem resolve(CompletionItem param, IProgressMonitor monitor) { // resolving documentation // 1. get the required type proposal when the argument guessing is // turned off and the proposal is a constructor. - if (manager.getPreferences().getGuessMethodArgumentsMode() == CompletionGuessMethodArgumentsMode.OFF) { + if (manager.getPreferences().getGuessMethodArgumentsMode() == CompletionGuessMethodArgumentsMode.OFF || + JavaLanguageServerPlugin.getPreferencesManager().getPreferences().isCollapseCompletionItemsEnabled()) { CompletionProposal requiredTypeProposal = CompletionProposalUtils.getRequiredTypeProposal(proposal); if (requiredTypeProposal != null) { proposal = requiredTypeProposal; 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 c1ddc6a60e..d34f9d6a79 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 @@ -398,6 +398,8 @@ public class Preferences { */ public static final String JAVA_COMPLETION_GUESS_METHOD_ARGUMENTS_KEY = "java.completion.guessMethodArguments"; + public static final String JAVA_COMPLETION_COLLAPSE_KEY = "java.completion.collapseCompletionItems"; + /** * A named preference that defines how member elements are ordered by code * actions. @@ -621,6 +623,7 @@ public class Preferences { private boolean foldingRangeEnabled; private boolean selectionRangeEnabled; private CompletionGuessMethodArgumentsMode guessMethodArguments; + private boolean collapseCompletionItems; private boolean javaFormatComments; private boolean hashCodeEqualsTemplateUseJava7Objects; @@ -877,6 +880,7 @@ public Preferences() { foldingRangeEnabled = true; selectionRangeEnabled = true; guessMethodArguments = CompletionGuessMethodArgumentsMode.INSERT_PARAMETER_NAMES; + collapseCompletionItems = false; javaFormatComments = true; hashCodeEqualsTemplateUseJava7Objects = false; hashCodeEqualsTemplateUseInstanceof = false; @@ -1080,6 +1084,9 @@ public static Preferences createFrom(Map configuration) { CompletionGuessMethodArgumentsMode.INSERT_PARAMETER_NAMES)); } + boolean collapseCompletionItemsEnabled = getBoolean(configuration, JAVA_COMPLETION_COLLAPSE_KEY, false); + prefs.setCollapseCompletionItemsEnabled(collapseCompletionItemsEnabled); + boolean hashCodeEqualsTemplateUseJava7Objects = getBoolean(configuration, JAVA_CODEGENERATION_HASHCODEEQUALS_USEJAVA7OBJECTS, false); prefs.setHashCodeEqualsTemplateUseJava7Objects(hashCodeEqualsTemplateUseJava7Objects); boolean hashCodeEqualsTemplateUseInstanceof = getBoolean(configuration, JAVA_CODEGENERATION_HASHCODEEQUALS_USEINSTANCEOF, false); @@ -1544,6 +1551,11 @@ public Preferences setGuessMethodArgumentsMode(CompletionGuessMethodArgumentsMod return this; } + public Preferences setCollapseCompletionItemsEnabled(boolean enabled) { + this.collapseCompletionItems = enabled; + return this; + } + public Preferences setJavaFormatEnabled(boolean enabled) { this.javaFormatEnabled = enabled; return this; @@ -1864,6 +1876,10 @@ public CompletionGuessMethodArgumentsMode getGuessMethodArgumentsMode() { return guessMethodArguments; } + public boolean isCollapseCompletionItemsEnabled() { + return collapseCompletionItems; + } + public boolean isHashCodeEqualsTemplateUseJava7Objects() { return hashCodeEqualsTemplateUseJava7Objects; } 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 8a1254b009..58e538368b 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 @@ -4087,6 +4087,53 @@ public void testCompletion_order() throws Exception { assertTrue(list.getItems().get(2).getFilterText().startsWith("test(String x, int y, boolean z)")); } + @Test + public void testCompletion_collapse() throws Exception { + when(preferenceManager.getClientPreferences().isCompletionItemLabelDetailsSupport()).thenReturn(true); + preferenceManager.getPreferences().setCollapseCompletionItemsEnabled(true); + ICompilationUnit unit = getWorkingCopy("src/org/sample/Test.java", String.join("\n", + //@formatter:off + "package org.sample", + "public class Test {", + " public void test(String x){}", + " public void test(String x, int y){}", + " public void test(String x, int y, boolean z){}", + " public static void main(String[] args) {", + " Test obj = new Test();", + " obj.test", + " }", + "}")); + //@formatter:on + CompletionList list = requestCompletions(unit, "obj.test"); + assertFalse(list.getItems().isEmpty()); + assertTrue(list.getItems().get(0).getLabelDetails().getDetail().startsWith("(...)")); + assertTrue(list.getItems().get(0).getLabelDetails().getDescription().startsWith("3 overloads")); + } + + @Test + public void testCompletion_collapse_extends() throws Exception { + when(preferenceManager.getClientPreferences().isCompletionItemLabelDetailsSupport()).thenReturn(true); + preferenceManager.getPreferences().setCollapseCompletionItemsEnabled(true); + ICompilationUnit unit = getWorkingCopy("src/org/sample/Test.java", String.join("\n", + //@formatter:off + "package org.sample", + "class Test extends TestSuper {", + " public void test(String x){}", + " public static void main(String[] args) {", + " Test obj = new Test();", + " obj.test", + " }", + "}", + "public class TestSuper {", + " public void test(String x, int y){}", + "}")); + //@formatter:on + CompletionList list = requestCompletions(unit, "obj.test"); + assertFalse(list.getItems().isEmpty()); + assertTrue(list.getItems().get(0).getLabelDetails().getDetail().startsWith("(...)")); + assertTrue(list.getItems().get(0).getLabelDetails().getDescription().startsWith("2 overloads")); + } + private CompletionList requestCompletions(ICompilationUnit unit, String completeBehind) throws JavaModelException { return requestCompletions(unit, completeBehind, 0); }