selection after methods chains completion fixed and chain relevance improved

This commit is contained in:
Dmitry Batkovich
2013-10-24 12:58:14 +04:00
parent 80106fc16c
commit ca4af156d6
12 changed files with 358 additions and 46 deletions
@@ -73,7 +73,9 @@ public class MethodsChainsCompletionContributor extends CompletionContributor {
}
contextRelevantTypes.remove(targetClassQName);
final List<LookupElement> foundElements = searchForLookups(targetClassQName, contextRelevantTypes, completionContext);
//final boolean useBigrams = ApplicationManager.getApplication().isUnitTestMode() || parameters.getInvocationCount() == 3;
final boolean useBigrams = true;
final List<LookupElement> foundElements = searchForLookups(targetClassQName, contextRelevantTypes, completionContext, useBigrams);
result.addAllElements(foundElements);
}
});
@@ -81,8 +83,9 @@ public class MethodsChainsCompletionContributor extends CompletionContributor {
private static List<LookupElement> searchForLookups(final String targetClassQName,
final Set<String> contextRelevantTypes,
final ChainCompletionContext completionContext) {
final MethodChainsSearchService searchService = new MethodChainsSearchService(completionContext.getProject());
final ChainCompletionContext completionContext,
final boolean useBigrams) {
final MethodChainsSearchService searchService = new MethodChainsSearchService(completionContext.getProject(), useBigrams);
final List<MethodsChain> searchResult =
searchChains(targetClassQName, contextRelevantTypes, MAX_SEARCH_RESULT_SIZE, MAX_CHAIN_SIZE, completionContext, searchService);
if (searchResult.size() < MAX_SEARCH_RESULT_SIZE) {
@@ -1,10 +1,13 @@
package com.intellij.codeInsight.completion.methodChains.completion.lookup;
import com.intellij.codeInsight.completion.CompletionInitializationContext;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementDecorator;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
@@ -20,6 +23,7 @@ import java.util.Collection;
* @author Dmitry Batkovich <dmitry.batkovich@jetbrains.com>
*/
public class ChainCompletionNewVariableLookupElement extends LookupElementDecorator<LookupElement> {
private final static Logger log = Logger.getInstance(ChainCompletionNewVariableLookupElement.class);
private final PsiClass myPsiClass;
private final String myNewVarName;
@@ -39,20 +43,25 @@ public class ChainCompletionNewVariableLookupElement extends LookupElementDecora
@Override
public void handleInsert(final InsertionContext context) {
final RangeMarker rangeMarker = context.getDocument().createRangeMarker(context.getStartOffset(), context.getStartOffset());
getDelegate().handleInsert(context);
final PsiFile file = context.getFile();
((PsiJavaFile)file).importClass(myPsiClass);
final PsiElement caretElement = file.findElementAt(context.getEditor().getCaretModel().getOffset());
if (caretElement == null) {
throw new NullPointerException();
log.error("element on caret position MUST BE not null");
return;
}
final PsiStatement statement = (PsiStatement) caretElement.getPrevSibling();
final PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(statement, PsiCodeBlock.class);
if (codeBlock == null) {
throw new NullPointerException();
log.error("code block MUST BE not null");
return;
}
final Project project = context.getProject();
final Ref<PsiElement> insertedStatementRef = Ref.create();
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
context.commitDocument();
new WriteCommandAction.Simple(project, file) {
@Override
protected void run() throws Throwable {
@@ -61,10 +70,9 @@ public class ChainCompletionNewVariableLookupElement extends LookupElementDecora
}
}.execute();
final PsiLiteralExpression nullKeyword = findNull(insertedStatementRef.get());
context.commitDocument();
PsiDocumentManager.getInstance(context.getProject()).doPostponedOperationsAndUnblockDocument(context.getDocument());
getDelegate().handleInsert(context);
context.getDocument().insertString(rangeMarker.getStartOffset(), myNewVarName + ".");
context.commitDocument();
final int offset = nullKeyword.getTextOffset();
final int endOffset = offset + nullKeyword.getTextLength();
context.getEditor().getSelectionModel().setSelection(offset, endOffset);
@@ -74,7 +82,7 @@ public class ChainCompletionNewVariableLookupElement extends LookupElementDecora
@NotNull
@Override
public String getLookupString() {
return myNewVarName + "." + getDelegate().getLookupString();
return getDelegate().getLookupString();
}
@Override
@@ -3,6 +3,7 @@ package com.intellij.codeInsight.completion.methodChains.completion.lookup;
import com.intellij.codeInsight.completion.methodChains.search.ChainRelevance;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementDecorator;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import org.jetbrains.annotations.NotNull;
/**
@@ -16,6 +17,12 @@ public final class WeightableChainLookupElement extends LookupElementDecorator<L
myChainRelevance = relevance;
}
@Override
public void renderElement(LookupElementPresentation presentation) {
super.renderElement(presentation);
presentation.setItemText("c: " + presentation.getItemText());
}
public ChainRelevance getChainRelevance() {
return myChainRelevance;
}
@@ -7,7 +7,7 @@ import org.jetbrains.annotations.TestOnly;
* @author Dmitry Batkovich <dmitry.batkovich@jetbrains.com>
*/
public class ChainRelevance implements Comparable<ChainRelevance> {
public static final ChainRelevance LOWEST = new ChainRelevance(Integer.MAX_VALUE, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, false, false);
public static final ChainRelevance LOWEST = new ChainRelevance(Integer.MAX_VALUE, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, false, false, 0);
private final int myChainSize;
private final int myLastMethodOccurrences;
@@ -15,19 +15,22 @@ public class ChainRelevance implements Comparable<ChainRelevance> {
private final int myNotMatchedStringVars;
private final boolean myHasCallingVariableInContext;
private final boolean myFirstMethodStatic;
private final int myParametersInContext;
public ChainRelevance(final int chainSize,
final int lastMethodOccurrences,
final int unreachableParametersCount,
final int notMatchedStringVars,
final boolean hasCallingVariableInContext,
final boolean firstMethodStatic) {
final boolean firstMethodStatic,
final int parametersInContext) {
myChainSize = chainSize;
myLastMethodOccurrences = lastMethodOccurrences;
myUnreachableParametersCount = unreachableParametersCount;
myNotMatchedStringVars = notMatchedStringVars;
myHasCallingVariableInContext = hasCallingVariableInContext;
myFirstMethodStatic = firstMethodStatic;
myParametersInContext = parametersInContext;
}
@TestOnly
@@ -62,16 +65,22 @@ public class ChainRelevance implements Comparable<ChainRelevance> {
@Override
public int compareTo(@NotNull final ChainRelevance that) {
if (myHasCallingVariableInContext && !that.myHasCallingVariableInContext) {
return 1;
}
if (that.myHasCallingVariableInContext && !myHasCallingVariableInContext) {
return -1;
}
if (myFirstMethodStatic && !that.myFirstMethodStatic) {
return -1;
}
if (that.myFirstMethodStatic && !myFirstMethodStatic) {
return 1;
}
if (myHasCallingVariableInContext && !that.myHasCallingVariableInContext) {
if (myParametersInContext > that.myParametersInContext) {
return 1;
}
if (that.myHasCallingVariableInContext && !myHasCallingVariableInContext) {
if (myParametersInContext <= that.myParametersInContext) {
return -1;
}
int sub = myLastMethodOccurrences - that.myLastMethodOccurrences;
@@ -56,22 +56,27 @@ public class ChainsSearcher {
final List<WeightAware<MethodIncompleteSignature>> allInitialVertexes = initResult.getVertexes();
final LinkedList<WeightAware<Pair<MethodIncompleteSignature, MethodsChain>>> q =
new LinkedList<WeightAware<Pair<MethodIncompleteSignature, MethodsChain>>>(
ContainerUtil.map(allInitialVertexes, new Function<WeightAware<MethodIncompleteSignature>, WeightAware<Pair<MethodIncompleteSignature, MethodsChain>>>() {
@Override
public WeightAware<Pair<MethodIncompleteSignature, MethodsChain>> fun(
final WeightAware<MethodIncompleteSignature> methodIncompleteSignatureWeightAware) {
return new WeightAware<Pair<MethodIncompleteSignature, MethodsChain>>(
new Pair<MethodIncompleteSignature, MethodsChain>(
methodIncompleteSignatureWeightAware
.getUnderlying(),
new MethodsChain(resolver.get(
methodIncompleteSignatureWeightAware.getUnderlying()),
methodIncompleteSignatureWeightAware.getWeight(),
methodIncompleteSignatureWeightAware.getUnderlying().getOwner())),
methodIncompleteSignatureWeightAware.getWeight());
}
}));
new LinkedList<WeightAware<Pair<MethodIncompleteSignature, MethodsChain>>>(ContainerUtil.map(allInitialVertexes,
new Function<WeightAware<MethodIncompleteSignature>, WeightAware<Pair<MethodIncompleteSignature, MethodsChain>>>() {
@Override
public WeightAware<Pair<MethodIncompleteSignature, MethodsChain>> fun(
final WeightAware<MethodIncompleteSignature> methodIncompleteSignatureWeightAware) {
return new WeightAware<Pair<MethodIncompleteSignature, MethodsChain>>(
new Pair<MethodIncompleteSignature, MethodsChain>(
methodIncompleteSignatureWeightAware
.getUnderlying(),
new MethodsChain(resolver.get(
methodIncompleteSignatureWeightAware
.getUnderlying()),
methodIncompleteSignatureWeightAware
.getWeight(),
methodIncompleteSignatureWeightAware
.getUnderlying()
.getOwner())),
methodIncompleteSignatureWeightAware
.getWeight());
}
}));
int maxWeight = 0;
for (final MethodsChain methodsChain : knownDistance.values()) {
@@ -215,4 +220,32 @@ public class ChainsSearcher {
return myResult.size();
}
}
}
private static boolean doChoose(final SortedSet<UsageIndexValue> bigrams, final int currentWeight, final int maxResultSize) {
if (bigrams.size() == 1) {
return true;
}
int sumWeight = 0;
for (final UsageIndexValue bigram : bigrams) {
sumWeight += bigram.getOccurrences();
}
if (Math.abs(sumWeight - currentWeight) < currentWeight / maxResultSize) {
return true;
}
final List<UsageIndexValue> essentialValues = new ArrayList<UsageIndexValue>();
Integer max = null;
for (UsageIndexValue bigram : bigrams) {
if (max == null) {
max = bigram.getOccurrences();
}
if (max / bigram.getOccurrences() > maxResultSize) {
break;
}
essentialValues.add(bigram);
if (essentialValues.size() > maxResultSize) {
return false;
}
}
return true;
}
}
@@ -8,8 +8,6 @@ import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiManager;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -22,8 +20,10 @@ public class MethodChainsSearchService {
private final MethodsUsageIndex myMethodsUsageIndex;
private final BigramMethodsUsageIndex myBigramMethodsUsageIndex;
private final Project myProject;
private final boolean myUseBigrams;
public MethodChainsSearchService(final Project project) {
public MethodChainsSearchService(final Project project, final boolean useBigrams) {
myUseBigrams = useBigrams;
myMethodsUsageIndex = MethodsUsageIndex.getInstance(project);
myBigramMethodsUsageIndex = BigramMethodsUsageIndex.getInstance(project);
myProject = project;
@@ -36,9 +36,11 @@ public class MethodChainsSearchService {
@NotNull
@SuppressWarnings("unchecked")
public SortedSet<UsageIndexValue> getBigram(final MethodIncompleteSignature methodIncompleteSignature) {
final TreeSet<UsageIndexValue> value = myBigramMethodsUsageIndex.getValues(methodIncompleteSignature);
if (value != null) {
return value;
final TreeSet<UsageIndexValue> values = myUseBigrams
? myBigramMethodsUsageIndex.getValues(methodIncompleteSignature)
: myMethodsUsageIndex.getValues(methodIncompleteSignature.getOwner());
if (values != null) {
return values;
}
return EMPTY_SORTED_SET;
}
@@ -53,6 +53,7 @@ public class MethodsChainLookupRangingHelper {
final int lastMethodWeight = chain.getChainWeight();
int unreachableParametersCount = 0;
int notMatchedStringVars = 0;
int matchedParametersInContext = 0;
Boolean isFirstMethodStatic = null;
Boolean hasCallingVariableInContext = null;
LookupElement chainLookupElement = null;
@@ -89,9 +90,11 @@ public class MethodsChainLookupRangingHelper {
if (isHead && procResult.isIntroduceNewVariable()) {
newVariableClass = qualifierClass;
}
matchedParametersInContext += procResult.getMatchedParametersInContext();
unreachableParametersCount += procResult.getUnreachableParametersCount();
notMatchedStringVars += procResult.getNotMatchedStringVars();
chainLookupElement = isHead ? procResult.getLookupElement() : new JavaChainLookupElement(chainLookupElement, procResult.getLookupElement());
chainLookupElement =
isHead ? procResult.getLookupElement() : new JavaChainLookupElement(chainLookupElement, procResult.getLookupElement());
}
if (newVariableClass != null) {
@@ -99,12 +102,8 @@ public class MethodsChainLookupRangingHelper {
}
final ChainRelevance relevance =
new ChainRelevance(chainSize,
lastMethodWeight,
unreachableParametersCount,
notMatchedStringVars,
hasCallingVariableInContext,
isFirstMethodStatic);
new ChainRelevance(chainSize, lastMethodWeight, unreachableParametersCount, notMatchedStringVars, hasCallingVariableInContext,
isFirstMethodStatic, matchedParametersInContext);
return new WeightableChainLookupElement(chainLookupElement, relevance);
}
@@ -119,6 +118,7 @@ public class MethodsChainLookupRangingHelper {
final NullableNotNullManager nullableNotNullManager) {
int unreachableParametersCount = 0;
int notMatchedStringVars = 0;
int matchedParametersInContext = 0;
boolean hasCallingVariableInContext = false;
boolean introduceNewVariable = false;
final PsiParameterList parameterList = method.getParameterList();
@@ -142,18 +142,21 @@ public class MethodsChainLookupRangingHelper {
final PsiVariable contextVariable = ContainerUtil.getFirstItem(contextVariables, null);
if (contextVariable != null) {
if (contextVariables.size() == 1) parametersMap.put(i, new VariableSubLookupElement(contextVariable));
matchedParametersInContext++;
continue;
}
final Collection<ContextRelevantVariableGetter> relevantVariablesGetters = context.getRelevantVariablesGetters(typeQName);
final ContextRelevantVariableGetter contextVariableGetter = ContainerUtil.getFirstItem(relevantVariablesGetters, null);
if (contextVariableGetter != null) {
if (relevantVariablesGetters.size() == 1) parametersMap.put(i, contextVariableGetter.createSubLookupElement());
matchedParametersInContext++;
continue;
}
final Collection<PsiMethod> containingClassMethods = context.getContainingClassMethods(typeQName);
final PsiMethod contextRelevantGetter = ContainerUtil.getFirstItem(containingClassMethods, null);
if (contextRelevantGetter != null) {
if (containingClassMethods.size() == 1) parametersMap.put(i, new GetterLookupSubLookupElement(method.getName()));
matchedParametersInContext++;
continue;
}
final ContextRelevantStaticMethod contextRelevantStaticMethod =
@@ -163,6 +166,7 @@ public class MethodsChainLookupRangingHelper {
// In most cases it is not really relevant
//
//parametersMap.put(i, contextRelevantStaticMethod.createLookupElement());
matchedParametersInContext++;
continue;
}
if (!nullableNotNullManager.isNullable(parameter, true)) {
@@ -174,6 +178,7 @@ public class MethodsChainLookupRangingHelper {
final LookupElement lookupElement;
if (isHeadMethod) {
if (method.hasModifierProperty(PsiModifier.STATIC)) {
hasCallingVariableInContext = true;
lookupElement = createLookupElement(method, parametersMap);
}
else if (method.isConstructor()) {
@@ -212,7 +217,12 @@ public class MethodsChainLookupRangingHelper {
else {
lookupElement = createLookupElement(method, parametersMap);
}
return new MethodProcResult(lookupElement, unreachableParametersCount, notMatchedStringVars, hasCallingVariableInContext, introduceNewVariable);
return new MethodProcResult(lookupElement,
unreachableParametersCount,
notMatchedStringVars,
hasCallingVariableInContext,
introduceNewVariable,
matchedParametersInContext);
}
private static class MethodProcResult {
@@ -221,17 +231,20 @@ public class MethodsChainLookupRangingHelper {
private final int myNotMatchedStringVars;
private final boolean myHasCallingVariableInContext;
private final boolean myIntroduceNewVariable;
private final int myMatchedParametersInContext;
private MethodProcResult(final LookupElement methodLookup,
final int unreachableParametersCount,
final int notMatchedStringVars,
final boolean hasCallingVariableInContext,
final boolean introduceNewVariable) {
final boolean introduceNewVariable,
final int matchedParametersInContext) {
myMethodLookup = methodLookup;
myUnreachableParametersCount = unreachableParametersCount;
myNotMatchedStringVars = notMatchedStringVars;
myHasCallingVariableInContext = hasCallingVariableInContext;
myIntroduceNewVariable = introduceNewVariable;
myMatchedParametersInContext = matchedParametersInContext;
}
private boolean isIntroduceNewVariable() {
@@ -253,6 +266,10 @@ public class MethodsChainLookupRangingHelper {
private int getNotMatchedStringVars() {
return myNotMatchedStringVars;
}
public int getMatchedParametersInContext() {
return myMatchedParametersInContext;
}
}
}
@@ -0,0 +1,26 @@
import java.jang.String;
class PsiManager {
public static PsiManager getInstance() {
return null;
}
}
interface PsiClass {
PsiManager getManager();
}
interface PsiMethod {
PsiClass getContainingClass();
}
interface PsiMethodCallExpression {
PsiMethod resolveMethod();
}
public class TestCompletion {
public void method() {
PsiManager m = <caret>
}
}
@@ -0,0 +1,69 @@
public class TestIndex {
public void m1() {
//12
PsiManager.getInstance();
PsiManager.getInstance();
PsiManager.getInstance();
PsiManager.getInstance();
PsiManager.getInstance();
PsiManager.getInstance();
PsiManager.getInstance();
PsiManager.getInstance();
PsiManager.getInstance();
PsiManager.getInstance();
PsiManager.getInstance();
PsiManager.getInstance();
}
public void m2(PsiClass psiClass) {
//3
psiClass.getManager();
psiClass.getManager();
psiClass.getManager();
}
public void m3(PsiMethod method) {
//14
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
method.getContainingClass();
}
public void m4(PsiMethodCallExpression psiMethodCallExpression) {
//5
psiMethodCallExpression.resolveMethod();
psiMethodCallExpression.resolveMethod();
psiMethodCallExpression.resolveMethod();
psiMethodCallExpression.resolveMethod();
psiMethodCallExpression.resolveMethod();
}
}
class PsiManager {
public static PsiManager getInstance() {
return null;
}
}
interface PsiClass {
PsiManager getManager();
}
interface PsiMethod {
PsiClass getContainingClass();
}
interface PsiMethodCallExpression {
PsiMethod resolveMethod();
}
@@ -0,0 +1,49 @@
import java.jang.String;
interface Project {
}
interface PsiFile {
}
interface VirtualFile {
}
interface Document {
}
class PsiManager {
static PsiManager getInstance(Project p) {
return null;
}
PsiFile findFile(VirtualFile f) {
return null;
}
}
class PsiDocumentManager {
static PsiDocumentManager getInstance(Project p) {
return null;
}
PsiFile getPsiFile(Document d) {
return null;
}
}
interface PsiFileFactory {
PsiFile createFileFromText(String s);
}
interface PsiClass {
PsiFile getContainingClass();
}
public class TestCompletion {
void m() {
PsiFileFactory f = null;
VirtualFile vf = null;
PsiFile file = <caret>
}
}
@@ -0,0 +1,76 @@
import java.lang.String;
public class TestIndex {
void m() {
PsiManager.getInstance(null).findFile(null);
PsiManager.getInstance(null).findFile(null);
PsiManager.getInstance(null).findFile(null);
PsiManager.getInstance(null).findFile(null);
}
void m2() {
PsiDocumentManager.getInstance(null).getPsiFile(null);
PsiDocumentManager.getInstance(null).getPsiFile(null);
PsiDocumentManager.getInstance(null).getPsiFile(null);
PsiDocumentManager.getInstance(null).getPsiFile(null);
}
void m3() {
PsiFileFactory f = null;
f.createFileFromText("");
f.createFileFromText("");
f.createFileFromText("");
f.createFileFromText("");
}
void m4() {
PsiClass c = null;
c.getContainingClass();
c.getContainingClass();
c.getContainingClass();
c.getContainingClass();
}
}
interface Project {
}
interface PsiFile {
}
interface VirtualFile {
}
interface Document {
}
class PsiManager {
static PsiManager getInstance(Project p) {
return null;
}
PsiFile findFile(VirtualFile f) {
return null;
}
}
class PsiDocumentManager {
static PsiDocumentManager getInstance(Project p) {
return null;
}
PsiFile getPsiFile(Document d) {
return null;
}
}
interface PsiFileFactory {
PsiFile createFileFromText(String s);
}
interface PsiClass {
PsiFile getContainingClass();
}
@@ -78,6 +78,10 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest {
assertAdvisorLookupElementEquals("getInstance().findFile().findElementAt", 0, 4, 3, 0, assertOneElement(doCompletion()));
}
public void _testChainsWithIndependentCallings() {
assertOneElement(doCompletion());
}
public void testMethodReturnsSubclassOfTargetClassNotShowed2() {
assertEmpty(doCompletion());
}
@@ -144,6 +148,15 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest {
assertEmpty(doCompletion());
}
public void testResultOrdering() {
final List<WeightableChainLookupElement> lookupElements = doCompletion();
assertSize(4, lookupElements);
assertLookupElementStringEquals(lookupElements.get(0), "f.createFileFromText");
assertLookupElementStringEquals(lookupElements.get(1), "getInstance().findFile");
assertLookupElementStringEquals(lookupElements.get(2), "getInstance().getPsiFile");
assertLookupElementStringEquals(lookupElements.get(3), "getContainingClass");
}
public void testResultRelevance() {
final List<WeightableChainLookupElement> weightableChainLookupElements = doCompletion();
assertEquals("e.getContainingClass", weightableChainLookupElements.get(0).getLookupString());
@@ -154,7 +167,7 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest {
final List<WeightableChainLookupElement> weightableChainLookupElements = doCompletion();
assertSize(2, weightableChainLookupElements);
assertEquals("e.getProject1", weightableChainLookupElements.get(0).getLookupString());
assertEquals("psiManager.getProject", weightableChainLookupElements.get(1).getLookupString());
assertEquals("getProject", weightableChainLookupElements.get(1).getLookupString());
}
public void testRenderingVariableInContextAndNotInContext() {