PY-47281 Re-use QualifiedNameBasedScope in PyQualifiedNameCompletionMatcher

GitOrigin-RevId: f8407f86e083bc36f1be80b6fa43348a350511f2
This commit is contained in:
Mikhail Golubev
2021-02-19 16:19:49 +00:00
committed by intellij-monorepo-bot
parent 363e6c68ec
commit df979257a6
@@ -6,12 +6,9 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.DelegatingGlobalSearchScope;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.psi.util.QualifiedName;
@@ -26,7 +23,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -46,7 +42,7 @@ public class PyQualifiedNameCompletionMatcher {
StubIndex stubIndex = StubIndex.getInstance();
Project project = Objects.requireNonNull(scope.getProject());
GlobalSearchScope moduleMatchingScope = new ModuleQualifiedNameMatchingScope(scope, matcher, project);
GlobalSearchScope moduleMatchingScope = scope.intersectWith(new ModuleQualifiedNameMatchingScope(matcher, project));
Set<QualifiedName> alreadySuggestedAttributes = new HashSet<>();
IndexLookupStats stats = new IndexLookupStats();
try {
@@ -60,7 +56,7 @@ public class PyQualifiedNameCompletionMatcher {
attributeName, project, moduleMatchingScope, idFilter, PyElement.class, element -> {
ProgressManager.checkCanceled();
VirtualFile vFile = element.getContainingFile().getVirtualFile();
QualifiedName moduleQualifiedName = ModuleQualifiedNameMatchingScope.restoreModuleQualifiedName(vFile, project);
QualifiedName moduleQualifiedName = findQualifiedNameInClosestRoot(vFile, project);
assert moduleQualifiedName != null : vFile;
QualifiedName canonicalImportPath = findCanonicalImportPath(element, moduleQualifiedName, currentFile);
QualifiedName importPath;
@@ -196,57 +192,32 @@ public class PyQualifiedNameCompletionMatcher {
}
}
private static class ModuleQualifiedNameMatchingScope extends DelegatingGlobalSearchScope {
private static class ModuleQualifiedNameMatchingScope extends QualifiedNameFinder.QualifiedNameBasedScope {
private final QualifiedNameMatcher myQualifiedNameMatcher;
private final Project myProject;
ModuleQualifiedNameMatchingScope(@NotNull GlobalSearchScope baseScope,
@NotNull QualifiedNameMatcher qualifiedNameMatcher,
@NotNull Project project) {
super(baseScope);
ModuleQualifiedNameMatchingScope(@NotNull QualifiedNameMatcher qualifiedNameMatcher, @NotNull Project project) {
super(project);
myQualifiedNameMatcher = qualifiedNameMatcher;
myProject = project;
}
@Override
public boolean contains(@NotNull VirtualFile file) {
if (!super.contains(file)) return false;
QualifiedName qualifiedName = restoreModuleQualifiedName(file, myProject);
if (qualifiedName == null) return false;
return myQualifiedNameMatcher.qualifierMatches(qualifiedName);
}
@Nullable
private static QualifiedName restoreModuleQualifiedName(@NotNull VirtualFile vFile, @NotNull Project project) {
ProjectFileIndex projectFileIndex = ProjectFileIndex.SERVICE.getInstance(project);
String fileName = vFile.getName();
VirtualFile nameAnchor = fileName.equals(PyNames.INIT_DOT_PY) || fileName.equals(PyNames.INIT_DOT_PYI) ? vFile.getParent() : vFile;
VirtualFile closestRoot = findClosestRoot(nameAnchor, projectFileIndex);
if (closestRoot == null) return null;
String relativePath = VfsUtilCore.getRelativePath(nameAnchor, closestRoot);
// A relative path can be empty in case of __init__.py directly inside a root.
if (relativePath == null || relativePath.isEmpty()) return null;
return convertPathToImportableQualifiedName(relativePath);
}
@Nullable
private static QualifiedName convertPathToImportableQualifiedName(@NotNull String relativePath) {
List<String> parts = StringUtil.split(relativePath, VfsUtilCore.VFS_SEPARATOR);
String fileName = parts.get(parts.size() - 1);
parts.set(parts.size() - 1, StringUtil.substringBeforeLast(fileName, "."));
if (ContainerUtil.exists(parts, p -> !PyNames.isIdentifier(p))) return null;
return QualifiedName.fromComponents(parts);
}
@Nullable
private static VirtualFile findClosestRoot(@NotNull VirtualFile vFile, @NotNull ProjectFileIndex projectFileIndex) {
VirtualFile sourceRoot = projectFileIndex.getSourceRootForFile(vFile);
if (sourceRoot != null) return sourceRoot;
VirtualFile contentRoot = projectFileIndex.getContentRootForFile(vFile);
if (contentRoot != null) return contentRoot;
VirtualFile libraryRoot = projectFileIndex.getClassRootForFile(vFile);
if (libraryRoot != null) return libraryRoot;
return null;
protected boolean containsQualifiedNameInRoot(@NotNull VirtualFile root, @NotNull QualifiedName qName) {
return ContainerUtil.all(qName.getComponents(), PyNames::isIdentifier) && myQualifiedNameMatcher.qualifierMatches(qName);
}
}
@Nullable
private static QualifiedName findQualifiedNameInClosestRoot(@NotNull VirtualFile file, @NotNull Project project) {
// TODO Come up with a better API for exposing these internals
Ref<QualifiedName> result = Ref.create();
//noinspection ResultOfMethodCallIgnored
new QualifiedNameFinder.QualifiedNameBasedScope(project) {
@Override
protected boolean containsQualifiedNameInRoot(@NotNull VirtualFile root, @NotNull QualifiedName qName) {
result.set(qName);
return true;
}
}.contains(file);
return result.get();
}
}