IDEABKL-6585 Completion: suggest variants for mistyped pattern

This commit is contained in:
peter
2019-03-26 12:07:25 +01:00
parent dc2b8a5bd7
commit a3f7fc805c
9 changed files with 97 additions and 26 deletions
@@ -0,0 +1,5 @@
public class Util {
int goo() {
retru<caret>
}
}
@@ -139,6 +139,7 @@ public class KeywordCompletionTest extends LightCompletionTestCase {
public void testCharInAnnotatedParameter() { doTest(1, "char"); }
public void testReturnInTernary() { doTest(1, "return"); }
public void testReturnInRussian() { doTest(1, "return"); }
public void testReturnWithTypo() { doTest(1, "return"); }
public void testFinalAfterParameterAnno() { doTest(2, "final", "float", "class"); }
public void testFinalAfterParameterAnno2() { doTest(2, "final", "float", "class"); }
public void testFinalAfterCase() { doTest(3, "final", "float", "class"); }
@@ -142,7 +142,7 @@ public class Main {
public static void main(String[] args) {
PublicInterface i = PublicInterface.createPrivateImplementation();
i.getV<caret>x
i.getVa<caret>x
}
}'''
@@ -1907,4 +1907,11 @@ class Abc {
myFixture.type('O')
myFixture.assertPreferredCompletionItems 0, 'SubOther'
}
void "test correct typos"() {
myFixture.configureByText("a.java", "class MyClass { MyCals<caret> }")
myFixture.completeBasic()
myFixture.type('\n')
myFixture.checkResult("class MyClass { MyClass<caret> }")
}
}
@@ -145,7 +145,7 @@ public abstract class CompletionResultSet implements Consumer<LookupElement> {
if (stop) {
stopHere();
}
myCompletionService.getVariantsFromContributors(parameters, myContributor, new BatchConsumer<CompletionResult>() {
myCompletionService.getVariantsFromContributors(parameters, myContributor, getPrefixMatcher(), new BatchConsumer<CompletionResult>() {
@Override
public void startBatch() {
CompletionResultSet.this.startBatch();
@@ -1,6 +1,7 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.progress.ProgressManager;
@@ -13,6 +14,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* For completion FAQ, see {@link CompletionContributor}.
@@ -45,13 +47,19 @@ public abstract class CompletionService {
public void getVariantsFromContributors(final CompletionParameters parameters,
@Nullable final CompletionContributor from,
final Consumer<CompletionResult> consumer) {
getVariantsFromContributors(parameters, from, createMatcher(suggestPrefix(parameters), false), consumer);
}
protected void getVariantsFromContributors(CompletionParameters parameters,
@Nullable CompletionContributor from,
PrefixMatcher matcher, Consumer<CompletionResult> consumer) {
final List<CompletionContributor> contributors = CompletionContributor.forParameters(parameters);
for (int i = contributors.indexOf(from) + 1; i < contributors.size(); i++) {
ProgressManager.checkCanceled();
CompletionContributor contributor = contributors.get(i);
CompletionResultSet result = createResultSet(parameters, consumer, contributor);
CompletionResultSet result = createResultSet(parameters, consumer, contributor, matcher);
contributor.fillCompletionVariants(parameters, result);
if (result.isStopped()) {
return;
@@ -60,7 +68,13 @@ public abstract class CompletionService {
}
protected abstract CompletionResultSet createResultSet(CompletionParameters parameters, Consumer<CompletionResult> consumer,
@NotNull CompletionContributor contributor);
@NotNull CompletionContributor contributor, PrefixMatcher matcher);
protected abstract String suggestPrefix(CompletionParameters parameters);
@NotNull
protected abstract PrefixMatcher createMatcher(String prefix, boolean typoTolerant);
@Nullable
public abstract CompletionProcess getCurrentCompletion();
@@ -74,7 +88,9 @@ public abstract class CompletionService {
public void performCompletion(CompletionParameters parameters, Consumer<? super CompletionResult> consumer) {
final Set<LookupElement> lookupSet = ContainerUtil.newConcurrentSet();
getVariantsFromContributors(parameters, null, new BatchConsumer<CompletionResult>() {
AtomicBoolean typoTolerant = new AtomicBoolean();
BatchConsumer<CompletionResult> batchConsumer = new BatchConsumer<CompletionResult>() {
@Override
public void startBatch() {
if (consumer instanceof BatchConsumer) {
@@ -91,11 +107,20 @@ public abstract class CompletionService {
@Override
public void consume(CompletionResult result) {
if (typoTolerant.get() && result.getLookupElement().getAutoCompletionPolicy() != AutoCompletionPolicy.NEVER_AUTOCOMPLETE) {
result = result.withLookupElement(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(result.getLookupElement()));
}
if (lookupSet.add(result.getLookupElement())) {
consumer.consume(result);
}
}
});
};
String prefix = suggestPrefix(parameters);
getVariantsFromContributors(parameters, null, createMatcher(prefix, false), batchConsumer);
if (lookupSet.isEmpty() && prefix.length() > 2) {
typoTolerant.set(true);
getVariantsFromContributors(parameters, null, createMatcher(prefix, true), batchConsumer);
}
}
public abstract CompletionSorter defaultSorter(CompletionParameters parameters, PrefixMatcher matcher);
@@ -26,6 +26,7 @@ public class CamelHumpMatcher extends PrefixMatcher {
private final MinusculeMatcher myCaseInsensitiveMatcher;
private final boolean myCaseSensitive;
private static boolean ourForceStartMatching;
private final boolean myTypoTolerant;
public CamelHumpMatcher(@NotNull final String prefix) {
@@ -33,8 +34,13 @@ public class CamelHumpMatcher extends PrefixMatcher {
}
public CamelHumpMatcher(String prefix, boolean caseSensitive) {
this(prefix, caseSensitive, false);
}
CamelHumpMatcher(String prefix, boolean caseSensitive, boolean typoTolerant) {
super(prefix);
myCaseSensitive = caseSensitive;
myTypoTolerant = typoTolerant;
myMatcher = createMatcher(myCaseSensitive);
myCaseInsensitiveMatcher = createMatcher(false);
}
@@ -57,6 +63,10 @@ public class CamelHumpMatcher extends PrefixMatcher {
return false;
}
boolean isTypoTolerant() {
return myTypoTolerant;
}
private static int skipUnderscores(@NotNull String name) {
return CharArrayUtil.shiftForward(name, 0, "_");
}
@@ -110,24 +120,26 @@ public class CamelHumpMatcher extends PrefixMatcher {
return this;
}
return new CamelHumpMatcher(prefix, myCaseSensitive);
return new CamelHumpMatcher(prefix, myCaseSensitive, myTypoTolerant);
}
private MinusculeMatcher createMatcher(final boolean caseSensitive) {
String prefix = applyMiddleMatching(myPrefix);
if (!caseSensitive) {
return NameUtil.buildMatcher(prefix, NameUtil.MatchingCaseSensitivity.NONE);
NameUtil.MatcherBuilder builder = NameUtil.buildMatcher(prefix);
if (caseSensitive) {
int setting = CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE;
if (setting == CodeInsightSettings.FIRST_LETTER) {
builder = builder.withCaseSensitivity(NameUtil.MatchingCaseSensitivity.FIRST_LETTER);
}
else if (setting == CodeInsightSettings.ALL) {
builder = builder.withCaseSensitivity(NameUtil.MatchingCaseSensitivity.ALL);
}
}
switch (CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE) {
case CodeInsightSettings.NONE:
return NameUtil.buildMatcher(prefix, NameUtil.MatchingCaseSensitivity.NONE);
case CodeInsightSettings.FIRST_LETTER:
return NameUtil.buildMatcher(prefix, NameUtil.MatchingCaseSensitivity.FIRST_LETTER);
default:
return NameUtil.buildMatcher(prefix, NameUtil.MatchingCaseSensitivity.ALL);
if (myTypoTolerant) {
builder = builder.typoTolerant();
}
return builder.build();
}
public static String applyMiddleMatching(String prefix) {
@@ -74,17 +74,30 @@ public final class CompletionServiceImpl extends CompletionService {
}
@Override
protected CompletionResultSet createResultSet(CompletionParameters parameters, Consumer<CompletionResult> consumer,
@NotNull CompletionContributor contributor) {
protected String suggestPrefix(CompletionParameters parameters) {
final PsiElement position = parameters.getPosition();
final int offset = parameters.getOffset();
TextRange range = position.getTextRange();
assert range.containsOffset(offset) : position + "; " + offset + " not in " + range;
//noinspection deprecation
final String prefix = CompletionData.findPrefixStatic(position, offset);
CamelHumpMatcher matcher = new CamelHumpMatcher(prefix);
CompletionSorterImpl sorter = defaultSorter(parameters, matcher);
return new CompletionResultSetImpl(consumer, matcher, contributor, parameters, sorter, null);
return CompletionData.findPrefixStatic(position, offset);
}
@Override
@NotNull
protected PrefixMatcher createMatcher(String prefix, boolean typoTolerant) {
return createMatcher(prefix, true, typoTolerant);
}
@NotNull
private static CamelHumpMatcher createMatcher(String prefix, boolean caseSensitive, boolean typoTolerant) {
return new CamelHumpMatcher(prefix, caseSensitive, typoTolerant);
}
@Override
protected CompletionResultSet createResultSet(CompletionParameters parameters, Consumer<CompletionResult> consumer,
@NotNull CompletionContributor contributor, PrefixMatcher matcher) {
return new CompletionResultSetImpl(consumer, matcher, contributor, parameters, defaultSorter(parameters, matcher), null);
}
@Override
@@ -178,7 +191,9 @@ public final class CompletionServiceImpl extends CompletionService {
@NotNull
@Override
public CompletionResultSet caseInsensitive() {
return withPrefixMatcher(new CamelHumpMatcher(getPrefixMatcher().getPrefix(), false));
PrefixMatcher matcher = getPrefixMatcher();
boolean typoTolerant = matcher instanceof CamelHumpMatcher && ((CamelHumpMatcher)matcher).isTypoTolerant();
return withPrefixMatcher(createMatcher(matcher.getPrefix(), false, typoTolerant));
}
@Override
@@ -404,6 +404,7 @@ public class NameUtil {
private final String pattern;
private String separators = "";
private MatchingCaseSensitivity caseSensitivity = MatchingCaseSensitivity.NONE;
private boolean typoTolerant = Registry.is("ide.completion.typo.tolerance");
public MatcherBuilder(String pattern) {
this.pattern = pattern;
@@ -419,9 +420,14 @@ public class NameUtil {
return this;
}
public MatcherBuilder typoTolerant() {
this.typoTolerant = true;
return this;
}
public MinusculeMatcher build() {
return Registry.is("ide.completion.typo.tolerance") ? FixingLayoutTypoTolerantMatcher.create(pattern, caseSensitivity, separators)
: new FixingLayoutMatcher(pattern, caseSensitivity, separators);
return typoTolerant ? FixingLayoutTypoTolerantMatcher.create(pattern, caseSensitivity, separators)
: new FixingLayoutMatcher(pattern, caseSensitivity, separators);
}
}