Skip to content

Commit

Permalink
WIP completion refactoring
Browse files Browse the repository at this point in the history
Signed-off-by: Fred Bricon <[email protected]>
  • Loading branch information
fbricon committed Dec 16, 2019
1 parent cf6358b commit 2aa4845
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 206 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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<CompletionProposal> {

private Map<CompletionProposal, char[]> 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
Expand All @@ -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);
Expand Down Expand Up @@ -124,11 +175,27 @@ public void accept(CompletionProposal proposal) {
}

public List<CompletionItem> getCompletionItems() {
response.setProposals(proposals);
CompletionResponses.store(response);
//Sort the results by relevance 1st
proposals.sort(new ProposalComparator(proposals.size()));
List<CompletionItem> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,24 +32,48 @@
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;
import org.eclipse.lsp4j.CompletionOptions;
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<String> UNSUPPORTED_RESOURCES = Sets.newHashSet("module-info.java", "package-info.java");

static final Comparator<CompletionItem> PROPOSAL_COMPARATOR = new Comparator<CompletionItem>() {

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<List<CompletionItem>, CompletionList> completion(CompletionParams position,
IProgressMonitor monitor) {
List<CompletionItem> 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) {
Expand All @@ -59,27 +83,30 @@ Either<List<CompletionItem>, 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<CompletionItem> 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<CompletionItem> 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);
Expand All @@ -95,7 +122,6 @@ private List<CompletionItem> 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()) {
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
@Override
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams position) {
logInfo(">> document/completion");
CompletionHandler handler = new CompletionHandler();
CompletionHandler handler = new CompletionHandler(preferenceManager);
final IProgressMonitor[] monitors = new IProgressMonitor[1];
CompletableFuture<Either<List<CompletionItem>, CompletionList>> result = computeAsync((monitor) -> {
monitors[0] = monitor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,13 @@ public class Preferences {
public static final String JAVA_COMPLETION_FAVORITE_MEMBERS_KEY = "java.completion.favoriteStaticMembers";
public static final List<String> 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.
Expand Down Expand Up @@ -361,8 +368,8 @@ public class Preferences {
private String formatterProfileName;
private Collection<IPath> rootPaths;
private Collection<IPath> triggerFiles;

private int parallelBuildsCount;
private int maxCompletionResults;

static {
JAVA_IMPORT_EXCLUSIONS_DEFAULT = new LinkedList<>();
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -612,6 +620,9 @@ public static Preferences createFrom(Map<String, Object> 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;
}

Expand Down Expand Up @@ -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;
}
}
Loading

0 comments on commit 2aa4845

Please sign in to comment.