Skip to content

Commit

Permalink
Introduce completion item collapse for method overloads
Browse files Browse the repository at this point in the history
Signed-off-by: Hope Hadfield <[email protected]>
  • Loading branch information
hopehadfield authored and rgrunber committed Feb 22, 2024
1 parent 6d8eb7a commit 657442b
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.eclipse.jdt.ls.core.internal.contentassist;

import java.lang.reflect.Field;
import java.util.Map;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.CompletionContext;
Expand Down Expand Up @@ -51,11 +52,19 @@ public class CompletionProposalDescriptionProvider {
*/
private CompletionContext fContext;
private ICompilationUnit fUnit;
private Map<String, Integer> fCollapsedTypes;

/**
* Creates a new label provider.
*
*/
public CompletionProposalDescriptionProvider(ICompilationUnit unit, CompletionContext context, Map<String, Integer> collapsedTypes) {
super();
fContext = context;
fUnit = unit;
fCollapsedTypes = collapsedTypes;
}

public CompletionProposalDescriptionProvider(ICompilationUnit unit, CompletionContext context) {
super();
fContext = context;
Expand Down Expand Up @@ -319,20 +328,33 @@ private static final StringBuilder appendParameterSignature(StringBuilder buffer
*/
private void createMethodProposalLabel(CompletionProposal methodProposal, CompletionItem item) {
StringBuilder description = this.createMethodProposalDescription(methodProposal);
String proposalName = String.valueOf(methodProposal.getName());
boolean skipDetail = false;
if (isCompletionItemLabelDetailsSupport()){
StringBuilder methodParams = new StringBuilder();
methodParams.append('(');
appendUnboundedParameterList(methodParams, methodProposal);
methodParams.append(')');
if (methodProposal.getKind() != CompletionProposal.CONSTRUCTOR_INVOCATION) {
StringBuilder returnType = new StringBuilder();
appendReturnType(returnType, methodProposal);
setLabelDetails(item, String.valueOf(methodProposal.getName()), methodParams.toString(), returnType.toString());
if (fCollapsedTypes.getOrDefault(proposalName, 0) > 1 && methodProposal.getKind() != CompletionProposal.CONSTRUCTOR_INVOCATION) {
setLabelDetails(item, proposalName, "(...)", fCollapsedTypes.get(proposalName).toString() + " overloads");
skipDetail = true;
} else {
setLabelDetails(item, String.valueOf(methodProposal.getName()), methodParams.toString(), null);
StringBuilder methodParams = new StringBuilder();
methodParams.append('(');
appendUnboundedParameterList(methodParams, methodProposal);
methodParams.append(')');
if (methodProposal.getKind() != CompletionProposal.CONSTRUCTOR_INVOCATION) {
StringBuilder returnType = new StringBuilder();
appendReturnType(returnType, methodProposal);
setLabelDetails(item, proposalName, methodParams.toString(), returnType.toString());
} else {
setLabelDetails(item, proposalName, methodParams.toString(), null);
}
}
} else {
item.setLabel(description.toString());
if (fCollapsedTypes.getOrDefault(proposalName, 0) > 1 && methodProposal.getKind() != CompletionProposal.CONSTRUCTOR_INVOCATION) {
item.setLabel(proposalName + "(...)");
item.setDetail(fCollapsedTypes.get(proposalName).toString() + " overloads");
skipDetail = true;
} else {
item.setLabel(description.toString());
}
}
item.setInsertText(String.valueOf(methodProposal.getName()));

Expand All @@ -352,13 +374,16 @@ private void createMethodProposalLabel(CompletionProposal methodProposal, Comple

declaringType= Signature.getSimpleName(declaringType);
typeInfo.append(declaringType);
StringBuilder detail = new StringBuilder();
if (typeInfo.length() > 0) {
detail.append(typeInfo);
detail.append('.');

if (!skipDetail) {
StringBuilder detail = new StringBuilder();
if (typeInfo.length() > 0) {
detail.append(typeInfo);
detail.append('.');
}
detail.append(description);
item.setDetail(detail.toString());
}
detail.append(description);
item.setDetail(detail.toString());

if (fUnit != null && methodProposal.isConstructor() && typeInfo.length() > 0 && item.getData() != null && methodProposal.getRequiredProposals() != null && methodProposal.getRequiredProposals().length > 0) {
CompletionProposal requiredProposal = methodProposal.getRequiredProposals()[0];
Expand Down Expand Up @@ -701,7 +726,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,7 +68,7 @@ public final class CompletionProposalRequestor extends CompletionRequestor {

private List<CompletionProposal> proposals = new ArrayList<>();
// Cache to store all the types that has been collapsed, due to off mode of argument guessing.
private Set<String> collapsedTypes = new HashSet<>();
private Map<String, Integer> collapsedTypes = new HashMap<>();
private final ICompilationUnit unit;
private final String uri; // URI of this.unit, used in future "resolve" requests
private CompletionProposalDescriptionProvider descriptionProvider;
Expand Down Expand Up @@ -471,7 +470,7 @@ public void acceptContext(CompletionContext context) {
super.acceptContext(context);
this.context = context;
response.setContext(context);
this.descriptionProvider = new CompletionProposalDescriptionProvider(unit, context);
this.descriptionProvider = new CompletionProposalDescriptionProvider(unit, context, collapsedTypes);
this.proposalProvider = new CompletionProposalReplacementProvider(
unit,
context,
Expand Down Expand Up @@ -739,15 +738,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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -622,6 +624,7 @@ public class Preferences {
private boolean foldingRangeEnabled;
private boolean selectionRangeEnabled;
private CompletionGuessMethodArgumentsMode guessMethodArguments;
private boolean collapseCompletionItems;

private boolean javaFormatComments;
private boolean hashCodeEqualsTemplateUseJava7Objects;
Expand Down Expand Up @@ -879,6 +882,7 @@ public Preferences() {
foldingRangeEnabled = true;
selectionRangeEnabled = true;
guessMethodArguments = CompletionGuessMethodArgumentsMode.INSERT_PARAMETER_NAMES;
collapseCompletionItems = false;
javaFormatComments = true;
hashCodeEqualsTemplateUseJava7Objects = false;
hashCodeEqualsTemplateUseInstanceof = false;
Expand Down Expand Up @@ -1083,6 +1087,9 @@ public static Preferences createFrom(Map<String, Object> 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);
Expand Down Expand Up @@ -1557,6 +1564,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;
Expand Down Expand Up @@ -1877,6 +1889,10 @@ public CompletionGuessMethodArgumentsMode getGuessMethodArgumentsMode() {
return guessMethodArguments;
}

public boolean isCollapseCompletionItemsEnabled() {
return collapseCompletionItems;
}

public boolean isHashCodeEqualsTemplateUseJava7Objects() {
return hashCodeEqualsTemplateUseJava7Objects;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down

0 comments on commit 657442b

Please sign in to comment.