diff --git a/java/java-tests/testData/fileStructure/filtering/Camel.tree b/java/java-tests/testData/fileStructure/filtering/Camel.tree index 0abeed943e4c..d3be89f6cebb 100644 --- a/java/java-tests/testData/fileStructure/filtering/Camel.tree +++ b/java/java-tests/testData/fileStructure/filtering/Camel.tree @@ -1,3 +1,5 @@ -Camel.java -Camel - [getSomeHead():void] \ No newline at end of file + [doSomethingWithMyHead():void] + getSomeArtHeader():void + getSomeHead():void \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/SecondSmartTypeCompletionTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/completion/SecondSmartTypeCompletionTest.java index f1095de8d88c..04cd51aca2ce 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/SecondSmartTypeCompletionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/SecondSmartTypeCompletionTest.java @@ -80,7 +80,11 @@ public class SecondSmartTypeCompletionTest extends LightCompletionTestCase { } public void testNoObjectMethodsAsFirstPart() throws Throwable { doTest(); } - public void testGetClassLoader() throws Throwable { doTest(); } + public void testGetClassLoader() throws Throwable { + configure(); + selectItem(myItems[0]); + checkResult(); + } public void testNewStaticProblem() throws Throwable { doTest(); } public void testChainingPerformance() throws Throwable { diff --git a/platform/platform-tests/testSrc/com/intellij/psi/util/NameUtilTest.java b/platform/platform-tests/testSrc/com/intellij/psi/util/NameUtilTest.java index 3922fe2e7b11..6381addfdbe1 100644 --- a/platform/platform-tests/testSrc/com/intellij/psi/util/NameUtilTest.java +++ b/platform/platform-tests/testSrc/com/intellij/psi/util/NameUtilTest.java @@ -51,6 +51,11 @@ public class NameUtilTest extends UsefulTestCase { assertMatches("AACl", "AAClass"); assertMatches("ZZZ", "ZZZZZZZZZZ"); } + + public void testSkipWords() { + assertMatches("nt", "NameUtilTest"); + assertDoesntMatch("ABCD", "AbstractButton.DISABLED_ICON_CHANGED_PROPERTY"); + } public void testSimpleCasesWithFirstLowercased() throws Exception { assertMatches("N", "nameUtilTest"); @@ -79,9 +84,9 @@ public class NameUtilTest extends UsefulTestCase { public void testXMLCompletion() throws Exception { assertDoesntMatch("N_T", "NameUtilTest"); - assertDoesntMatch("ORGS_ACC", "ORGS_POSITION_ACCOUNTABILITY"); - assertDoesntMatch("ORGS-ACC", "ORGS-POSITION_ACCOUNTABILITY"); - assertDoesntMatch("ORGS.ACC", "ORGS.POSITION_ACCOUNTABILITY"); + assertMatches("ORGS_ACC", "ORGS_POSITION_ACCOUNTABILITY"); + assertMatches("ORGS-ACC", "ORGS-POSITION_ACCOUNTABILITY"); + assertMatches("ORGS.ACC", "ORGS.POSITION_ACCOUNTABILITY"); } public void testStarFalsePositive() throws Exception { @@ -112,7 +117,7 @@ public class NameUtilTest extends UsefulTestCase { assertDoesntMatch("ARS.j", "activity_report_summary.xml"); assertDoesntMatch("ARS.j", "activity_report_summary_justsometingwrong.xml"); - assertDoesntMatch("foo.goo", "foo.bar.goo"); + assertMatches("foo.goo", "foo.bar.goo"); } public void testSpaceForAnyWordsInBetween() { @@ -168,8 +173,8 @@ public class NameUtilTest extends UsefulTestCase { public void testSkipDot() { assertMatches("ja", "jquery.autocomplete.js"); assertMatches("ja.js", "jquery.autocomplete.js"); - assertDoesntMatch("jajs", "jquery.autocomplete.js"); - assertDoesntMatch("j.ajs", "jquery.autocomplete.js"); + assertMatches("jajs", "jquery.autocomplete.js"); + assertMatches("j.ajs", "jquery.autocomplete.js"); } public void testNoExtension() { @@ -192,11 +197,11 @@ public class NameUtilTest extends UsefulTestCase { assertDoesntMatch("*inspection*.pro", "InspectionsInProgress.png"); } - public void testIgnoreLeadingUnderscore() throws Exception { - assertMatches("form", "_form.html.erb"); + public void testLeadingUnderscore() throws Exception { + assertDoesntMatch("form", "_form.html.erb"); assertMatches("_form", "_form.html.erb"); assertMatches("_form", "__form"); - assertFalse(NameUtil.buildMatcher("_form", 1, true, true, false).matches("__form")); + assertTrue(firstLetterMatcher("_form").matches("__form")); } public void testLowerCaseWords() throws Exception { @@ -208,6 +213,8 @@ public class NameUtilTest extends UsefulTestCase { } public void testObjectiveCCases() throws Exception { + assertMatches("h*:", "h:aaa"); + assertMatches("h:", "h:aaa"); assertMatches("text:sh", "textField:shouldChangeCharactersInRange:replacementString:"); assertMatches("abc", "aaa:bbb:ccc"); assertMatches("textField:sh", "textField:shouldChangeCharactersInRange:replacementString:"); @@ -293,7 +300,7 @@ public class NameUtilTest extends UsefulTestCase { assertDoesntMatch("foo", "fxoo"); assertMatches("foo", "fOo"); assertMatches("foo", "fxOo"); - assertDoesntMatch("foo", "fXOo"); + assertMatches("foo", "fXOo"); assertMatches("fOo", "foo"); assertMatches("fOo", "FaOaOaXXXX"); assertMatches("ncdfoe", "NoClassDefFoundException"); @@ -338,7 +345,7 @@ public class NameUtilTest extends UsefulTestCase { assertMatches("a@b", "a@bc"); assertMatches("a/text", "a/Text"); - assertDoesntMatch("a/text", "a/bbbText"); + assertMatches("a/text", "a/bbbText"); } public void testMinusculeFirstLetter() { @@ -459,7 +466,7 @@ public class NameUtilTest extends UsefulTestCase { for (String s : CollectionFactory.ar("*", "*i", "*a", "*u", "T", "ti", longName, longName.substring(0, 20))) { matching.add(new MinusculeMatcher(s, NameUtil.MatchingCaseSensitivity.NONE)); } - for (String s : CollectionFactory.ar("A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "ta")) { + for (String s : CollectionFactory.ar("A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "tag")) { nonMatching.add(new MinusculeMatcher(s, NameUtil.MatchingCaseSensitivity.NONE)); } diff --git a/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java b/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java index 28af50427c84..5ccd23873643 100644 --- a/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java +++ b/platform/util/src/com/intellij/psi/codeStyle/MinusculeMatcher.java @@ -26,6 +26,8 @@ import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.Iterator; +import static com.intellij.psi.codeStyle.NameUtil.MatchingCaseSensitivity.*; + /** * @author peter */ @@ -35,7 +37,7 @@ public class MinusculeMatcher implements Matcher { public MinusculeMatcher(String pattern, NameUtil.MatchingCaseSensitivity options) { myOptions = options; - myPattern = StringUtil.trimEnd(pattern, "* ").replaceAll(":", "\\*:").toCharArray(); + myPattern = StringUtil.trimEnd(pattern, "* ").toCharArray(); } @Nullable @@ -74,7 +76,7 @@ public class MinusculeMatcher implements Matcher { return null; } - if (myOptions == NameUtil.MatchingCaseSensitivity.ALL && name.charAt(nameIndex) != myPattern[patternIndex]) { + if (myOptions == ALL && name.charAt(nameIndex) != myPattern[patternIndex]) { return null; } @@ -89,14 +91,14 @@ public class MinusculeMatcher implements Matcher { } char p = myPattern[patternIndex + i]; char w = name.charAt(i + nameIndex); - if (lastUpper == i - 1 && NameUtil.isWordStart(p) && myOptions != NameUtil.MatchingCaseSensitivity.ALL) { + if (lastUpper == i - 1 && NameUtil.isWordStart(p) && myOptions != ALL) { if (p == w) { lastUpper = i; } p = StringUtil.toLowerCase(p); } - if (myOptions != NameUtil.MatchingCaseSensitivity.ALL) { + if (myOptions != ALL) { w = StringUtil.toLowerCase(w); } if (w != p) { @@ -122,8 +124,10 @@ public class MinusculeMatcher implements Matcher { @Nullable private FList matchAfterFragment(String name, int patternIndex, int nameIndex, int nextStart, int lastUpper, int matchLen) { boolean star = patternIndex + matchLen < myPattern.length && myPattern[patternIndex + matchLen] == '*'; - if (lastUpper >= 0) { - FList ranges = matchName(name, patternIndex + lastUpper + 1, star && matchLen == lastUpper ? nameIndex + lastUpper : nextStart); + if (lastUpper >= 1) { + int nextNameIndex = star && matchLen == lastUpper ? nameIndex + lastUpper : nextStart; + int nextPatternIndex = patternIndex + lastUpper + 1; + FList ranges = star ? matchName(name, nextPatternIndex, nextNameIndex) : skipWords(name, nextPatternIndex, nextNameIndex); if (ranges != null) { return prependRange(ranges, nameIndex, lastUpper + 1); } @@ -131,7 +135,7 @@ public class MinusculeMatcher implements Matcher { int trial = matchLen; while (trial > 0) { - FList ranges = matchName(name, patternIndex + trial, nextStart); + FList ranges = skipWords(name, patternIndex + trial, nextStart); if (ranges != null) { return prependRange(ranges, nameIndex, trial); } @@ -230,11 +234,17 @@ public class MinusculeMatcher implements Matcher { @Nullable private FList skipWords(String name, int patternIndex, int nameIndex) { - while (' ' == myPattern[patternIndex]) { + while (patternIndex < myPattern.length && ' ' == myPattern[patternIndex]) { patternIndex++; - if (patternIndex == myPattern.length) { - return null; - } + } + if (patternIndex == myPattern.length) { + return FList.emptyList(); + } + if (nameIndex == name.length()) { + return null; + } + if (myPattern[patternIndex] == '*') { + return skipChars(name, patternIndex, nameIndex, true); } if (nameIndex == 0 || NameUtil.isWordStart(name, nameIndex)) { @@ -244,7 +254,7 @@ public class MinusculeMatcher implements Matcher { } } - boolean separatorInPattern = NameUtil.isWordSeparator(myPattern[patternIndex]); + boolean separatorInPattern = !Character.isLetterOrDigit(myPattern[patternIndex]); int fromIndex = nameIndex; while (fromIndex < name.length()) { int next = separatorInPattern ? name.indexOf(myPattern[patternIndex], fromIndex) : NameUtil.nextWord(name, fromIndex); @@ -340,9 +350,124 @@ public class MinusculeMatcher implements Matcher { return myPattern.length == 0 ? Collections.emptyList() : null; } + if (true) { + return matchWildcards(name, 0, 0); + } + return matchName(name, 0, 0); } + @Nullable + private FList matchWildcards(String name, int patternIndex, int nameIndex) { + while (isWildcard(patternIndex)) { + patternIndex++; + } + boolean space = isPatternChar(patternIndex - 1, ' '); + if (patternIndex == myPattern.length) { + if (space && nameIndex != name.length() && (patternIndex < 2 || !NameUtil.isWordStart(myPattern[patternIndex - 2]))) { + return null; + } + return FList.emptyList(); + } + + char p = myPattern[patternIndex]; + if (isWildcard(patternIndex - 1)) { + { + FList ranges = matchFragment(name, patternIndex, nameIndex); + if (ranges != null) { + return ranges; + } + } + while (true) { + nameIndex = space ? indexOfWordStart(name, p, nameIndex) : StringUtil.indexOfIgnoreCase(name, p, nameIndex + 1); + if (nameIndex < 0) return null; + if (!Character.isUpperCase(p) || NameUtil.isWordStart(name, nameIndex)) { + FList ranges = matchFragment(name, patternIndex, nameIndex); + if (ranges != null) { + return ranges; + } + } + } + } + return matchFragment(name, patternIndex, nameIndex); + } + + @Nullable + private FList matchFragment(String name, int patternIndex, int nameIndex) { + if (!isFirstCharMatching(name, nameIndex, myPattern[patternIndex])) { + return null; + } + + int minFragment = isPatternChar(patternIndex - 1, '*') && Character.isLetterOrDigit(name.charAt(nameIndex)) && !NameUtil.isWordStart(name, nameIndex) ? 2 : 1; + int i = 1; + while (nameIndex + i < name.length() && + patternIndex + i < myPattern.length && + StringUtil.charsEqual(myPattern[patternIndex + i], name.charAt(nameIndex + i), myOptions != ALL)) { + if (Character.isUpperCase(myPattern[patternIndex + i])) { + if (i < minFragment) { + return null; + } + if (myPattern[patternIndex + i] != name.charAt(nameIndex + i)) { + int nextWordStart = indexOfWordStart(name, myPattern[patternIndex + i], nameIndex + i); + if (nextWordStart >= 0) { + FList ranges = matchWildcards(name, patternIndex + i, nextWordStart); + if (ranges != null) { + return prependRange(ranges, nameIndex, i); + } + } + } + } + i++; + } + + if (patternIndex + i >= myPattern.length) { + return i >= minFragment ? FList.emptyList().prepend(TextRange.from(nameIndex, i)) : null; + } + while (i >= minFragment) { + int nextWordStart = isWildcard(patternIndex + i) ? nameIndex + i : indexOfWordStart(name, myPattern[patternIndex + i], nameIndex + i); + if (nextWordStart >= 0) { + FList ranges = matchWildcards(name, patternIndex + i, nextWordStart); + if (ranges != null) { + return prependRange(ranges, nameIndex, i); + } + } + i--; + } + return null; + } + + private boolean isFirstCharMatching(String name, int nameIndex, char p) { + return nameIndex < name.length() && StringUtil.charsEqual(p, name.charAt(nameIndex), + myOptions == FIRST_LETTER && nameIndex > 0 || myOptions == NONE); + } + + private boolean isWildcard(int patternIndex) { + return isPatternChar(patternIndex, ' ') || isPatternChar(patternIndex, '*'); + } + private boolean isPatternChar(int patternIndex, char c) { + if (patternIndex < 0 || patternIndex >= myPattern.length) return false; + return myPattern[patternIndex] == c; + } + + private static int indexOfWordStart(String name, char p, int startFrom) { + if (p == '.' || NameUtil.isWordSeparator(p)) { + return StringUtil.indexOfIgnoreCase(name, p, startFrom + 1); + } + if (startFrom >= name.length()) { + return -1; + } + int nextWordStart = startFrom; + while (true) { + nextWordStart = NameUtil.nextWord(name, nextWordStart); + if (nextWordStart >= name.length()) { + return -1; + } + if (StringUtil.charsEqualIgnoreCase(p, name.charAt(nextWordStart))) { + return nextWordStart; + } + } + } + @Override public String toString() { return "MinusculeMatcher{" + diff --git a/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java b/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java index a7b578bd3642..4beabaf48406 100644 --- a/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java +++ b/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java @@ -1,415 +1,415 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.psi.codeStyle; - -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.util.text.StringUtilRt; -import com.intellij.util.ArrayUtil; -import com.intellij.util.Function; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -public class NameUtil { - private static final Function LOWERCASE_MAPPING = new Function() { - public String fun(final String s) { - return s.toLowerCase(); - } - }; - private static final int MAX_LENGTH = 40; - - private NameUtil() {} - - public static List nameToWordsLowerCase(String name){ - return ContainerUtil.map(nameToWords(name), LOWERCASE_MAPPING); - } - - public static String[] nameToWords(String name){ - ArrayList array = new ArrayList(); - int index = 0; - int wordStart; - - while(index < name.length()){ - wordStart = index; - int upperCaseCount = 0; - int lowerCaseCount = 0; - int digitCount = 0; - int specialCount = 0; - while(index < name.length()){ - char c = name.charAt(index); - if (Character.isDigit(c)){ - if (upperCaseCount > 0 || lowerCaseCount > 0 || specialCount > 0) break; - digitCount++; - } - else if (Character.isUpperCase(c)){ - if (lowerCaseCount > 0 || digitCount > 0 || specialCount > 0) break; - upperCaseCount++; - } - else if (Character.isLowerCase(c)){ - if (digitCount > 0 || specialCount > 0) break; - if (upperCaseCount > 1) { - index--; - break; - } - lowerCaseCount++; - } - else{ - if (upperCaseCount > 0 || lowerCaseCount > 0 || digitCount > 0) break; - specialCount++; - } - index++; - } - String word = name.substring(wordStart, index); - if (!StringUtil.isEmptyOrSpaces(word)) { - array.add(word); - } - } - return ArrayUtil.toStringArray(array); - } - - public static String buildRegexp(String pattern, int exactPrefixLen, boolean allowToUpper, boolean allowToLower) { - return buildRegexp(pattern, exactPrefixLen, allowToUpper, allowToLower, false, false); - } - - public static String buildRegexp(String pattern, - int exactPrefixLen, - boolean allowToUpper, - boolean allowToLower, - boolean lowerCaseWords, - boolean forCompletion) { - final int eol = pattern.indexOf('\n'); - if (eol != -1) { - pattern = pattern.substring(0, eol); - } - if (pattern.length() >= MAX_LENGTH) { - pattern = pattern.substring(0, MAX_LENGTH); - } - - @NonNls final StringBuilder buffer = new StringBuilder(); - boolean lastIsUppercase = false; - boolean prevIsUppercase = false; - final boolean endsWithSpace = !forCompletion && StringUtil.endsWithChar(pattern, ' '); - if (!forCompletion) { - pattern = pattern.trim(); - } - exactPrefixLen = Math.min(exactPrefixLen, pattern.length()); - /*final boolean uppercaseOnly = containsOnlyUppercaseLetters(pattern.substring(exactPrefixLen)); - if (uppercaseOnly) { - allowToLower = false; - }*/ - if (exactPrefixLen > 0) { - char c = pattern.charAt(exactPrefixLen - 1); - prevIsUppercase = Character.isUpperCase(c) || Character.isDigit(c); - } - - for (int i = 0; i != exactPrefixLen; ++i) { - final char c = pattern.charAt(i); - if (Character.isLetterOrDigit(c)) { - buffer.append(c); - } - else { - // for standard RegExp engine - // buffer.append("\\u"); - // buffer.append(Integer.toHexString(c + 0x20000).substring(1)); - - // for OROMATCHER RegExp engine - buffer.append("\\").append(c); - //buffer.append(Integer.toHexString(c + 0x20000).substring(2)); - } - } - - if (exactPrefixLen == 0) { - buffer.append("_*"); // ignore leading underscores - } - - boolean firstIdentifierLetter = (exactPrefixLen == 0); - //System.out.println("pattern = " + pattern); - for (int i = exactPrefixLen; i < pattern.length(); i++) { - final char c = pattern.charAt(i); - lastIsUppercase = false; - if (Character.isLetterOrDigit(c)) { - prevIsUppercase = false; - - // This logic allows to use uppercase letters only to catch the name like PDM for PsiDocumentManager - if (Character.isUpperCase(c) || Character.isDigit(c)) { - prevIsUppercase = true; - lastIsUppercase = true; - - buffer.append('('); - - if (!firstIdentifierLetter) { - buffer.append("[a-z\\s0-9\\$]*"); - } - - buffer.append(c); - if (allowToLower) { - buffer.append('|'); - buffer.append(Character.toLowerCase(c)); - } - if (!firstIdentifierLetter) { - buffer.append("|[A-Za-z\\s0-9\\$]*[_-]+["); - buffer.append(c); - buffer.append(Character.toLowerCase(c)); - buffer.append("]"); - } - buffer.append(')'); - } - else if (Character.isLowerCase(c) && allowToUpper) { - buffer.append('['); - buffer.append(c); - buffer.append(Character.toUpperCase(c)); - buffer.append(']'); - if (lowerCaseWords) { - buffer.append("([a-z\\s0-9\\$]*[-_]+)?"); - } - } - else { - buffer.append(c); - } - - firstIdentifierLetter = false; - } - else if (c == '*') { - buffer.append(".*"); - firstIdentifierLetter = true; - } - else if (c == '.') { - if (!firstIdentifierLetter) { - buffer.append("[a-z\\s0-9\\$]*\\."); - } - else { - buffer.append("\\."); - } - firstIdentifierLetter = true; - } - else if (c == ' ') { - buffer.append("([a-z\\s0-9\\$_-]*[\\ _-]+)+"); - firstIdentifierLetter = true; - } - else { - if (c == ':' || prevIsUppercase) { - buffer.append("[A-Za-z\\s0-9\\$]*"); - } - - firstIdentifierLetter = true; - // for standard RegExp engine - // buffer.append("\\u"); - // buffer.append(Integer.toHexString(c + 0x20000).substring(1)); - - // for OROMATCHER RegExp engine - buffer.append("\\").append(c); - //buffer.append(Integer.toHexString(c + 0x20000).substring(3)); - } - } - - if (!endsWithSpace) { - buffer.append(".*"); - } - else if (lastIsUppercase) { - buffer.append("[a-z\\s0-9\\$]*"); - } - - //System.out.println("rx = " + buffer.toString()); - return buffer.toString(); - } - - /** - * Splits an identifier into words, separated with underscores or upper-case characters - * (camel-case). - * - * @param name the identifier to split. - * @return the array of strings into which the identifier has been split. - */ - public static String[] splitNameIntoWords(@NotNull String name) { - final String[] underlineDelimited = name.split("_"); - List result = new ArrayList(); - for (String word : underlineDelimited) { - addAllWords(word, result); - } - return ArrayUtil.toStringArray(result); - } - - public static List getSuggestionsByName(String name, - String prefix, - String suffix, - boolean upperCaseStyle, - boolean preferLongerNames, boolean isArray) { - ArrayList answer = new ArrayList(); - String[] words = nameToWords(name); - - for (int step = 0; step < words.length; step++) { - int wordCount = preferLongerNames ? words.length - step : step + 1; - - String startWord = words[words.length - wordCount]; - char c = startWord.charAt(0); - if( c == '_' || !Character.isJavaIdentifierStart( c ) ) - { - continue; - } - - answer.add(compoundSuggestion(prefix, upperCaseStyle, words, wordCount, startWord, c, isArray, false) + suffix); - answer.add(compoundSuggestion(prefix, upperCaseStyle, words, wordCount, startWord, c, isArray, true) + suffix); - } - return answer; - } - - private static String compoundSuggestion(String prefix, - boolean upperCaseStyle, - String[] words, - int wordCount, - String startWord, - char c, - boolean isArray, - boolean skip_) { - StringBuilder buffer = new StringBuilder(); - - buffer.append(prefix); - - if (upperCaseStyle) { - startWord = StringUtilRt.toUpperCase(startWord); - } - else { - if (prefix.length() == 0 || StringUtil.endsWithChar(prefix, '_')) { - startWord = startWord.toLowerCase(); - } - else { - startWord = Character.toUpperCase(c) + startWord.substring(1); - } - } - buffer.append(startWord); - - for (int i = words.length - wordCount + 1; i < words.length; i++) { - String word = words[i]; - String prevWord = words[i - 1]; - if (upperCaseStyle) { - word = StringUtilRt.toUpperCase(word); - if (prevWord.charAt(prevWord.length() - 1) != '_' && word.charAt(0) != '_') { - word = "_" + word; - } - } - else { - if (prevWord.charAt(prevWord.length() - 1) == '_') { - word = word.toLowerCase(); - } - - if (skip_) { - if (word.equals("_")) continue; - if (prevWord.equals("_")) { - word = StringUtil.capitalize(word); - } - } - } - buffer.append(word); - } - - String suggestion = buffer.toString(); - if (isArray) { - suggestion = StringUtil.pluralize(suggestion); - if (upperCaseStyle) { - suggestion = StringUtilRt.toUpperCase(suggestion); - } - } - return suggestion; - } - - static boolean isWordStart(String text, int i) { - if (isWordStart(text.charAt(i))) { - return true; - } - if (!Character.isLetterOrDigit(text.charAt(i))) { - return false; - } - if (i > 0 && (isWordSeparator(text.charAt(i - 1)) || text.charAt(i - 1) == '.')) { - return true; - } - return false; - } - - static boolean isWordStart(char p) { - return Character.isUpperCase(p) || Character.isDigit(p); - } - - static int nextWord(String text, int start) { - if (!Character.isLetterOrDigit(text.charAt(start))) { - return start + 1; - } - - int i = start; - while (i < text.length() && isWordStart(text, i)) { - i++; - } - if (i > start + 1) { - if (i == text.length() || !Character.isLetterOrDigit(text.charAt(i))) { - return i; - } - return i - 1; - } - while (i < text.length() && !isWordStart(text, i) && Character.isLetterOrDigit(text.charAt(i))) { - i++; - } - return i; - } - - private static void addAllWords(String text, List result) { - int start = 0; - while (start < text.length()) { - int next = nextWord(text, start); - result.add(text.substring(start, next)); - start = next; - } - } - - static boolean isWordSeparator(char c) { - return Character.isWhitespace(c) || c == '_' || c == '-' || c == ':'; - } - - /** - * @deprecated use com.intellij.util.text.Matcher - */ - public interface Matcher { - boolean matches(String name); - } - - @SuppressWarnings("UnusedDeclaration") - @Deprecated - public static com.intellij.util.text.Matcher buildCompletionMatcher(String pattern, int exactPrefixLen, boolean allowToUpper, boolean allowToLower) { - MatchingCaseSensitivity options = !allowToLower && !allowToUpper ? MatchingCaseSensitivity.ALL : exactPrefixLen > 0 ? MatchingCaseSensitivity.FIRST_LETTER : MatchingCaseSensitivity.NONE; - return buildMatcher(pattern, options); - } - - public static com.intellij.util.text.Matcher buildMatcher(String pattern, int exactPrefixLen, boolean allowToUpper, boolean allowToLower) { - MatchingCaseSensitivity options = !allowToLower && !allowToUpper ? MatchingCaseSensitivity.ALL : exactPrefixLen > 0 ? MatchingCaseSensitivity.FIRST_LETTER : MatchingCaseSensitivity.NONE; - return buildMatcher(pattern, options); - } - - @SuppressWarnings("UnusedParameters") - @Deprecated - public static com.intellij.util.text.Matcher buildMatcher(String pattern, int exactPrefixLen, boolean allowToUpper, boolean allowToLower, boolean lowerCaseWords) { - MatchingCaseSensitivity options = !allowToLower && !allowToUpper ? MatchingCaseSensitivity.ALL : exactPrefixLen > 0 ? MatchingCaseSensitivity.FIRST_LETTER : MatchingCaseSensitivity.NONE; - return buildMatcher(pattern, options); - } - - public static MinusculeMatcher buildMatcher(final String pattern, MatchingCaseSensitivity options) { - return new MinusculeMatcher(pattern, options); - } - - public enum MatchingCaseSensitivity { - NONE, FIRST_LETTER, ALL - } -} +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.psi.codeStyle; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.util.text.StringUtilRt; +import com.intellij.util.ArrayUtil; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class NameUtil { + private static final Function LOWERCASE_MAPPING = new Function() { + public String fun(final String s) { + return s.toLowerCase(); + } + }; + private static final int MAX_LENGTH = 40; + + private NameUtil() {} + + public static List nameToWordsLowerCase(String name){ + return ContainerUtil.map(nameToWords(name), LOWERCASE_MAPPING); + } + + public static String[] nameToWords(String name){ + ArrayList array = new ArrayList(); + int index = 0; + int wordStart; + + while(index < name.length()){ + wordStart = index; + int upperCaseCount = 0; + int lowerCaseCount = 0; + int digitCount = 0; + int specialCount = 0; + while(index < name.length()){ + char c = name.charAt(index); + if (Character.isDigit(c)){ + if (upperCaseCount > 0 || lowerCaseCount > 0 || specialCount > 0) break; + digitCount++; + } + else if (Character.isUpperCase(c)){ + if (lowerCaseCount > 0 || digitCount > 0 || specialCount > 0) break; + upperCaseCount++; + } + else if (Character.isLowerCase(c)){ + if (digitCount > 0 || specialCount > 0) break; + if (upperCaseCount > 1) { + index--; + break; + } + lowerCaseCount++; + } + else{ + if (upperCaseCount > 0 || lowerCaseCount > 0 || digitCount > 0) break; + specialCount++; + } + index++; + } + String word = name.substring(wordStart, index); + if (!StringUtil.isEmptyOrSpaces(word)) { + array.add(word); + } + } + return ArrayUtil.toStringArray(array); + } + + public static String buildRegexp(String pattern, int exactPrefixLen, boolean allowToUpper, boolean allowToLower) { + return buildRegexp(pattern, exactPrefixLen, allowToUpper, allowToLower, false, false); + } + + public static String buildRegexp(String pattern, + int exactPrefixLen, + boolean allowToUpper, + boolean allowToLower, + boolean lowerCaseWords, + boolean forCompletion) { + final int eol = pattern.indexOf('\n'); + if (eol != -1) { + pattern = pattern.substring(0, eol); + } + if (pattern.length() >= MAX_LENGTH) { + pattern = pattern.substring(0, MAX_LENGTH); + } + + @NonNls final StringBuilder buffer = new StringBuilder(); + boolean lastIsUppercase = false; + boolean prevIsUppercase = false; + final boolean endsWithSpace = !forCompletion && StringUtil.endsWithChar(pattern, ' '); + if (!forCompletion) { + pattern = pattern.trim(); + } + exactPrefixLen = Math.min(exactPrefixLen, pattern.length()); + /*final boolean uppercaseOnly = containsOnlyUppercaseLetters(pattern.substring(exactPrefixLen)); + if (uppercaseOnly) { + allowToLower = false; + }*/ + if (exactPrefixLen > 0) { + char c = pattern.charAt(exactPrefixLen - 1); + prevIsUppercase = Character.isUpperCase(c) || Character.isDigit(c); + } + + for (int i = 0; i != exactPrefixLen; ++i) { + final char c = pattern.charAt(i); + if (Character.isLetterOrDigit(c)) { + buffer.append(c); + } + else { + // for standard RegExp engine + // buffer.append("\\u"); + // buffer.append(Integer.toHexString(c + 0x20000).substring(1)); + + // for OROMATCHER RegExp engine + buffer.append("\\").append(c); + //buffer.append(Integer.toHexString(c + 0x20000).substring(2)); + } + } + + if (exactPrefixLen == 0) { + buffer.append("_*"); // ignore leading underscores + } + + boolean firstIdentifierLetter = (exactPrefixLen == 0); + //System.out.println("pattern = " + pattern); + for (int i = exactPrefixLen; i < pattern.length(); i++) { + final char c = pattern.charAt(i); + lastIsUppercase = false; + if (Character.isLetterOrDigit(c)) { + prevIsUppercase = false; + + // This logic allows to use uppercase letters only to catch the name like PDM for PsiDocumentManager + if (Character.isUpperCase(c) || Character.isDigit(c)) { + prevIsUppercase = true; + lastIsUppercase = true; + + buffer.append('('); + + if (!firstIdentifierLetter) { + buffer.append("[a-z\\s0-9\\$]*"); + } + + buffer.append(c); + if (allowToLower) { + buffer.append('|'); + buffer.append(Character.toLowerCase(c)); + } + if (!firstIdentifierLetter) { + buffer.append("|[A-Za-z\\s0-9\\$]*[_-]+["); + buffer.append(c); + buffer.append(Character.toLowerCase(c)); + buffer.append("]"); + } + buffer.append(')'); + } + else if (Character.isLowerCase(c) && allowToUpper) { + buffer.append('['); + buffer.append(c); + buffer.append(Character.toUpperCase(c)); + buffer.append(']'); + if (lowerCaseWords) { + buffer.append("([a-z\\s0-9\\$]*[-_]+)?"); + } + } + else { + buffer.append(c); + } + + firstIdentifierLetter = false; + } + else if (c == '*') { + buffer.append(".*"); + firstIdentifierLetter = true; + } + else if (c == '.') { + if (!firstIdentifierLetter) { + buffer.append("[a-z\\s0-9\\$]*\\."); + } + else { + buffer.append("\\."); + } + firstIdentifierLetter = true; + } + else if (c == ' ') { + buffer.append("([a-z\\s0-9\\$_-]*[\\ _-]+)+"); + firstIdentifierLetter = true; + } + else { + if (c == ':' || prevIsUppercase) { + buffer.append("[A-Za-z\\s0-9\\$]*"); + } + + firstIdentifierLetter = true; + // for standard RegExp engine + // buffer.append("\\u"); + // buffer.append(Integer.toHexString(c + 0x20000).substring(1)); + + // for OROMATCHER RegExp engine + buffer.append("\\").append(c); + //buffer.append(Integer.toHexString(c + 0x20000).substring(3)); + } + } + + if (!endsWithSpace) { + buffer.append(".*"); + } + else if (lastIsUppercase) { + buffer.append("[a-z\\s0-9\\$]*"); + } + + //System.out.println("rx = " + buffer.toString()); + return buffer.toString(); + } + + /** + * Splits an identifier into words, separated with underscores or upper-case characters + * (camel-case). + * + * @param name the identifier to split. + * @return the array of strings into which the identifier has been split. + */ + public static String[] splitNameIntoWords(@NotNull String name) { + final String[] underlineDelimited = name.split("_"); + List result = new ArrayList(); + for (String word : underlineDelimited) { + addAllWords(word, result); + } + return ArrayUtil.toStringArray(result); + } + + public static List getSuggestionsByName(String name, + String prefix, + String suffix, + boolean upperCaseStyle, + boolean preferLongerNames, boolean isArray) { + ArrayList answer = new ArrayList(); + String[] words = nameToWords(name); + + for (int step = 0; step < words.length; step++) { + int wordCount = preferLongerNames ? words.length - step : step + 1; + + String startWord = words[words.length - wordCount]; + char c = startWord.charAt(0); + if( c == '_' || !Character.isJavaIdentifierStart( c ) ) + { + continue; + } + + answer.add(compoundSuggestion(prefix, upperCaseStyle, words, wordCount, startWord, c, isArray, false) + suffix); + answer.add(compoundSuggestion(prefix, upperCaseStyle, words, wordCount, startWord, c, isArray, true) + suffix); + } + return answer; + } + + private static String compoundSuggestion(String prefix, + boolean upperCaseStyle, + String[] words, + int wordCount, + String startWord, + char c, + boolean isArray, + boolean skip_) { + StringBuilder buffer = new StringBuilder(); + + buffer.append(prefix); + + if (upperCaseStyle) { + startWord = StringUtilRt.toUpperCase(startWord); + } + else { + if (prefix.length() == 0 || StringUtil.endsWithChar(prefix, '_')) { + startWord = startWord.toLowerCase(); + } + else { + startWord = Character.toUpperCase(c) + startWord.substring(1); + } + } + buffer.append(startWord); + + for (int i = words.length - wordCount + 1; i < words.length; i++) { + String word = words[i]; + String prevWord = words[i - 1]; + if (upperCaseStyle) { + word = StringUtilRt.toUpperCase(word); + if (prevWord.charAt(prevWord.length() - 1) != '_' && word.charAt(0) != '_') { + word = "_" + word; + } + } + else { + if (prevWord.charAt(prevWord.length() - 1) == '_') { + word = word.toLowerCase(); + } + + if (skip_) { + if (word.equals("_")) continue; + if (prevWord.equals("_")) { + word = StringUtil.capitalize(word); + } + } + } + buffer.append(word); + } + + String suggestion = buffer.toString(); + if (isArray) { + suggestion = StringUtil.pluralize(suggestion); + if (upperCaseStyle) { + suggestion = StringUtilRt.toUpperCase(suggestion); + } + } + return suggestion; + } + + static boolean isWordStart(String text, int i) { + if (isWordStart(text.charAt(i))) { + return true; + } + if (!Character.isLetterOrDigit(text.charAt(i))) { + return false; + } + if (i == 0 || !Character.isLetterOrDigit(text.charAt(i - 1))) { + return true; + } + return false; + } + + static boolean isWordStart(char p) { + return Character.isUpperCase(p) || Character.isDigit(p); + } + + static int nextWord(String text, int start) { + if (!Character.isLetterOrDigit(text.charAt(start))) { + return start + 1; + } + + int i = start; + while (i < text.length() && isWordStart(text, i)) { + i++; + } + if (i > start + 1) { + if (i == text.length() || !Character.isLetterOrDigit(text.charAt(i))) { + return i; + } + return i - 1; + } + while (i < text.length() && !isWordStart(text, i) && Character.isLetterOrDigit(text.charAt(i))) { + i++; + } + return i; + } + + private static void addAllWords(String text, List result) { + int start = 0; + while (start < text.length()) { + int next = nextWord(text, start); + result.add(text.substring(start, next)); + start = next; + } + } + + static boolean isWordSeparator(char c) { + return Character.isWhitespace(c) || c == '_' || c == '-' || c == ':'; + } + + /** + * @deprecated use com.intellij.util.text.Matcher + */ + public interface Matcher { + boolean matches(String name); + } + + @SuppressWarnings("UnusedDeclaration") + @Deprecated + public static com.intellij.util.text.Matcher buildCompletionMatcher(String pattern, int exactPrefixLen, boolean allowToUpper, boolean allowToLower) { + MatchingCaseSensitivity options = !allowToLower && !allowToUpper ? MatchingCaseSensitivity.ALL : exactPrefixLen > 0 ? MatchingCaseSensitivity.FIRST_LETTER : MatchingCaseSensitivity.NONE; + return buildMatcher(pattern, options); + } + + public static com.intellij.util.text.Matcher buildMatcher(String pattern, int exactPrefixLen, boolean allowToUpper, boolean allowToLower) { + MatchingCaseSensitivity options = !allowToLower && !allowToUpper ? MatchingCaseSensitivity.ALL : exactPrefixLen > 0 ? MatchingCaseSensitivity.FIRST_LETTER : MatchingCaseSensitivity.NONE; + return buildMatcher(pattern, options); + } + + @SuppressWarnings("UnusedParameters") + @Deprecated + public static com.intellij.util.text.Matcher buildMatcher(String pattern, int exactPrefixLen, boolean allowToUpper, boolean allowToLower, boolean lowerCaseWords) { + MatchingCaseSensitivity options = !allowToLower && !allowToUpper ? MatchingCaseSensitivity.ALL : exactPrefixLen > 0 ? MatchingCaseSensitivity.FIRST_LETTER : MatchingCaseSensitivity.NONE; + return buildMatcher(pattern, options); + } + + public static MinusculeMatcher buildMatcher(final String pattern, MatchingCaseSensitivity options) { + return new MinusculeMatcher(pattern, options); + } + + public enum MatchingCaseSensitivity { + NONE, FIRST_LETTER, ALL + } +} diff --git a/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java b/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java index b1eeb1a7c490..87f9b214b213 100644 --- a/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java +++ b/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java @@ -334,7 +334,7 @@ public class AndroidLayoutDomTest extends AndroidDomTest { } public void testIdCompletion2() throws Throwable { - doTestCompletionVariants("idcompl2.xml", "@android:id/text1", "@android:id/text2"); + doTestCompletionVariants("idcompl2.xml", "@android:id/text1", "@android:id/text2", "@android:id/inputExtractEditText", "@android:id/startSelectingText", "@android:id/stopSelectingText"); } public void testIdHighlighting() throws Throwable { diff --git a/plugins/android/testSrc/org/jetbrains/android/dom/AndroidValueResourcesTest.java b/plugins/android/testSrc/org/jetbrains/android/dom/AndroidValueResourcesTest.java index 0cba392db1f8..0e118ae028ca 100644 --- a/plugins/android/testSrc/org/jetbrains/android/dom/AndroidValueResourcesTest.java +++ b/plugins/android/testSrc/org/jetbrains/android/dom/AndroidValueResourcesTest.java @@ -17,6 +17,7 @@ package org.jetbrains.android.dom; import com.android.sdklib.SdkConstants; +import com.intellij.codeInsight.completion.CompletionType; import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.intention.IntentionAction; @@ -72,7 +73,11 @@ public class AndroidValueResourcesTest extends AndroidDomTest { } public void testStyles2() throws Throwable { - toTestCompletion("styles2.xml", "styles2_after.xml"); + VirtualFile file = copyFileToProject("styles2.xml"); + myFixture.configureFromExistingVirtualFile(file); + myFixture.complete(CompletionType.BASIC); + myFixture.type('\n'); + myFixture.checkResultByFile(testFolder + '/' + "styles2_after.xml"); } public void testStyles3() throws Throwable { @@ -155,7 +160,11 @@ public class AndroidValueResourcesTest extends AndroidDomTest { } public void testStylesAttrNameWithoutPrefix() throws Throwable { - doTestCompletion(); + VirtualFile file = copyFileToProject(getTestName(true) + ".xml"); + myFixture.configureFromExistingVirtualFile(file); + myFixture.complete(CompletionType.BASIC); + myFixture.type('\n'); + myFixture.checkResultByFile(testFolder + '/' + getTestName(true) + "_after.xml"); } public void testMoreTypes() throws Throwable { diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/completion/GrCompletionWithLibraryTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/completion/GrCompletionWithLibraryTest.groovy index f1f8060200e9..11434b05046c 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/completion/GrCompletionWithLibraryTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/completion/GrCompletionWithLibraryTest.groovy @@ -76,7 +76,12 @@ class GrCompletionWithLibraryTest extends GroovyCompletionTestBase { public void testInjectMethodForMap() throws Throwable {doBasicTest();} public void testClosureDefaultParameterInEachMethod() throws Throwable {doBasicTest();} public void testEachMethodForRanges() throws Throwable {doBasicTest();} - public void testEachMethodForEnumRanges() throws Throwable {doBasicTest();} + public void testEachMethodForEnumRanges() throws Throwable { + myFixture.configureByFile(getTestName(false) + ".groovy") + myFixture.completeBasic() + myFixture.type('\n') + myFixture.checkResultByFile(getTestName(false) + "_after.groovy"); + } public void testPrintlnSpace() { checkCompletion 'print', 'l ', "println " } public void testHashCodeSpace() { checkCompletion 'if ("".h', ' ', 'if ("".hashCode() ' } diff --git a/plugins/groovy/testdata/groovy/oldCompletion/reference/grvy959.test b/plugins/groovy/testdata/groovy/oldCompletion/reference/grvy959.test index 7c47320f491f..54bcb6f50d4c 100644 --- a/plugins/groovy/testdata/groovy/oldCompletion/reference/grvy959.test +++ b/plugins/groovy/testdata/groovy/oldCompletion/reference/grvy959.test @@ -4,5 +4,6 @@ public String fooooo(){} MyClass.fo ----- +findIndexOf forName forName \ No newline at end of file diff --git a/xml/tests/src/com/intellij/codeInsight/completion/XmlCompletionTest.java b/xml/tests/src/com/intellij/codeInsight/completion/XmlCompletionTest.java index 9ed968fa7fb6..99fe3391f654 100644 --- a/xml/tests/src/com/intellij/codeInsight/completion/XmlCompletionTest.java +++ b/xml/tests/src/com/intellij/codeInsight/completion/XmlCompletionTest.java @@ -284,6 +284,7 @@ public class XmlCompletionTest extends LightCodeInsightFixtureTestCase { public void testEntityRefCompletion2() throws Exception { configureByFiles("28.xml", "28.ent"); complete(); + type('\n'); checkResultByFile("28_after.xml"); }