Use indexes instead of slow directory search for script dependencies
#KT-30018 Comment
This commit is contained in:
+37
-69
@@ -22,19 +22,17 @@ import com.intellij.openapi.roots.impl.PackageDirectoryCache
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.NonClasspathClassFinder
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiClassOwner
|
||||
import com.intellij.psi.search.EverythingGlobalScope
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import org.jetbrains.kotlin.idea.caches.project.ScriptModuleSearchScope
|
||||
import org.jetbrains.kotlin.load.java.AbstractJavaClassFinder
|
||||
import org.jetbrains.kotlin.resolve.jvm.KotlinSafeClassFinder
|
||||
|
||||
|
||||
|
||||
class KotlinScriptDependenciesClassFinder(project: Project,
|
||||
private val scriptDependenciesManager: ScriptDependenciesManager
|
||||
class KotlinScriptDependenciesClassFinder(
|
||||
project: Project,
|
||||
private val scriptDependenciesManager: ScriptDependenciesManager
|
||||
) : NonClasspathClassFinder(project), KotlinSafeClassFinder {
|
||||
|
||||
private val myCaches by lazy {
|
||||
@@ -47,11 +45,11 @@ class KotlinScriptDependenciesClassFinder(project: Project,
|
||||
override fun calcClassRoots(): List<VirtualFile> = scriptDependenciesManager.getAllScriptsClasspath().toList()
|
||||
|
||||
override fun getCache(scope: GlobalSearchScope?): PackageDirectoryCache =
|
||||
(scope as? ScriptModuleSearchScope ?:
|
||||
(scope as? AbstractJavaClassFinder.FilterOutKotlinSourceFilesScope)?.base as? ScriptModuleSearchScope
|
||||
)?.let {
|
||||
myCaches[it.scriptFile]
|
||||
} ?: super.getCache(scope)
|
||||
(scope as? ScriptModuleSearchScope
|
||||
?: (scope as? AbstractJavaClassFinder.FilterOutKotlinSourceFilesScope)?.base as? ScriptModuleSearchScope
|
||||
)?.let {
|
||||
myCaches[it.scriptFile]
|
||||
} ?: super.getCache(scope)
|
||||
|
||||
override fun clearCache() {
|
||||
super.clearCache()
|
||||
@@ -59,68 +57,38 @@ class KotlinScriptDependenciesClassFinder(project: Project,
|
||||
}
|
||||
|
||||
override fun findClass(qualifiedName: String, scope: GlobalSearchScope): PsiClass? {
|
||||
val psiClass = findClassInCache(qualifiedName, scope) ?: return null
|
||||
return when {
|
||||
scope is ScriptModuleSearchScope ||
|
||||
(scope as? AbstractJavaClassFinder.FilterOutKotlinSourceFilesScope)?.base is ScriptModuleSearchScope ||
|
||||
scope is EverythingGlobalScope ||
|
||||
psiClass.containingFile?.virtualFile.let { file ->
|
||||
file != null &&
|
||||
with(ProjectFileIndex.SERVICE.getInstance(myProject)) {
|
||||
!isInContent(file) &&
|
||||
!isInLibraryClasses(file) &&
|
||||
!isInLibrarySource(file)
|
||||
}
|
||||
} -> psiClass
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun findClassInCache(qualifiedName: String, scope: GlobalSearchScope): PsiClass? {
|
||||
if (qualifiedName.isEmpty()) return null
|
||||
|
||||
return splitDotQualifiedName(qualifiedName).map { (packageName, classNames) ->
|
||||
findClassInPackage(packageName, classNames, scope)
|
||||
}.find { it != null }
|
||||
}
|
||||
|
||||
private fun findClassInPackage(packageName: String, classNames: List<String>, scope: GlobalSearchScope): PsiClass? {
|
||||
var result: PsiClass? = null
|
||||
ContainerUtil.process(getCache(scope).getDirectoriesByPackageName(packageName)) { dir ->
|
||||
if (dir !in scope) return@process true
|
||||
|
||||
findClassInDir(dir, classNames)?.let {
|
||||
result = it
|
||||
return@process false
|
||||
tailrec fun findClassInner(parentQualifier: String, inners: List<String> = emptyList()): PsiClass? {
|
||||
if (parentQualifier.isEmpty()) return null
|
||||
val parentClass = super.findClass(parentQualifier, scope)
|
||||
if (parentClass != null) {
|
||||
if (inners.isNotEmpty()) {
|
||||
val innerClass = inners.fold<String, PsiClass?>(parentClass) { c: PsiClass?, name: String ->
|
||||
c?.findInnerClassByName(name, false)
|
||||
}
|
||||
if (innerClass != null) return innerClass
|
||||
} else return parentClass
|
||||
}
|
||||
return@process true
|
||||
return findClassInner(
|
||||
parentQualifier.substringBeforeLast('.', ""),
|
||||
listOf(parentQualifier.substringAfterLast('.')) + inners
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun findClassInDir(dir: VirtualFile, classNames: List<String>): PsiClass? {
|
||||
val firstClassName = classNames.first()
|
||||
val virtualFile = dir.findChild("$firstClassName.class") ?: return null
|
||||
val psiFile = psiManager.findFile(virtualFile) as? PsiClassOwner ?: return null
|
||||
val topLevelClass = psiFile.classes.singleOrNull() ?: return null
|
||||
return classNames.subList(1, classNames.size).fold<String, PsiClass?>(topLevelClass) { currentPsiClass, className ->
|
||||
currentPsiClass?.findInnerClassByName(className, false)
|
||||
return findClassInner(qualifiedName)?.let { aClass ->
|
||||
when {
|
||||
scope is ScriptModuleSearchScope ||
|
||||
(scope as? AbstractJavaClassFinder.FilterOutKotlinSourceFilesScope)?.base is ScriptModuleSearchScope ||
|
||||
scope is EverythingGlobalScope ||
|
||||
aClass.containingFile?.virtualFile.let { file ->
|
||||
file != null &&
|
||||
with(ProjectFileIndex.SERVICE.getInstance(myProject)) {
|
||||
!isInContent(file) &&
|
||||
!isInLibraryClasses(file) &&
|
||||
!isInLibrarySource(file)
|
||||
}
|
||||
} -> aClass
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun splitDotQualifiedName(qualifiedName: String): Sequence<Pair<String, List<String>>> {
|
||||
val (packageName, className) = qualifiedName.splitByLastDot()
|
||||
|
||||
return generateSequence(Pair(packageName, listOf(className))) {
|
||||
(prevPackageName, prevClassNames) ->
|
||||
if (prevPackageName == "") return@generateSequence null
|
||||
|
||||
val (newPackageName, newTopLevelClassName) = prevPackageName.splitByLastDot()
|
||||
Pair(newPackageName, listOf(newTopLevelClassName) + prevClassNames)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.splitByLastDot(): Pair<String, String> {
|
||||
return Pair(substringBeforeLast('.', missingDelimiterValue = ""), substringAfterLast('.'))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user