speedup Goto File on long patterns

This commit is contained in:
peter
2018-01-19 16:53:36 +01:00
parent 39b8271900
commit 686b2bcdd3
3 changed files with 61 additions and 16 deletions
@@ -439,6 +439,12 @@ class Intf {
assert gotoFile('objc/features/i') == [index, i18n]
}
void "test matching file in a matching directory"() {
def file = addEmptyFile("foo/index/index")
assert gotoFile('in') == [file, file.parent]
assert gotoFile('foin') == [file, file.parent]
}
private List<Object> gotoClass(String text, boolean checkboxState = false) {
return getPopupElements(new GotoClassModel2(project), text, checkboxState)
}
@@ -38,6 +38,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
/**
@@ -69,7 +71,7 @@ class DirectoryPathMatcher {
String nextPattern = myPattern + c;
if (c == '*' || c == '/' || c == ' ') return new DirectoryPathMatcher(myModel, myFiles, nextPattern);
List<Pair<VirtualFile, String>> files = myFiles != null ? myFiles : getProjectRoots(myModel);
List<Pair<VirtualFile, String>> files = getMatchingRoots();
List<Pair<VirtualFile, String>> nextRoots = new ArrayList<>();
MinusculeMatcher matcher = GotoFileItemProvider.getQualifiedNameMatcher(nextPattern);
@@ -77,22 +79,55 @@ class DirectoryPathMatcher {
if (containsChar(pair.second, c) && matcher.matches(pair.second)) {
nextRoots.add(pair);
} else {
processSubdirectoriesContaining(pair.first, c, sub -> {
processProjectFilesUnder(pair.first, sub -> {
if (!sub.isDirectory()) return false;
if (!containsChar(sub.getName(), c)) return true; //go deeper
String fullName = myModel.getFullName(sub);
if (fullName == null) return false;
if (fullName == null) return true;
fullName = FileUtil.toSystemIndependentName(fullName);
if (matcher.matches(fullName)) {
nextRoots.add(Pair.create(sub, fullName));
return true;
return false;
}
return false;
return true;
});
}
}
return nextRoots.isEmpty() ? null : new DirectoryPathMatcher(myModel, nextRoots, nextPattern);
}
/** return null if not cheap */
@Nullable
Set<String> findFileNamesMatchingIfCheap(char nextLetter, MinusculeMatcher matcher) {
List<Pair<VirtualFile, String>> files = getMatchingRoots();
Set<String> names = new HashSet<>();
AtomicInteger counter = new AtomicInteger();
BooleanSupplier tooMany = () -> counter.incrementAndGet() > 1000;
for (Pair<VirtualFile, String> pair : files) {
if (containsChar(pair.second, nextLetter) && matcher.matches(pair.second)) {
names.add(pair.first.getName());
}
processProjectFilesUnder(pair.first, sub -> {
counter.incrementAndGet();
if (tooMany.getAsBoolean()) return false;
String name = sub.getName();
if (containsChar(name, nextLetter) && matcher.matches(name)) {
names.add(name);
}
return true;
});
}
return tooMany.getAsBoolean() ? null : names;
}
@NotNull
private List<Pair<VirtualFile, String>> getMatchingRoots() {
return myFiles != null ? myFiles : getProjectRoots(myModel);
}
@NotNull
GlobalSearchScope narrowDown(@NotNull GlobalSearchScope fileSearchScope) {
if (myFiles == null) return fileSearchScope;
@@ -102,19 +137,13 @@ class DirectoryPathMatcher {
}
private void processSubdirectoriesContaining(VirtualFile root, char c, Processor<VirtualFile> consumer) {
private void processProjectFilesUnder(VirtualFile root, Processor<VirtualFile> consumer) {
GlobalSearchScope scope = GlobalSearchScope.allScope(myModel.getProject());
VfsUtilCore.visitChildrenRecursively(root, new VirtualFileVisitor<Object>() {
@Override
public boolean visitFile(@NotNull VirtualFile file) {
if (!file.isDirectory() || !scope.contains(file)) return false;
String name = file.getName();
if (containsChar(name, c) && consumer.process(file)) {
return false;
}
return true;
return scope.contains(file) && consumer.process(file);
}
@Nullable
@@ -275,7 +275,17 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider {
DirectoryPathMatcher dirMatcher) {
MinusculeMatcher fullMatcher = getQualifiedNameMatcher(sanitizedPattern);
List<List<String>> groups = groupByMatchingDegree(!pattern.startsWith("*"));
List<MatchResult> matchingNames = this.matchingNames;
if (patternSuffix.length() <= 3) {
// just enumerate over files
// otherwise there are too many names matching the remaining few letters, and querying index for all of them with a very constrained scope is expensive
Set<String> existingNames = dirMatcher.findFileNamesMatchingIfCheap(patternSuffix.charAt(0), matcher);
if (existingNames != null) {
matchingNames = ContainerUtil.filter(matchingNames, mr -> existingNames.contains(mr.elementName));
}
}
List<List<String>> groups = groupByMatchingDegree(!pattern.startsWith("*"), matchingNames);
for (List<String> group : groups) {
List<PsiFileSystemItem> files = getFilesMatchingPath(pattern, everywhere, fullMatcher, group, dirMatcher, indicator);
if (!files.isEmpty()) {
@@ -300,7 +310,7 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider {
return ContainerUtil.exists(groups, group -> !getFilesMatchingPath(pattern, true, fullMatcher, group, dirMatcher, indicator).isEmpty());
}
private List<List<String>> groupByMatchingDegree(boolean preferStartMatches) {
private List<List<String>> groupByMatchingDegree(boolean preferStartMatches, List<MatchResult> matchingNames) {
if (matchingNames.isEmpty()) return Collections.emptyList();
List<List<String>> groups = new ArrayList<>();