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);
  }
Пример #3
0
 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);
  }