diff --git a/java/java-tests/testData/codeInsight/completion/keywords/returnWithTypo.java b/java/java-tests/testData/codeInsight/completion/keywords/returnWithTypo.java new file mode 100644 index 000000000000..4588a4b10376 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/keywords/returnWithTypo.java @@ -0,0 +1,5 @@ +public class Util { + int goo() { + retru + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java index a744e8a3151e..1042e858b473 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java @@ -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"); } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy index 9ce72ae2ca05..bc2352785049 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionDfaTest.groovy @@ -142,7 +142,7 @@ public class Main { public static void main(String[] args) { PublicInterface i = PublicInterface.createPrivateImplementation(); - i.getVx + i.getVax } }''' diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.groovy index feb6b8640dad..5c5b13d8af40 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.groovy @@ -1907,4 +1907,11 @@ class Abc { myFixture.type('O') myFixture.assertPreferredCompletionItems 0, 'SubOther' } + + void "test correct typos"() { + myFixture.configureByText("a.java", "class MyClass { MyCals }") + myFixture.completeBasic() + myFixture.type('\n') + myFixture.checkResult("class MyClass { MyClass }") + } } diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionResultSet.java b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionResultSet.java index da12212069a3..826f41b99105 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionResultSet.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionResultSet.java @@ -145,7 +145,7 @@ public abstract class CompletionResultSet implements Consumer { if (stop) { stopHere(); } - myCompletionService.getVariantsFromContributors(parameters, myContributor, new BatchConsumer() { + myCompletionService.getVariantsFromContributors(parameters, myContributor, getPrefixMatcher(), new BatchConsumer() { @Override public void startBatch() { CompletionResultSet.this.startBatch(); diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java index bf162b9013a2..bfa389af8a38 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionService.java @@ -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 consumer) { + getVariantsFromContributors(parameters, from, createMatcher(suggestPrefix(parameters), false), consumer); + } + + protected void getVariantsFromContributors(CompletionParameters parameters, + @Nullable CompletionContributor from, + PrefixMatcher matcher, Consumer consumer) { final List 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 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 consumer) { final Set lookupSet = ContainerUtil.newConcurrentSet(); - getVariantsFromContributors(parameters, null, new BatchConsumer() { + AtomicBoolean typoTolerant = new AtomicBoolean(); + + BatchConsumer batchConsumer = new BatchConsumer() { @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); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java index 9455243efd0f..006eb42c456e 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java @@ -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) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java index d8bada603659..3fd9e71ff688 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java @@ -74,17 +74,30 @@ public final class CompletionServiceImpl extends CompletionService { } @Override - protected CompletionResultSet createResultSet(CompletionParameters parameters, Consumer 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 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 diff --git a/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java b/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java index e529263b6576..fadc217c867e 100644 --- a/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java +++ b/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java @@ -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); } }