From ee23cde7a86e66a57e1536676a7f3f55b2ea22f6 Mon Sep 17 00:00:00 2001 From: Mikhail Sokolov Date: Mon, 23 Dec 2019 19:01:59 +0300 Subject: [PATCH] IDEA-229306 (IDEA-CR-56836) Goto Class should prefer start matches GitOrigin-RevId: 4481c7ac60053834905010e7d0ac69f495a0629d --- .../com/intellij/psi/codeStyle/NameUtil.java | 11 +++++- .../PreferStartMatchMatcherWrapper.java | 39 +++++++++++++++++++ .../ide/actions/GotoFileItemProvider.java | 23 ++++++++--- .../RecentFilesSEContributor.java | 7 +++- .../DefaultChooseByNameItemProvider.java | 33 +++++++++------- .../ide/util/gotoByName/MatchResult.java | 21 +++------- 6 files changed, 98 insertions(+), 36 deletions(-) create mode 100644 platform/analysis-api/src/com/intellij/psi/codeStyle/PreferStartMatchMatcherWrapper.java diff --git a/platform/analysis-api/src/com/intellij/psi/codeStyle/NameUtil.java b/platform/analysis-api/src/com/intellij/psi/codeStyle/NameUtil.java index 492328ba0b27..5240b5f63bbd 100644 --- a/platform/analysis-api/src/com/intellij/psi/codeStyle/NameUtil.java +++ b/platform/analysis-api/src/com/intellij/psi/codeStyle/NameUtil.java @@ -296,6 +296,7 @@ public final class NameUtil { private String separators = ""; private MatchingCaseSensitivity caseSensitivity = MatchingCaseSensitivity.NONE; private boolean typoTolerant = Registry.is("ide.completion.typo.tolerance"); + private boolean preferStartMatches = false; public MatcherBuilder(String pattern) { this.pattern = pattern; @@ -316,9 +317,15 @@ public final class NameUtil { return this; } + public MatcherBuilder preferringStartMatches() { + preferStartMatches = true; + return this; + } + public MinusculeMatcher build() { - return typoTolerant ? FixingLayoutTypoTolerantMatcher.create(pattern, caseSensitivity, separators) - : new FixingLayoutMatcher(pattern, caseSensitivity, separators); + MinusculeMatcher matcher = typoTolerant ? FixingLayoutTypoTolerantMatcher.create(pattern, caseSensitivity, separators) + : new FixingLayoutMatcher(pattern, caseSensitivity, separators); + return preferStartMatches ? new PreferStartMatchMatcherWrapper(matcher) : matcher; } } diff --git a/platform/analysis-api/src/com/intellij/psi/codeStyle/PreferStartMatchMatcherWrapper.java b/platform/analysis-api/src/com/intellij/psi/codeStyle/PreferStartMatchMatcherWrapper.java new file mode 100644 index 000000000000..ce19b5749a05 --- /dev/null +++ b/platform/analysis-api/src/com/intellij/psi/codeStyle/PreferStartMatchMatcherWrapper.java @@ -0,0 +1,39 @@ +// Copyright 2000-2019 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.psi.codeStyle; + +import com.intellij.openapi.util.TextRange; +import com.intellij.util.containers.FList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class PreferStartMatchMatcherWrapper extends MinusculeMatcher { + public static final int START_MATCH_WEIGHT = 10000; + @NotNull + private final MinusculeMatcher myDelegateMatcher; + + public PreferStartMatchMatcherWrapper(@NotNull MinusculeMatcher matcher) { + myDelegateMatcher = matcher; + } + + @Override + @NotNull + public String getPattern() { + return myDelegateMatcher.getPattern(); + } + + @Override + public FList matchingFragments(@NotNull String name) { + return myDelegateMatcher.matchingFragments(name); + } + + @Override + public int matchingDegree(@NotNull String name, + boolean valueStartCaseMatch, + @Nullable FList fragments) { + int degree = myDelegateMatcher.matchingDegree(name, valueStartCaseMatch, fragments); + if (fragments == null || fragments.isEmpty()) return degree; + + if (fragments.getHead().getStartOffset() == 0) degree += START_MATCH_WEIGHT; + return degree; + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoFileItemProvider.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoFileItemProvider.java index d92ccb199c82..399165087f69 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/GotoFileItemProvider.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoFileItemProvider.java @@ -144,7 +144,12 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider { @NotNull public static MinusculeMatcher getQualifiedNameMatcher(@NotNull String pattern) { - return NameUtil.buildMatcher("*" + StringUtil.replace(StringUtil.replace(pattern, "\\", "*\\*"), "/", "*/*"), NameUtil.MatchingCaseSensitivity.NONE); + pattern = "*" + StringUtil.replace(StringUtil.replace(pattern, "\\", "*\\*"), "/", "*/*"); + + return NameUtil.buildMatcher(pattern) + .withCaseSensitivity(NameUtil.MatchingCaseSensitivity.NONE) + .preferringStartMatches() + .build(); } @NotNull @@ -318,7 +323,15 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider { SuffixMatches(@NotNull String pattern, int from, @NotNull ProgressIndicator indicator) { patternSuffix = pattern.substring(from); - matcher = NameUtil.buildMatcher((from > 0 ? " " : "*") + patternSuffix, NameUtil.MatchingCaseSensitivity.NONE); + boolean preferStartMatches = from == 0 && !patternSuffix.startsWith("*"); + String matchPattern = (from > 0 ? " " : "*") + patternSuffix; + + NameUtil.MatcherBuilder builder = NameUtil.buildMatcher(matchPattern).withCaseSensitivity(NameUtil.MatchingCaseSensitivity.NONE); + if (preferStartMatches) { + builder.preferringStartMatches(); + } + + this.matcher = builder.build(); this.indicator = indicator; } @@ -355,7 +368,7 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider { } } - List> groups = groupByMatchingDegree(!parameters.getCompletePattern().startsWith("*"), matchingNames); + List> groups = groupByMatchingDegree(matchingNames); for (List group : groups) { JBIterable> filesMatchingPath = getFilesMatchingPath(parameters, group, dirMatcher, indicator); Iterable> matchedFiles = @@ -389,13 +402,13 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider { } @NotNull - private List> groupByMatchingDegree(boolean preferStartMatches, @NotNull List matchingNames) { + private List> groupByMatchingDegree(@NotNull List matchingNames) { Comparator comparator = (mr1, mr2) -> { boolean exactPrefix1 = StringUtil.startsWith(mr1.elementName, patternSuffix); boolean exactPrefix2 = StringUtil.startsWith(mr2.elementName, patternSuffix); if (exactPrefix1 && exactPrefix2) return 0; if (exactPrefix1 != exactPrefix2) return exactPrefix1 ? -1 : 1; - return mr1.compareDegrees(mr2, preferStartMatches); + return mr1.compareDegrees(mr2); }; return sortAndGroup(matchingNames, comparator); diff --git a/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/RecentFilesSEContributor.java b/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/RecentFilesSEContributor.java index 6d8cb4cdd2d4..1dbc6c54703e 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/RecentFilesSEContributor.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/RecentFilesSEContributor.java @@ -62,7 +62,12 @@ public class RecentFilesSEContributor extends FileSearchEverywhereContributor { } String searchString = filterControlSymbols(pattern); - MinusculeMatcher matcher = NameUtil.buildMatcher("*" + searchString).build(); + boolean preferStartMatches = !searchString.startsWith("*"); + NameUtil.MatcherBuilder builder = NameUtil.buildMatcher("*" + searchString); + if (preferStartMatches) { + builder = builder.preferringStartMatches(); + } + MinusculeMatcher matcher = builder.build(); List opened = Arrays.asList(FileEditorManager.getInstance(myProject).getSelectedFiles()); List history = Lists.reverse(EditorHistoryManager.getInstance(myProject).getFileList()); diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/DefaultChooseByNameItemProvider.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/DefaultChooseByNameItemProvider.java index 34b89131d28b..1adbdd70d34c 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/DefaultChooseByNameItemProvider.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/DefaultChooseByNameItemProvider.java @@ -114,7 +114,7 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP indicator.checkCanceled(); - return processByNames(base, everywhere, indicator, context, consumer, preferStartMatches, namesList, parameters); + return processByNames(base, everywhere, indicator, context, consumer, namesList, parameters); } @NotNull @@ -152,7 +152,7 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP @NotNull ProgressIndicator indicator, @Nullable Supplier allNamesProducer, String namePattern, boolean preferStartMatches) { - List namesList = getAllNames(base, parameters, indicator, allNamesProducer, namePattern); + List namesList = getAllNames(base, parameters, indicator, allNamesProducer, namePattern, preferStartMatches); indicator.checkCanceled(); String pattern = parameters.getCompletePattern(); @@ -160,7 +160,7 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP long started = System.currentTimeMillis(); namesList.sort(Comparator.comparing((MatchResult mr) -> !pattern.equalsIgnoreCase(mr.elementName)) .thenComparing((MatchResult mr) -> !namePattern.equalsIgnoreCase(mr.elementName)) - .thenComparing((mr1, mr2) -> mr1.compareWith(mr2, preferStartMatches))); + .thenComparing(Comparator.naturalOrder())); if (LOG.isDebugEnabled()) { LOG.debug("sorted:"+ (System.currentTimeMillis() - started) + ",results:" + namesList.size()); } @@ -172,7 +172,8 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP @NotNull FindSymbolParameters parameters, @NotNull ProgressIndicator indicator, @Nullable Supplier allNamesProducer, - @NotNull String namePattern) { + @NotNull String namePattern, + boolean preferStartMatches) { List namesList = new ArrayList<>(); final CollectConsumer collect = new SynchronizedCollectConsumer<>(namesList); @@ -182,7 +183,7 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP indicator.checkCanceled(); long started = System.currentTimeMillis(); String fullPattern = parameters.getCompletePattern(); - MinusculeMatcher matcher = buildPatternMatcher(namePattern); + MinusculeMatcher matcher = buildPatternMatcher(namePattern, preferStartMatches); ((ChooseByNameModelEx)model).processNames(sequence -> { indicator.checkCanceled(); MatchResult result = matches(base, fullPattern, matcher, sequence); @@ -202,7 +203,7 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP } String[] names = allNamesProducer.get(); long started = System.currentTimeMillis(); - processNamesByPattern(base, names, namePattern, indicator, collect); + processNamesByPattern(base, names, namePattern, indicator, collect, preferStartMatches); if (LOG.isDebugEnabled()) { LOG.debug("matched:"+ (System.currentTimeMillis() - started)+ "," + names.length); } @@ -225,7 +226,6 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP @NotNull ProgressIndicator indicator, @Nullable PsiElement context, @NotNull Processor> consumer, - boolean preferStartMatches, @NotNull List namesList, @NotNull FindSymbolParameters parameters) { List> sameNameElements = new SmartList<>(); @@ -239,7 +239,7 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP @Override public int compare(Pair o1, Pair o2) { int result = modelComparator.compare(o1.first, o2.first); - return result != 0 ? result : o1.second.compareWith(o2.second, preferStartMatches); + return result != 0 ? result : o1.second.compareTo(o2.second); } }; @@ -333,6 +333,7 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP @NotNull @Override public List filterNames(@NotNull ChooseByNameBase base, @NotNull String[] names, @NotNull String pattern) { + boolean preferStartMatches = pattern.startsWith("*"); pattern = convertToMatchingPattern(base, pattern); if (pattern.isEmpty() && !base.canShowListForEmptyPattern()) return Collections.emptyList(); @@ -341,7 +342,7 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP synchronized (filtered) { filtered.add(result.elementName); } - }); + }, preferStartMatches); synchronized (filtered) { return filtered; } @@ -351,8 +352,9 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP @NotNull final String[] names, @NotNull final String pattern, final ProgressIndicator indicator, - @NotNull final Consumer consumer) { - MinusculeMatcher matcher = buildPatternMatcher(pattern); + @NotNull final Consumer consumer, + boolean preferStartMatches) { + MinusculeMatcher matcher = buildPatternMatcher(pattern, preferStartMatches); Processor processor = name -> { ProgressManager.checkCanceled(); MatchResult result = matches(base, pattern, matcher, name); @@ -415,8 +417,13 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP } @NotNull - private static MinusculeMatcher buildPatternMatcher(@NotNull String pattern) { - return NameUtil.buildMatcher(pattern, NameUtil.MatchingCaseSensitivity.NONE); + private static MinusculeMatcher buildPatternMatcher(@NotNull String pattern, boolean preferStartMatches) { + NameUtil.MatcherBuilder builder = NameUtil.buildMatcher(pattern).withCaseSensitivity(NameUtil.MatchingCaseSensitivity.NONE); + if (preferStartMatches) { + builder = builder.preferringStartMatches(); + } + + return builder.build(); } protected static class PathProximityComparator implements Comparator { diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/MatchResult.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/MatchResult.java index 270f5253cf84..f992439678b6 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/MatchResult.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/MatchResult.java @@ -17,7 +17,7 @@ package com.intellij.ide.util.gotoByName; import org.jetbrains.annotations.NotNull; -public class MatchResult { +public class MatchResult implements Comparable { @NotNull public final String elementName; public final int matchingDegree; @@ -29,22 +29,13 @@ public class MatchResult { this.startMatch = startMatch; } - public int compareDegrees(@NotNull MatchResult that, boolean preferStartMatches) { - boolean start1 = startMatch; - boolean start2 = that.startMatch; - int startMatchResult = start1 == start2 ? 0 : start1 ? -1 : 1; - if (preferStartMatches && startMatchResult != 0) return startMatchResult; - - int degree1 = matchingDegree; - int degree2 = that.matchingDegree; - if (degree2 < degree1) return -1; - if (degree2 > degree1) return 1; - - return -startMatchResult; + public int compareDegrees(@NotNull MatchResult that) { + return Integer.compare(that.matchingDegree, matchingDegree); } - int compareWith(@NotNull MatchResult that, boolean preferStartMatches) { - int result = compareDegrees(that, preferStartMatches); + @Override + public int compareTo(@NotNull MatchResult that) { + int result = compareDegrees(that); return result != 0 ? result : elementName.compareToIgnoreCase(that.elementName); }