mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
speedup goto file without slashes by looking for files only in matching directories
This commit is contained in:
@@ -402,6 +402,7 @@ class Intf {
|
||||
assert (gotoClass('Ыекштп')[0] as PsiClass).name == 'String'
|
||||
assert (gotoSymbol('Ыекштп')[0] as PsiClass).name == 'String'
|
||||
assert (gotoFile('Ыекштп')[0] as PsiFile).name == 'String.class'
|
||||
assert (gotoFile('дфтпЫекштп')[0] as PsiFile).name == 'String.class'
|
||||
}
|
||||
|
||||
void "test prefer exact case match"() {
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.ide.actions;
|
||||
|
||||
import com.intellij.ide.util.gotoByName.GotoFileModel;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.roots.LibraryOrSdkOrderEntry;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.roots.OrderEntry;
|
||||
import com.intellij.openapi.roots.OrderRootType;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VfsUtilCore;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileVisitor;
|
||||
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
|
||||
import com.intellij.psi.codeStyle.MinusculeMatcher;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.GlobalSearchScopesCore;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
class DirectoryPathMatcher {
|
||||
@NotNull private final GotoFileModel myModel;
|
||||
@Nullable private final List<Pair<VirtualFile, String>> myFiles;
|
||||
@NotNull private final String myPattern;
|
||||
|
||||
private DirectoryPathMatcher(@NotNull GotoFileModel model, @Nullable List<Pair<VirtualFile, String>> files, @NotNull String pattern) {
|
||||
myModel = model;
|
||||
myFiles = files;
|
||||
myPattern = pattern;
|
||||
}
|
||||
|
||||
static DirectoryPathMatcher root(@NotNull GotoFileModel model) {
|
||||
return new DirectoryPathMatcher(model, null, "");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
DirectoryPathMatcher appendChar(char c) {
|
||||
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>> nextRoots = new ArrayList<>();
|
||||
MinusculeMatcher matcher = GotoFileItemProvider.getQualifiedNameMatcher(nextPattern);
|
||||
for (Pair<VirtualFile, String> pair : files) {
|
||||
if (containsChar(pair.second, c) && matcher.matches(pair.second)) {
|
||||
nextRoots.add(pair);
|
||||
} else {
|
||||
processSubdirectoriesContaining(pair.first, c, sub -> {
|
||||
String fullName = myModel.getFullName(sub);
|
||||
if (fullName != null && matcher.matches(fullName)) {
|
||||
nextRoots.add(Pair.create(sub, fullName));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return nextRoots.isEmpty() ? null : new DirectoryPathMatcher(myModel, nextRoots, nextPattern);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
GlobalSearchScope narrowDown(@NotNull GlobalSearchScope fileSearchScope) {
|
||||
if (myFiles == null) return fileSearchScope;
|
||||
|
||||
VirtualFile[] array = ContainerUtil.map2Array(myFiles, VirtualFile.class, p -> p.first);
|
||||
return fileSearchScope.intersectWith(GlobalSearchScopesCore.directoriesScope(myModel.getProject(), true, array));
|
||||
|
||||
}
|
||||
|
||||
private void processSubdirectoriesContaining(VirtualFile root, char c, 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;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) {
|
||||
return file instanceof NewVirtualFile ? ((NewVirtualFile)file).getCachedChildren() : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean containsChar(String name, char c) {
|
||||
return StringUtil.indexOfIgnoreCase(name, c, 0) >= 0;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<Pair<VirtualFile, String>> getProjectRoots(GotoFileModel model) {
|
||||
Set<VirtualFile> roots = new HashSet<>();
|
||||
for (Module module : ModuleManager.getInstance(model.getProject()).getModules()) {
|
||||
Collections.addAll(roots, ModuleRootManager.getInstance(module).getContentRoots());
|
||||
for (OrderEntry entry : ModuleRootManager.getInstance(module).getOrderEntries()) {
|
||||
if (entry instanceof LibraryOrSdkOrderEntry) {
|
||||
Collections.addAll(roots, entry.getFiles(OrderRootType.CLASSES));
|
||||
Collections.addAll(roots, entry.getFiles(OrderRootType.SOURCES));
|
||||
}
|
||||
}
|
||||
}
|
||||
return roots.stream()
|
||||
.map(root -> {
|
||||
VirtualFile top = model.getTopLevelRoot(root);
|
||||
return top != null ? top : root;
|
||||
})
|
||||
.distinct()
|
||||
.map(r -> Pair.create(r, StringUtil.notNullize(model.getFullName(r))))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package com.intellij.ide.actions;
|
||||
import com.intellij.ide.util.gotoByName.*;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectFileIndex;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
@@ -26,13 +27,18 @@ import com.intellij.openapi.util.io.FileUtilRt;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFileSystemItem;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.psi.codeStyle.FixingLayoutMatcher;
|
||||
import com.intellij.psi.codeStyle.MinusculeMatcher;
|
||||
import com.intellij.psi.codeStyle.NameUtil;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.FList;
|
||||
import com.intellij.util.indexing.FindSymbolParameters;
|
||||
import one.util.streamex.IntStreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -68,15 +74,30 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider {
|
||||
pattern = pattern.substring(1);
|
||||
}
|
||||
|
||||
if (!processItemsForPattern(base, pattern, everywhere, consumer)) {
|
||||
return false;
|
||||
}
|
||||
String fixed = FixingLayoutMatcher.fixLayout(pattern);
|
||||
return fixed == null || processItemsForPattern(base, fixed, everywhere, consumer);
|
||||
}
|
||||
|
||||
private boolean processItemsForPattern(@NotNull ChooseByNameBase base,
|
||||
@NotNull String pattern,
|
||||
boolean everywhere,
|
||||
@NotNull Processor<Object> consumer) {
|
||||
String sanitized = getSanitizedPattern(pattern, myModel);
|
||||
NameGrouper grouper = new NameGrouper(sanitized.substring(sanitized.lastIndexOf('/') + 1));
|
||||
myModel.processNames(name -> grouper.processName(name), true);
|
||||
DirectoryPathMatcher dirMatcher = DirectoryPathMatcher.root(myModel);
|
||||
while (true) {
|
||||
int index = grouper.index;
|
||||
SuffixMatches group = grouper.nextGroup(base);
|
||||
if (group == null) return true;
|
||||
if (!group.processFiles(pattern, sanitized, everywhere, consumer)) {
|
||||
if (!group.processFiles(pattern, sanitized, everywhere, consumer, dirMatcher)) {
|
||||
return false;
|
||||
}
|
||||
dirMatcher = dirMatcher.appendChar(sanitized.charAt(index));
|
||||
if (dirMatcher == null) return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,13 +137,17 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider {
|
||||
private List<PsiFileSystemItem> getFilesMatchingPath(@NotNull String pattern,
|
||||
boolean everywhere,
|
||||
MinusculeMatcher fullMatcher,
|
||||
List<String> fileNames) {
|
||||
List<String> fileNames,
|
||||
DirectoryPathMatcher dirMatcher) {
|
||||
GlobalSearchScope scope = dirMatcher.narrowDown(FindSymbolParameters.searchScopeFor(myProject, everywhere));
|
||||
FindSymbolParameters parameters = new FindSymbolParameters(pattern, pattern, scope, null);
|
||||
|
||||
List<PsiFileSystemItem> group = new ArrayList<>();
|
||||
Map<PsiFileSystemItem, Integer> qualifierMatchingDegrees = new HashMap<>();
|
||||
Map<PsiFileSystemItem, Integer> dirCloseness = new HashMap<>();
|
||||
Map<PsiFileSystemItem, Integer> nesting = new HashMap<>();
|
||||
for (String fileName : fileNames) {
|
||||
for (Object o : myModel.getElementsByName(fileName, everywhere, pattern)) {
|
||||
for (Object o : myModel.getElementsByName(fileName, parameters, new ProgressIndicatorBase())) {
|
||||
String fullName = myModel.getFullName(o);
|
||||
if (o instanceof PsiFileSystemItem && fullName != null) {
|
||||
fullName = FileUtilRt.toSystemIndependentName(fullName);
|
||||
@@ -175,7 +200,6 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider {
|
||||
|
||||
private class NameGrouper {
|
||||
private final String namePattern;
|
||||
@Nullable private final String alternativePattern;
|
||||
|
||||
/** Names placed into buckets where the index of bucket == {@link #findMatchStartingPosition} */
|
||||
private final List<List<String>> candidateNames;
|
||||
@@ -184,16 +208,12 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider {
|
||||
|
||||
NameGrouper(@NotNull String namePattern) {
|
||||
this.namePattern = namePattern;
|
||||
alternativePattern = FixingLayoutMatcher.fixLayout(namePattern);
|
||||
candidateNames = IntStreamEx.range(0, namePattern.length()).mapToObj(__ -> (List<String>)new ArrayList<String>()).toList();
|
||||
}
|
||||
|
||||
boolean processName(String name) {
|
||||
ProgressManager.checkCanceled();
|
||||
int position = findMatchStartingPosition(name, namePattern);
|
||||
if (position >= namePattern.length() && alternativePattern != null) {
|
||||
position = findMatchStartingPosition(name, alternativePattern);
|
||||
}
|
||||
if (position < namePattern.length()) {
|
||||
List<String> list = candidateNames.get(position);
|
||||
//noinspection SynchronizationOnLocalVariableOrMethodParameter
|
||||
@@ -247,13 +267,17 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean processFiles(@NotNull String pattern, String sanitizedPattern, boolean everywhere, Processor<? super PsiFileSystemItem> processor) {
|
||||
boolean processFiles(@NotNull String pattern,
|
||||
String sanitizedPattern,
|
||||
boolean everywhere,
|
||||
Processor<? super PsiFileSystemItem> processor,
|
||||
DirectoryPathMatcher dirMatcher) {
|
||||
MinusculeMatcher fullMatcher = getQualifiedNameMatcher(sanitizedPattern);
|
||||
|
||||
boolean empty = true;
|
||||
List<List<String>> groups = groupByMatchingDegree(!pattern.startsWith("*"));
|
||||
for (List<String> group : groups) {
|
||||
List<PsiFileSystemItem> files = getFilesMatchingPath(pattern, everywhere, fullMatcher, group);
|
||||
List<PsiFileSystemItem> files = getFilesMatchingPath(pattern, everywhere, fullMatcher, group, dirMatcher);
|
||||
empty &= files.isEmpty();
|
||||
if (!ContainerUtil.process(files, processor)) {
|
||||
return false;
|
||||
@@ -264,7 +288,7 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider {
|
||||
return false; // don't process expensive worse matches
|
||||
}
|
||||
|
||||
if (!everywhere && hasSuggestionsOutsideProject(pattern, fullMatcher, groups)) {
|
||||
if (!everywhere && hasSuggestionsOutsideProject(pattern, fullMatcher, groups, dirMatcher)) {
|
||||
// let the framework switch to searching outside project to display these well-matching suggestions
|
||||
// instead of worse-matching ones in project (that are very expensive to calculate)
|
||||
return false;
|
||||
@@ -272,8 +296,10 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasSuggestionsOutsideProject(@NotNull String pattern, MinusculeMatcher fullMatcher, List<List<String>> groups) {
|
||||
return ContainerUtil.exists(groups, group -> !getFilesMatchingPath(pattern, true, fullMatcher, group).isEmpty());
|
||||
private boolean hasSuggestionsOutsideProject(@NotNull String pattern,
|
||||
MinusculeMatcher fullMatcher,
|
||||
List<List<String>> groups, DirectoryPathMatcher dirMatcher) {
|
||||
return ContainerUtil.exists(groups, group -> !getFilesMatchingPath(pattern, true, fullMatcher, group, dirMatcher).isEmpty());
|
||||
}
|
||||
|
||||
private List<List<String>> groupByMatchingDegree(boolean preferStartMatches) {
|
||||
|
||||
+5
@@ -287,4 +287,9 @@ public abstract class ContributorsBasedGotoByModel implements ChooseByNameModelE
|
||||
public @NotNull String removeModelSpecificMarkup(@NotNull String pattern) {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Project getProject() {
|
||||
return myProject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import javax.swing.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Model for "Go to | File" action
|
||||
@@ -172,18 +173,20 @@ public class GotoFileModel extends FilteringGotoByModel<FileType> implements Dum
|
||||
@Override
|
||||
@Nullable
|
||||
public String getFullName(final Object element) {
|
||||
if (element instanceof PsiFileSystemItem) {
|
||||
VirtualFile file = ((PsiFileSystemItem)element).getVirtualFile();
|
||||
VirtualFile root = getTopLevelRoot(file);
|
||||
return root != null ? GotoFileCellRenderer.getRelativePathFromRoot(file, root)
|
||||
: GotoFileCellRenderer.getRelativePath(file, myProject);
|
||||
}
|
||||
|
||||
return getElementName(element);
|
||||
return element instanceof PsiFileSystemItem ? getFullName(((PsiFileSystemItem)element).getVirtualFile()) : getElementName(element);
|
||||
}
|
||||
|
||||
private VirtualFile getTopLevelRoot(VirtualFile file) {
|
||||
return JBIterable.generate(getContentRoot(file), r -> getContentRoot(r.getParent())).last();
|
||||
@Nullable
|
||||
public String getFullName(@NotNull VirtualFile file) {
|
||||
VirtualFile root = getTopLevelRoot(file);
|
||||
return root != null ? GotoFileCellRenderer.getRelativePathFromRoot(file, root)
|
||||
: GotoFileCellRenderer.getRelativePath(file, myProject);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public VirtualFile getTopLevelRoot(@NotNull VirtualFile file) {
|
||||
VirtualFile root = getContentRoot(file);
|
||||
return root == null ? null : JBIterable.generate(root, r -> getContentRoot(r.getParent())).last();
|
||||
}
|
||||
|
||||
private VirtualFile getContentRoot(@Nullable VirtualFile file) {
|
||||
|
||||
Reference in New Issue
Block a user