diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy index b3662e8dd134..d589d394798c 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy @@ -461,7 +461,7 @@ public class NormalCompletionTest extends LightFixtureCompletionTestCase { } public void testAtUnderClass() throws Throwable { - doTest(); + doTest('\n'); } public void testLocalClassName() throws Throwable { doTest(); } diff --git a/platform/lang-impl/src/com/intellij/ide/util/FileStructureDialog.java b/platform/lang-impl/src/com/intellij/ide/util/FileStructureDialog.java index 3d23bd496772..44bfb9cf4305 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/FileStructureDialog.java +++ b/platform/lang-impl/src/com/intellij/ide/util/FileStructureDialog.java @@ -46,6 +46,8 @@ import com.intellij.pom.Navigatable; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.MinusculeMatcher; +import com.intellij.psi.codeStyle.NameUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.*; import com.intellij.ui.docking.DockManager; @@ -488,6 +490,17 @@ public class FileStructureDialog extends DialogWrapper { } private static SpeedSearchComparator createSpeedSearchComparator() { - return new SpeedSearchComparator(false); + return new SpeedSearchComparator(false) { + @NotNull + @Override + protected MinusculeMatcher createMatcher(@NotNull String pattern) { + return createFileStructureMatcher(pattern); + } + }; + } + + @NotNull + public static MinusculeMatcher createFileStructureMatcher(@NotNull String pattern) { + return new MinusculeMatcher(pattern, NameUtil.MatchingCaseSensitivity.NONE, " ()"); } } diff --git a/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java b/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java index 9c182ddfd5c7..0b3997d7db21 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java +++ b/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java @@ -58,6 +58,7 @@ import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.MinusculeMatcher; import com.intellij.ui.*; import com.intellij.ui.popup.AbstractPopup; import com.intellij.ui.popup.PopupUpdateProcessor; @@ -190,7 +191,13 @@ public class FileStructurePopup implements Disposable, TreeActionsOwner { myTree.setCellRenderer(new NodeRenderer()); mySpeedSearch = new MyTreeSpeedSearch(); - mySpeedSearch.setComparator(new SpeedSearchComparator(false, true)); + mySpeedSearch.setComparator(new SpeedSearchComparator(false, true) { + @NotNull + @Override + protected MinusculeMatcher createMatcher(@NotNull String pattern) { + return FileStructureDialog.createFileStructureMatcher(pattern); + } + }); final FileStructurePopupFilter filter = new FileStructurePopupFilter(); myFilteringStructure = new FilteringTreeStructure(filter, myTreeStructure, ApplicationManager.getApplication().isUnitTestMode()); diff --git a/platform/platform-impl/src/com/intellij/ui/SpeedSearchComparator.java b/platform/platform-impl/src/com/intellij/ui/SpeedSearchComparator.java index 5b9bae7bb6d2..1ca215f0f84b 100644 --- a/platform/platform-impl/src/com/intellij/ui/SpeedSearchComparator.java +++ b/platform/platform-impl/src/com/intellij/ui/SpeedSearchComparator.java @@ -62,11 +62,16 @@ public class SpeedSearchComparator { if (!myShouldMatchFromTheBeginning && !pattern.startsWith("*")) { pattern = "*" + pattern; } - myMinusculeMatcher = new MinusculeMatcher(pattern, NameUtil.MatchingCaseSensitivity.NONE); + myMinusculeMatcher = createMatcher(pattern); } return myMinusculeMatcher; } + @NotNull + protected MinusculeMatcher createMatcher(@NotNull String pattern) { + return new MinusculeMatcher(pattern, NameUtil.MatchingCaseSensitivity.NONE); + } + public String getRecentSearchText() { return myRecentSearchText; } diff --git a/platform/platform-tests/testSrc/com/intellij/psi/util/NameUtilMatchingTest.groovy b/platform/platform-tests/testSrc/com/intellij/psi/util/NameUtilMatchingTest.groovy index 0e0231940761..57aff69321a6 100644 --- a/platform/platform-tests/testSrc/com/intellij/psi/util/NameUtilMatchingTest.groovy +++ b/platform/platform-tests/testSrc/com/intellij/psi/util/NameUtilMatchingTest.groovy @@ -15,6 +15,8 @@ */ package com.intellij.psi.util + +import com.intellij.ide.util.FileStructureDialog import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.codeStyle.MinusculeMatcher @@ -26,8 +28,9 @@ import com.intellij.util.ThrowableRunnable import com.intellij.util.containers.ContainerUtil import com.intellij.util.text.Matcher import groovy.transform.CompileStatic -import junit.framework.Assert import org.jetbrains.annotations.NonNls +import org.junit.Assert + /** * @author max * @author peter @@ -136,6 +139,13 @@ public class NameUtilMatchingTest extends UsefulTestCase { assertTrue(caseInsensitiveMatcher(" us").matches("getMyUsage")); } + public void "test filenames with dots and spaces"() { + assertMatches("Google Test.html", "Google Test Test.cc.html") + assertMatches("Google.html", "Google Test Test.cc.html") + assertMatches("Google .html", "Google Test Test.cc.html") + assertMatches("Google Test*.html", "Google Test Test.cc.html") + } + private static MinusculeMatcher caseInsensitiveMatcher(String pattern) { return NameUtil.buildMatcher(pattern, NameUtil.MatchingCaseSensitivity.NONE); } @@ -229,24 +239,28 @@ public class NameUtilMatchingTest extends UsefulTestCase { assertMatches("text*:sh", "textField:shouldChangeCharactersInRange:replacementString:"); } + private static MinusculeMatcher fileStructureMatcher(String pattern) { + FileStructureDialog.createFileStructureMatcher(pattern) + } + public void testFileStructure() { - assertDoesntMatch("hint", "height: int"); - assertMatches("Hint", "Height:int"); - assertDoesntMatch("Hint", "Height: int"); - assertMatches("hI", "Height: int"); + assert !fileStructureMatcher("hint").matches("height: int") + assert fileStructureMatcher("Hint").matches("Height:int") + assert !fileStructureMatcher("Hint").matches("Height: int") + assert fileStructureMatcher("hI"). matches("Height: int") - assertMatches("getColor", "getBackground(): Color"); - assertMatches("get color", "getBackground(): Color"); - assertDoesntMatch("getcolor", "getBackground(): Color"); + assert fileStructureMatcher("getColor"). matches("getBackground(): Color") + assert fileStructureMatcher("get color").matches("getBackground(): Color") + assert !fileStructureMatcher("getcolor"). matches("getBackground(): Color") - assertMatches("get()", "getBackground(): Color"); + assert fileStructureMatcher("get()").matches("getBackground(): Color") - assertMatches("setColor", "setBackground(Color): void"); - assertMatches("set color", "setBackground(Color): void"); - assertMatches("set Color", "setBackground(Color): void"); - assertMatches("set(color", "setBackground(Color): void"); - assertMatches("set(color)", "setBackground(Color): void"); - assertDoesntMatch("setcolor", "setBackground(Color): void"); + assert fileStructureMatcher("setColor"). matches("setBackground(Color): void") + assert fileStructureMatcher("set color"). matches("setBackground(Color): void") + assert fileStructureMatcher("set Color"). matches("setBackground(Color): void") + assert fileStructureMatcher("set(color"). matches("setBackground(Color): void") + assert fileStructureMatcher("set(color)").matches("setBackground(Color): void") + assert !fileStructureMatcher("setcolor"). matches("setBackground(Color): void") } public void testMiddleMatchingMinimumTwoConsecutiveLettersInWordMiddle() { @@ -369,7 +383,7 @@ public class NameUtilMatchingTest extends UsefulTestCase { assertMatches("ncdfoe", "NoClassDefFoundException"); assertMatches("fob", "FOO_BAR"); assertMatches("fo_b", "FOO_BAR"); - assertDoesntMatch("fob", "FOO BAR"); + assertMatches("fob", "FOO BAR"); assertMatches("fo b", "FOO BAR"); assertMatches("AACl", "AAClass"); assertMatches("ZZZ", "ZZZZZZZZZZ"); @@ -416,6 +430,7 @@ public class NameUtilMatchingTest extends UsefulTestCase { public void testSpecialSymbols() { assertMatches("a@b", "a@bc"); + assertDoesntMatch("*@in", "a int"); assertMatches("a/text", "a/Text"); assertMatches("a/text", "a/bbbText"); @@ -551,7 +566,7 @@ public class NameUtilMatchingTest extends UsefulTestCase { } public void testPreferBeforeSeparators() { - assertPreference("*point", "getLocation(): Point", "getPoint(): Point", NameUtil.MatchingCaseSensitivity.NONE); + assertPreference(fileStructureMatcher("*point"), "getLocation(): Point", "getPoint(): Point"); } public void testPreferNoWordSkipping() { @@ -588,7 +603,10 @@ public class NameUtilMatchingTest extends UsefulTestCase { @NonNls String less, @NonNls String more, NameUtil.MatchingCaseSensitivity sensitivity) { - MinusculeMatcher matcher = new MinusculeMatcher(pattern, sensitivity); + assertPreference(new MinusculeMatcher(pattern, sensitivity), less, more) + } + + private static void assertPreference(MinusculeMatcher matcher, String less, String more) { int iLess = matcher.matchingDegree(less); int iMore = matcher.matchingDegree(more); assertTrue(iLess + ">=" + iMore + "; " + less + ">=" + more, iLess < iMore); diff --git a/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java b/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java index 03fc8bde0055..6a1a46effe68 100644 --- a/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java +++ b/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java @@ -28,14 +28,12 @@ import java.util.BitSet; import java.util.Iterator; /** -* @author peter + * Tells whether a string matches a specific pattern. Allows for lowercase camel-hump matching. + * Used in navigation, code completion, speed search etc. + * + * @author peter */ public class MinusculeMatcher implements Matcher { - /** - * Lowercase humps don't work for parts separated by these characters - * Need either an explicit uppercase letter or the same separator character in prefix - */ - private static final String HARD_SEPARATORS = " ()"; private final ThreadLocal myMatchingState = new ThreadLocal() { @Override protected MatchingState initialValue() { @@ -44,6 +42,7 @@ public class MinusculeMatcher implements Matcher { }; private final char[] myPattern; + private final String myHardSeparators; private final NameUtil.MatchingCaseSensitivity myOptions; private final boolean myHasHumps; private final boolean myHasSeparators; @@ -55,9 +54,26 @@ public class MinusculeMatcher implements Matcher { private final char[] toLowerCase; private final boolean myHasWildCards; + /** + * Constructs a matcher by a given pattern. + * @param pattern the pattern + * @param options case sensitivity settings + */ public MinusculeMatcher(@NotNull String pattern, @NotNull NameUtil.MatchingCaseSensitivity options) { + this(pattern, options, ""); + } + + /** + * Constructs a matcher by a given pattern. + * @param pattern the pattern + * @param options case sensitivity settings + * @param hardSeparators A string of characters (empty by default). Lowercase humps don't work for parts separated by any of these characters. + * Need either an explicit uppercase letter or the same separator character in prefix + */ + public MinusculeMatcher(@NotNull String pattern, @NotNull NameUtil.MatchingCaseSensitivity options, @NotNull String hardSeparators) { myOptions = options; myPattern = StringUtil.trimEnd(pattern, "* ").toCharArray(); + myHardSeparators = hardSeparators; isLowerCase = new boolean[myPattern.length]; isUpperCase = new boolean[myPattern.length]; isWordSeparator = new boolean[myPattern.length]; @@ -80,7 +96,7 @@ public class MinusculeMatcher implements Matcher { } private static boolean isWordSeparator(char c) { - return Character.isWhitespace(c) || c == '_' || c == '-' || c == ':' || c == '+'; + return Character.isWhitespace(c) || c == '_' || c == '-' || c == ':' || c == '+' || c == '.'; } private static boolean isWordStart(String text, int i) { @@ -186,7 +202,7 @@ public class MinusculeMatcher implements Matcher { } int startIndex = first.getStartOffset(); - boolean afterSeparator = StringUtil.indexOfAny(name, HARD_SEPARATORS, 0, startIndex) >= 0; + boolean afterSeparator = StringUtil.indexOfAny(name, myHardSeparators, 0, startIndex) >= 0; boolean wordStart = startIndex == 0 || isWordStart(name, startIndex) && !isWordStart(name, startIndex - 1); boolean finalMatch = iterable.get(iterable.size() - 1).getEndOffset() == name.length(); @@ -293,7 +309,7 @@ public class MinusculeMatcher implements Matcher { return null; } // pattern humps are allowed to match in words separated by " ()", lowercase characters aren't - if (!allowSpecialChars && !myHasSeparators && !myHasHumps && StringUtil.containsAnyChar(name, HARD_SEPARATORS, nameIndex, nextOccurrence)) { + if (!allowSpecialChars && !myHasSeparators && !myHasHumps && StringUtil.containsAnyChar(name, myHardSeparators, nameIndex, nextOccurrence)) { return null; } // if the user has typed a dot, don't skip other dots between humps