private static FList<TextRange> prependRange( @NotNull FList<TextRange> ranges, int from, int length) { TextRange head = ranges.getHead(); if (head != null && head.getStartOffset() == from + length) { return ranges.getTail().prepend(new TextRange(from, head.getEndOffset())); } return ranges.prepend(TextRange.from(from, length)); }
/** * After a wildcard (* or space), search for the first non-wildcard pattern character in the name * starting from nameIndex and try to {@link #matchFragment(String, int, int, * com.intellij.psi.codeStyle.MinusculeMatcher.MatchingState)} for it. */ @Nullable private FList<TextRange> matchWildcards( @NotNull String name, int patternIndex, int nameIndex, MatchingState matchingState) { if (nameIndex < 0) { return null; } if (!isWildcard(patternIndex)) { if (patternIndex == myPattern.length) { return FList.emptyList(); } return matchFragment(name, patternIndex, nameIndex, matchingState); } do { patternIndex++; } while (isWildcard(patternIndex)); if (patternIndex == myPattern.length) { boolean space = isPatternChar(patternIndex - 1, ' '); // the trailing space should match if the pattern ends with the last word part, or only its // first hump character if (space && nameIndex != name.length() && (patternIndex < 2 || !NameUtil.isWordStart(myPattern[patternIndex - 2]))) { int spaceIndex = name.indexOf(' ', nameIndex); if (spaceIndex >= 0) { return FList.<TextRange>emptyList().prepend(TextRange.from(spaceIndex, 1)); } return null; } return FList.emptyList(); } FList<TextRange> ranges = matchFragment(name, patternIndex, nameIndex, matchingState); if (ranges != null) { return ranges; } return matchSkippingWords(name, patternIndex, nameIndex, true, matchingState); }
public static boolean processParents( @NotNull Artifact artifact, @NotNull PackagingElementResolvingContext context, @NotNull ParentElementProcessor processor, int maxLevel) { return processParents( artifact, context, processor, FList.<Pair<Artifact, CompositePackagingElement<?>>>emptyList(), maxLevel, new HashSet<Artifact>()); }
/** * Attempts to match an alphanumeric sequence of pattern (starting at patternIndex) to some * continuous substring of name, starting from nameIndex. */ private FList<TextRange> doMatchFragments( String name, int patternIndex, int nameIndex, MatchingState matchingState) { if (!isFirstCharMatching(name, nameIndex, patternIndex)) { return null; } // middle matches have to be at least of length 3, to prevent too many irrelevant matches int minFragment = isPatternChar(patternIndex - 1, '*') && !isWildcard(patternIndex + 1) && Character.isLetterOrDigit(name.charAt(nameIndex)) && !isWordStart(name, nameIndex) ? 3 : 1; int i = 1; boolean ignoreCase = myOptions != NameUtil.MatchingCaseSensitivity.ALL; while (nameIndex + i < name.length() && patternIndex + i < myPattern.length && charEquals( myPattern[patternIndex + i], patternIndex + i, name.charAt(nameIndex + i), ignoreCase)) { if (isUpperCase[patternIndex + i] && myHasHumps) { if (i < minFragment) { return null; } // when an uppercase pattern letter matches lowercase name letter, try to find an uppercase // (better) match further in the name if (myPattern[patternIndex + i] != name.charAt(nameIndex + i)) { int nextWordStart = indexOfWordStart(name, patternIndex + i, nameIndex + i); FList<TextRange> ranges = matchWildcards(name, patternIndex + i, nextWordStart, matchingState); if (ranges != null) { return prependRange(ranges, nameIndex, i); } // at least three consecutive uppercase letters shouldn't match lowercase if (i > 1 && isUpperCase[patternIndex + i - 1] && isUpperCase[patternIndex + i - 2]) { // but if there's a lowercase after them, it can match (in case shift was released a bit // later) if (nameIndex + i + 1 == name.length() || patternIndex + i + 1 < myPattern.length && !isLowerCase[patternIndex + i + 1]) { return null; } } } } i++; } // we've found the longest fragment matching pattern and name if (patternIndex + i >= myPattern.length) { return FList.<TextRange>emptyList().prepend(TextRange.from(nameIndex, i)); } // try to match the remainder of pattern with the remainder of name // it may not succeed with the longest matching fragment, then try shorter matches while (i >= minFragment || isWildcard(patternIndex + i)) { FList<TextRange> ranges = isWildcard(patternIndex + i) ? matchWildcards(name, patternIndex + i, nameIndex + i, matchingState) : matchSkippingWords(name, patternIndex + i, nameIndex + i, false, matchingState); if (ranges != null) { return prependRange(ranges, nameIndex, i); } i--; } return null; }
public int matchingDegree(@NotNull String name) { FList<TextRange> iterable = matchingFragments(name); if (iterable == null) return Integer.MIN_VALUE; if (iterable.isEmpty()) return 0; final TextRange first = iterable.getHead(); boolean startMatch = first.getStartOffset() == 0; int matchingCase = 0; int p = -1; int integral = 0; // -sum of matching-char-count * hump-index over all matched humps; favors longer // fragments matching earlier words int humpIndex = 1; int nextHumpStart = 0; for (TextRange range : iterable) { for (int i = range.getStartOffset(); i < range.getEndOffset(); i++) { boolean isHumpStart = false; while (nextHumpStart <= i) { if (nextHumpStart == i) { isHumpStart = true; } nextHumpStart = NameUtil.nextWord(name, nextHumpStart); if (first != range) { humpIndex++; } } integral -= humpIndex; char c = name.charAt(i); p = StringUtil.indexOf(myPattern, c, p + 1, myPattern.length, false); if (p < 0) { break; } if (c == myPattern[p]) { if (isUpperCase[p]) matchingCase += 50; // strongly prefer user's uppercase matching uppercase: they made an effort to // press Shift else if (i == 0 && startMatch) matchingCase += 15; // the very first letter case distinguishes classes in Java etc else if (isHumpStart) matchingCase += 1; // if a lowercase matches lowercase hump start, that also means something } else if (isHumpStart) { // disfavor hump starts where pattern letter case doesn't match name case matchingCase -= 20; } } } int startIndex = first.getStartOffset(); boolean afterSeparator = StringUtil.indexOfAny(name, HARD_SEPARATORS, 0, startIndex) >= 0; boolean wordStart = startIndex == 0 || isWordStart(name, startIndex) && !isWordStart(name, startIndex - 1); boolean finalMatch = iterable.get(iterable.size() - 1).getEndOffset() == name.length(); return (wordStart ? 1000 : 0) + integral * 10 + matchingCase * (startMatch ? 10 : 1) + // in start matches, case is more important; in middle matches - fragment length // (integral) (afterSeparator ? 0 : 2) + (finalMatch ? 1 : 0); }