Recreate module descriptors for affected modules only on a change
This commit is contained in:
@@ -18,7 +18,9 @@ package org.jetbrains.kotlin.analyzer
|
|||||||
|
|
||||||
import com.intellij.openapi.components.ServiceManager
|
import com.intellij.openapi.components.ServiceManager
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.ModificationTracker
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.psi.util.PsiModificationTracker
|
||||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||||
@@ -85,9 +87,17 @@ class ResolverForProjectImpl<M : ModuleInfo>(
|
|||||||
private val packagePartProviderFactory: (M, ModuleContent) -> PackagePartProvider = { _, _ -> PackagePartProvider.Empty },
|
private val packagePartProviderFactory: (M, ModuleContent) -> PackagePartProvider = { _, _ -> PackagePartProvider.Empty },
|
||||||
private val firstDependency: M? = null,
|
private val firstDependency: M? = null,
|
||||||
private val modulePlatforms: (M) -> MultiTargetPlatform?,
|
private val modulePlatforms: (M) -> MultiTargetPlatform?,
|
||||||
private val packageOracleFactory: PackageOracleFactory = PackageOracleFactory.OptimisticFactory
|
private val packageOracleFactory: PackageOracleFactory = PackageOracleFactory.OptimisticFactory,
|
||||||
|
private val invalidateOnOOCB: Boolean = true
|
||||||
) : ResolverForProject<M>() {
|
) : ResolverForProject<M>() {
|
||||||
private val descriptorByModule = mutableMapOf<M, ModuleDescriptorImpl>()
|
|
||||||
|
private class ModuleData(
|
||||||
|
val moduleDescriptor: ModuleDescriptorImpl,
|
||||||
|
val modificationTracker: ModificationTracker?,
|
||||||
|
val modificationCount: Long?
|
||||||
|
)
|
||||||
|
|
||||||
|
private val descriptorByModule = mutableMapOf<M, ModuleData>()
|
||||||
private val moduleInfoByDescriptor = mutableMapOf<ModuleDescriptorImpl, M>()
|
private val moduleInfoByDescriptor = mutableMapOf<ModuleDescriptorImpl, M>()
|
||||||
val modules = modules.toSet()
|
val modules = modules.toSet()
|
||||||
|
|
||||||
@@ -156,18 +166,42 @@ class ResolverForProjectImpl<M : ModuleInfo>(
|
|||||||
private fun doGetDescriptorForModule(module: M): ModuleDescriptorImpl {
|
private fun doGetDescriptorForModule(module: M): ModuleDescriptorImpl {
|
||||||
if (module in modules) {
|
if (module in modules) {
|
||||||
return projectContext.storageManager.compute {
|
return projectContext.storageManager.compute {
|
||||||
descriptorByModule.getOrPut(module) {
|
var moduleData = descriptorByModule.getOrPut(module) {
|
||||||
ModuleDescriptorImpl(module.name,
|
createModuleDescriptor(module)
|
||||||
projectContext.storageManager, builtIns, modulePlatforms(module), module.capabilities).apply {
|
|
||||||
moduleInfoByDescriptor[this] = module
|
|
||||||
setupModuleDescriptor(module, this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
val currentModCount = moduleData.modificationTracker?.modificationCount
|
||||||
|
if (currentModCount != null && currentModCount > moduleData.modificationCount!!) {
|
||||||
|
moduleData = recreateModuleDescriptor(module)
|
||||||
|
}
|
||||||
|
moduleData.moduleDescriptor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return delegateResolver.descriptorForModule(module) as ModuleDescriptorImpl
|
return delegateResolver.descriptorForModule(module) as ModuleDescriptorImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun recreateModuleDescriptor(module: M): ModuleData {
|
||||||
|
val oldDescriptor = descriptorByModule[module]?.moduleDescriptor
|
||||||
|
if (oldDescriptor != null) {
|
||||||
|
oldDescriptor.isValid = false
|
||||||
|
moduleInfoByDescriptor.remove(oldDescriptor)
|
||||||
|
resolverByModuleDescriptor.remove(oldDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
val moduleData = createModuleDescriptor(module)
|
||||||
|
descriptorByModule[module] = moduleData
|
||||||
|
return moduleData
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createModuleDescriptor(module: M): ModuleData {
|
||||||
|
val moduleDescriptor = ModuleDescriptorImpl(module.name,
|
||||||
|
projectContext.storageManager, builtIns, modulePlatforms(module), module.capabilities)
|
||||||
|
moduleInfoByDescriptor[moduleDescriptor] = module
|
||||||
|
setupModuleDescriptor(module, moduleDescriptor)
|
||||||
|
val modificationTracker = (module as? TrackableModuleInfo)?.createModificationTracker() ?:
|
||||||
|
(PsiModificationTracker.SERVICE.getInstance(projectContext.project).outOfCodeBlockModificationTracker.takeIf { invalidateOnOOCB })
|
||||||
|
return ModuleData(moduleDescriptor, modificationTracker, modificationTracker?.modificationCount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ModuleContent(
|
data class ModuleContent(
|
||||||
@@ -207,6 +241,10 @@ interface ModuleInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TrackableModuleInfo : ModuleInfo {
|
||||||
|
fun createModificationTracker(): ModificationTracker
|
||||||
|
}
|
||||||
|
|
||||||
abstract class AnalyzerFacade {
|
abstract class AnalyzerFacade {
|
||||||
abstract fun <M : ModuleInfo> createResolverForModule(
|
abstract fun <M : ModuleInfo> createResolverForModule(
|
||||||
moduleInfo: M,
|
moduleInfo: M,
|
||||||
|
|||||||
@@ -48,4 +48,6 @@ interface ModuleDescriptor : DeclarationDescriptor {
|
|||||||
class Capability<T>(val name: String) {
|
class Capability<T>(val name: String) {
|
||||||
override fun toString() = name
|
override fun toString() = name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isValid: Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
|
|||||||
private var dependencies: ModuleDependencies? = null
|
private var dependencies: ModuleDependencies? = null
|
||||||
private var packageFragmentProviderForModuleContent: PackageFragmentProvider? = null
|
private var packageFragmentProviderForModuleContent: PackageFragmentProvider? = null
|
||||||
|
|
||||||
|
override var isValid: Boolean = true
|
||||||
|
|
||||||
|
private fun assertValid() {
|
||||||
|
if (!isValid) {
|
||||||
|
throw IllegalStateException("Accessing invalid module descriptor $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val packages = storageManager.createMemoizedFunction<FqName, PackageViewDescriptor> {
|
private val packages = storageManager.createMemoizedFunction<FqName, PackageViewDescriptor> {
|
||||||
fqName: FqName -> LazyPackageViewDescriptorImpl(this, fqName, storageManager)
|
fqName: FqName -> LazyPackageViewDescriptorImpl(this, fqName, storageManager)
|
||||||
}
|
}
|
||||||
@@ -60,9 +68,13 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
|
|||||||
override val allImplementingModules: Set<ModuleDescriptor>
|
override val allImplementingModules: Set<ModuleDescriptor>
|
||||||
get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allImplementingModules
|
get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allImplementingModules
|
||||||
|
|
||||||
override fun getPackage(fqName: FqName): PackageViewDescriptor = packages(fqName)
|
override fun getPackage(fqName: FqName): PackageViewDescriptor {
|
||||||
|
assertValid()
|
||||||
|
return packages(fqName)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection<FqName> {
|
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection<FqName> {
|
||||||
|
assertValid()
|
||||||
return packageFragmentProvider.getSubPackagesOf(fqName, nameFilter)
|
return packageFragmentProvider.getSubPackagesOf(fqName, nameFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +129,10 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val packageFragmentProvider: PackageFragmentProvider
|
val packageFragmentProvider: PackageFragmentProvider
|
||||||
get() = packageFragmentProviderForWholeModuleWithDependencies
|
get() {
|
||||||
|
assertValid()
|
||||||
|
return packageFragmentProviderForWholeModuleWithDependencies
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T> getCapability(capability: ModuleDescriptor.Capability<T>) = capabilities[capability] as? T
|
override fun <T> getCapability(capability: ModuleDescriptor.Capability<T>) = capabilities[capability] as? T
|
||||||
|
|||||||
@@ -122,6 +122,11 @@ public class ErrorUtils {
|
|||||||
public KotlinBuiltIns getBuiltIns() {
|
public KotlinBuiltIns getBuiltIns() {
|
||||||
return DefaultBuiltIns.getInstance();
|
return DefaultBuiltIns.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.intellij.openapi.projectRoots.Sdk
|
|||||||
import com.intellij.openapi.roots.*
|
import com.intellij.openapi.roots.*
|
||||||
import com.intellij.openapi.roots.impl.libraries.LibraryEx
|
import com.intellij.openapi.roots.impl.libraries.LibraryEx
|
||||||
import com.intellij.openapi.roots.libraries.Library
|
import com.intellij.openapi.roots.libraries.Library
|
||||||
|
import com.intellij.openapi.util.ModificationTracker
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import com.intellij.psi.util.CachedValueProvider
|
import com.intellij.psi.util.CachedValueProvider
|
||||||
@@ -30,9 +31,11 @@ import com.intellij.psi.util.CachedValuesManager
|
|||||||
import com.intellij.util.PathUtil
|
import com.intellij.util.PathUtil
|
||||||
import com.intellij.util.SmartList
|
import com.intellij.util.SmartList
|
||||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||||
|
import org.jetbrains.kotlin.analyzer.TrackableModuleInfo
|
||||||
import org.jetbrains.kotlin.caches.resolve.LibraryModuleInfo
|
import org.jetbrains.kotlin.caches.resolve.LibraryModuleInfo
|
||||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||||
import org.jetbrains.kotlin.idea.framework.getLibraryPlatform
|
import org.jetbrains.kotlin.idea.framework.getLibraryPlatform
|
||||||
|
import org.jetbrains.kotlin.idea.project.KotlinModuleModificationTracker
|
||||||
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
|
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
|
||||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||||
import org.jetbrains.kotlin.idea.util.isInSourceContentWithoutInjected
|
import org.jetbrains.kotlin.idea.util.isInSourceContentWithoutInjected
|
||||||
@@ -97,7 +100,7 @@ private fun ideaModelDependencies(module: Module, productionOnly: Boolean): List
|
|||||||
return result.toList()
|
return result.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ModuleSourceInfo : IdeaModuleInfo {
|
interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo {
|
||||||
val module: Module
|
val module: Module
|
||||||
|
|
||||||
override val displayedName get() = module.name
|
override val displayedName get() = module.name
|
||||||
@@ -107,6 +110,9 @@ interface ModuleSourceInfo : IdeaModuleInfo {
|
|||||||
|
|
||||||
override val platform: TargetPlatform
|
override val platform: TargetPlatform
|
||||||
get() = TargetPlatformDetector.getPlatform(module)
|
get() = TargetPlatformDetector.getPlatform(module)
|
||||||
|
|
||||||
|
override fun createModificationTracker(): ModificationTracker =
|
||||||
|
KotlinModuleModificationTracker(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ModuleProductionSourceInfo(override val module: Module) : ModuleSourceInfo {
|
data class ModuleProductionSourceInfo(override val module: Module) : ModuleSourceInfo {
|
||||||
|
|||||||
+6
-1
@@ -125,6 +125,7 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
|
|||||||
LibraryModificationTracker.getInstance(project),
|
LibraryModificationTracker.getInstance(project),
|
||||||
ProjectRootModificationTracker.getInstance(project)
|
ProjectRootModificationTracker.getInstance(project)
|
||||||
),
|
),
|
||||||
|
invalidateOnOOCB = false,
|
||||||
reuseDataFrom = null
|
reuseDataFrom = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -134,6 +135,7 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
|
|||||||
project, librariesContext, settings,
|
project, librariesContext, settings,
|
||||||
reuseDataFrom = facadeForSdk,
|
reuseDataFrom = facadeForSdk,
|
||||||
moduleFilter = { it is LibraryInfo },
|
moduleFilter = { it is LibraryInfo },
|
||||||
|
invalidateOnOOCB = false,
|
||||||
dependencies = listOf(
|
dependencies = listOf(
|
||||||
LibraryModificationTracker.getInstance(project),
|
LibraryModificationTracker.getInstance(project),
|
||||||
ProjectRootModificationTracker.getInstance(project)
|
ProjectRootModificationTracker.getInstance(project)
|
||||||
@@ -146,7 +148,10 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
|
|||||||
project, modulesContext, settings,
|
project, modulesContext, settings,
|
||||||
reuseDataFrom = facadeForLibraries,
|
reuseDataFrom = facadeForLibraries,
|
||||||
moduleFilter = { !it.isLibraryClasses() },
|
moduleFilter = { !it.isLibraryClasses() },
|
||||||
dependencies = listOf(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT)
|
dependencies = listOf(
|
||||||
|
LibraryModificationTracker.getInstance(project),
|
||||||
|
ProjectRootModificationTracker.getInstance(project)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -52,7 +52,8 @@ fun createModuleResolverProvider(
|
|||||||
moduleFilter: (IdeaModuleInfo) -> Boolean,
|
moduleFilter: (IdeaModuleInfo) -> Boolean,
|
||||||
allModules: Collection<IdeaModuleInfo>?,
|
allModules: Collection<IdeaModuleInfo>?,
|
||||||
providedBuiltIns: KotlinBuiltIns?, // null means create new builtins based on SDK
|
providedBuiltIns: KotlinBuiltIns?, // null means create new builtins based on SDK
|
||||||
dependencies: Collection<Any>
|
dependencies: Collection<Any>,
|
||||||
|
invalidateOnOOCB: Boolean = true
|
||||||
): ModuleResolverProvider {
|
): ModuleResolverProvider {
|
||||||
val builtIns = providedBuiltIns ?: createBuiltIns(analysisSettings, globalContext)
|
val builtIns = providedBuiltIns ?: createBuiltIns(analysisSettings, globalContext)
|
||||||
|
|
||||||
@@ -82,7 +83,8 @@ fun createModuleResolverProvider(
|
|||||||
delegateResolver, { _, c -> IDEPackagePartProvider(c.moduleContentScope) },
|
delegateResolver, { _, c -> IDEPackagePartProvider(c.moduleContentScope) },
|
||||||
analysisSettings.sdk?.let { SdkInfo(project, it) },
|
analysisSettings.sdk?.let { SdkInfo(project, it) },
|
||||||
modulePlatforms = { module -> module.platform?.multiTargetPlatform },
|
modulePlatforms = { module -> module.platform?.multiTargetPlatform },
|
||||||
packageOracleFactory = project.service<IdePackageOracleFactory>()
|
packageOracleFactory = project.service<IdePackageOracleFactory>(),
|
||||||
|
invalidateOnOOCB = invalidateOnOOCB
|
||||||
)
|
)
|
||||||
|
|
||||||
if (providedBuiltIns == null && builtIns is JvmBuiltIns) {
|
if (providedBuiltIns == null && builtIns is JvmBuiltIns) {
|
||||||
|
|||||||
+6
-4
@@ -30,14 +30,15 @@ import org.jetbrains.kotlin.psi.KtFile
|
|||||||
import org.jetbrains.kotlin.resolve.CompositeBindingContext
|
import org.jetbrains.kotlin.resolve.CompositeBindingContext
|
||||||
|
|
||||||
internal class ProjectResolutionFacade(
|
internal class ProjectResolutionFacade(
|
||||||
val debugString: String,
|
private val debugString: String,
|
||||||
val resolverDebugName: String,
|
private val resolverDebugName: String,
|
||||||
val project: Project,
|
val project: Project,
|
||||||
val globalContext: GlobalContextImpl,
|
val globalContext: GlobalContextImpl,
|
||||||
val settings: PlatformAnalysisSettings,
|
val settings: PlatformAnalysisSettings,
|
||||||
val reuseDataFrom: ProjectResolutionFacade?,
|
val reuseDataFrom: ProjectResolutionFacade?,
|
||||||
val moduleFilter: (IdeaModuleInfo) -> Boolean,
|
val moduleFilter: (IdeaModuleInfo) -> Boolean,
|
||||||
val dependencies: List<Any>,
|
val dependencies: List<Any>,
|
||||||
|
private val invalidateOnOOCB: Boolean = true,
|
||||||
val syntheticFiles: Collection<KtFile> = listOf(),
|
val syntheticFiles: Collection<KtFile> = listOf(),
|
||||||
val allModules: Collection<IdeaModuleInfo>? = null // null means create resolvers for modules from idea model
|
val allModules: Collection<IdeaModuleInfo>? = null // null means create resolvers for modules from idea model
|
||||||
) {
|
) {
|
||||||
@@ -61,11 +62,12 @@ internal class ProjectResolutionFacade(
|
|||||||
delegateResolver = delegateResolverForProject, moduleFilter = moduleFilter,
|
delegateResolver = delegateResolverForProject, moduleFilter = moduleFilter,
|
||||||
allModules = allModules,
|
allModules = allModules,
|
||||||
providedBuiltIns = delegateResolverProvider?.builtIns,
|
providedBuiltIns = delegateResolverProvider?.builtIns,
|
||||||
dependencies = dependencies
|
dependencies = dependencies,
|
||||||
|
invalidateOnOOCB = invalidateOnOOCB
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val moduleResolverProvider: ModuleResolverProvider
|
private val moduleResolverProvider: ModuleResolverProvider
|
||||||
get() = globalContext.storageManager.compute { cachedValue.value }
|
get() = globalContext.storageManager.compute { cachedValue.value }
|
||||||
|
|
||||||
fun resolverForModuleInfo(moduleInfo: IdeaModuleInfo) = moduleResolverProvider.resolverForProject.resolverForModule(moduleInfo)
|
fun resolverForModuleInfo(moduleInfo: IdeaModuleInfo) = moduleResolverProvider.resolverForProject.resolverForModule(moduleInfo)
|
||||||
|
|||||||
+73
-3
@@ -16,8 +16,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.project
|
package org.jetbrains.kotlin.idea.project
|
||||||
|
|
||||||
|
import com.intellij.openapi.module.Module
|
||||||
|
import com.intellij.openapi.module.ModuleUtil
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.roots.ModuleRootManager
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
|
import com.intellij.openapi.util.ModificationTracker
|
||||||
import com.intellij.pom.PomManager
|
import com.intellij.pom.PomManager
|
||||||
import com.intellij.pom.PomModelAspect
|
import com.intellij.pom.PomModelAspect
|
||||||
import com.intellij.pom.event.PomModelEvent
|
import com.intellij.pom.event.PomModelEvent
|
||||||
@@ -28,6 +32,7 @@ import com.intellij.psi.PsiCodeFragment
|
|||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
||||||
import com.intellij.psi.util.PsiModificationTracker
|
import com.intellij.psi.util.PsiModificationTracker
|
||||||
|
import com.intellij.util.CommonProcessors
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||||
@@ -41,10 +46,27 @@ class KotlinCodeBlockModificationListener(
|
|||||||
project: Project,
|
project: Project,
|
||||||
private val treeAspect: TreeAspect
|
private val treeAspect: TreeAspect
|
||||||
) {
|
) {
|
||||||
|
private val perModuleModCount = mutableMapOf<Module, Long>()
|
||||||
|
private val modificationTrackerImpl = modificationTracker as PsiModificationTrackerImpl
|
||||||
|
|
||||||
|
private var lastAffectedModule: Module? = null
|
||||||
|
private var lastAffectedModuleModCount = -1L
|
||||||
|
|
||||||
|
// All modifications since that count are known to be single-module modifications reflected in
|
||||||
|
// perModuleModCount map
|
||||||
|
private var perModuleChangesHighwatermark: Long? = null
|
||||||
|
|
||||||
|
fun getModificationCount(module: Module): Long {
|
||||||
|
return perModuleModCount[module] ?:
|
||||||
|
perModuleChangesHighwatermark ?:
|
||||||
|
modificationTrackerImpl.outOfCodeBlockModificationCount
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasPerModuleModificationCounts() = perModuleChangesHighwatermark != null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val model = PomManager.getModel(project)
|
val model = PomManager.getModel(project)
|
||||||
@Suppress("NAME_SHADOWING")
|
val messageBusConnection = project.messageBus.connect()
|
||||||
val modificationTracker = modificationTracker as PsiModificationTrackerImpl
|
|
||||||
model.addModelListener(object: PomModelListener {
|
model.addModelListener(object: PomModelListener {
|
||||||
override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean {
|
override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean {
|
||||||
return aspect == treeAspect
|
return aspect == treeAspect
|
||||||
@@ -58,13 +80,31 @@ class KotlinCodeBlockModificationListener(
|
|||||||
// contents to be replaced, which is represented in a POM event as an empty list of changed elements
|
// contents to be replaced, which is represented in a POM event as an empty list of changed elements
|
||||||
if (changedElements.any { getInsideCodeBlockModificationScope(it.psi) == null } ||
|
if (changedElements.any { getInsideCodeBlockModificationScope(it.psi) == null } ||
|
||||||
(file is PsiCodeFragment && changedElements.isEmpty())) {
|
(file is PsiCodeFragment && changedElements.isEmpty())) {
|
||||||
|
messageBusConnection.deliverImmediately()
|
||||||
if (file.isPhysical) {
|
if (file.isPhysical) {
|
||||||
modificationTracker.incCounter()
|
lastAffectedModule = ModuleUtil.findModuleForPsiElement(file)
|
||||||
|
lastAffectedModuleModCount = modificationTrackerImpl.outOfCodeBlockModificationCount
|
||||||
|
modificationTrackerImpl.incCounter()
|
||||||
}
|
}
|
||||||
incOutOfBlockModificationCount(file)
|
incOutOfBlockModificationCount(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, PsiModificationTracker.Listener {
|
||||||
|
val newModCount = modificationTrackerImpl.outOfCodeBlockModificationCount
|
||||||
|
val affectedModule = lastAffectedModule
|
||||||
|
if (affectedModule != null && newModCount == lastAffectedModuleModCount + 1) {
|
||||||
|
if (perModuleChangesHighwatermark== null) {
|
||||||
|
perModuleChangesHighwatermark = lastAffectedModuleModCount
|
||||||
|
}
|
||||||
|
perModuleModCount[affectedModule] = newModCount
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
perModuleChangesHighwatermark = null
|
||||||
|
perModuleModCount.clear()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -117,6 +157,8 @@ class KotlinCodeBlockModificationListener(
|
|||||||
KtProperty::class.java,
|
KtProperty::class.java,
|
||||||
KtNamedFunction::class.java
|
KtNamedFunction::class.java
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun getInstance(project: Project) = project.getComponent(KotlinCodeBlockModificationListener::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,3 +166,31 @@ private val FILE_OUT_OF_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_OUT_OF_BLOCK_
|
|||||||
|
|
||||||
val KtFile.outOfBlockModificationCount: Long
|
val KtFile.outOfBlockModificationCount: Long
|
||||||
get() = getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
|
get() = getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
|
||||||
|
|
||||||
|
class KotlinModuleModificationTracker(val module: Module): ModificationTracker {
|
||||||
|
private val kotlinModCountListener = KotlinCodeBlockModificationListener.getInstance(module.project)
|
||||||
|
private val psiModificationTracker = PsiModificationTracker.SERVICE.getInstance(module.project)
|
||||||
|
private val dependencies by lazy {
|
||||||
|
HashSet<Module>().apply {
|
||||||
|
ModuleRootManager.getInstance(module).orderEntries().recursively().forEachModule(
|
||||||
|
CommonProcessors.CollectProcessor(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getModificationCount(): Long {
|
||||||
|
val currentGlobalCount = psiModificationTracker.outOfCodeBlockModificationCount
|
||||||
|
|
||||||
|
if (kotlinModCountListener.hasPerModuleModificationCounts()) {
|
||||||
|
val selfCount = kotlinModCountListener.getModificationCount(module)
|
||||||
|
if (selfCount == currentGlobalCount) return selfCount
|
||||||
|
var maxCount = selfCount
|
||||||
|
for (dependency in dependencies) {
|
||||||
|
val depCount = kotlinModCountListener.getModificationCount(dependency)
|
||||||
|
if (depCount == currentGlobalCount) return currentGlobalCount
|
||||||
|
if (depCount > maxCount) maxCount = depCount
|
||||||
|
}
|
||||||
|
return maxCount
|
||||||
|
}
|
||||||
|
return currentGlobalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package foo.bar.used
|
||||||
|
|
||||||
|
public fun m1() {
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import foo.bar.used.*
|
||||||
|
|
||||||
|
public fun m2() {
|
||||||
|
m1()
|
||||||
|
}
|
||||||
+87
-19
@@ -17,8 +17,16 @@
|
|||||||
package org.jetbrains.kotlin.idea.caches.resolve
|
package org.jetbrains.kotlin.idea.caches.resolve
|
||||||
|
|
||||||
import com.intellij.facet.FacetManager
|
import com.intellij.facet.FacetManager
|
||||||
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
import com.intellij.openapi.module.Module
|
import com.intellij.openapi.module.Module
|
||||||
|
import com.intellij.openapi.projectRoots.Sdk
|
||||||
import com.intellij.openapi.roots.DependencyScope
|
import com.intellij.openapi.roots.DependencyScope
|
||||||
|
import com.intellij.openapi.roots.ModuleRootManager
|
||||||
|
import com.intellij.openapi.roots.ModuleRootModificationUtil
|
||||||
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
import com.intellij.psi.PsiManager
|
||||||
|
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
||||||
|
import com.intellij.psi.util.PsiModificationTracker
|
||||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||||
import org.jetbrains.kotlin.analyzer.ResolverForModuleComputationTracker
|
import org.jetbrains.kotlin.analyzer.ResolverForModuleComputationTracker
|
||||||
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
|
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
|
||||||
@@ -30,8 +38,12 @@ import org.jetbrains.kotlin.idea.completion.test.withServiceRegistered
|
|||||||
import org.jetbrains.kotlin.idea.facet.KotlinFacetConfiguration
|
import org.jetbrains.kotlin.idea.facet.KotlinFacetConfiguration
|
||||||
import org.jetbrains.kotlin.idea.facet.KotlinFacetType
|
import org.jetbrains.kotlin.idea.facet.KotlinFacetType
|
||||||
import org.jetbrains.kotlin.idea.framework.JSLibraryKind
|
import org.jetbrains.kotlin.idea.framework.JSLibraryKind
|
||||||
|
import org.jetbrains.kotlin.idea.project.KotlinCodeBlockModificationListener
|
||||||
|
import org.jetbrains.kotlin.idea.project.KotlinModuleModificationTracker
|
||||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
|
import org.jetbrains.kotlin.idea.util.projectStructure.sdk
|
||||||
import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK
|
import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK
|
||||||
|
|
||||||
open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
|
open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
|
||||||
@@ -66,20 +78,9 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun testLazyResolvers() {
|
fun testLazyResolvers() {
|
||||||
val resolversComputed = mutableSetOf<Module>()
|
val tracker = ResolverTracker()
|
||||||
|
|
||||||
val resolversTracker = object : ResolverForModuleComputationTracker {
|
project.withServiceRegistered<ResolverForModuleComputationTracker, Unit>(tracker) {
|
||||||
override fun onResolverComputed(moduleInfo: ModuleInfo) {
|
|
||||||
(moduleInfo as IdeaModuleInfo).let {
|
|
||||||
if (it is ModuleSourceInfo) {
|
|
||||||
val module = it.module
|
|
||||||
resolversComputed.add(module)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
project.withServiceRegistered<ResolverForModuleComputationTracker, Unit>(resolversTracker) {
|
|
||||||
val module1 = module("m1")
|
val module1 = module("m1")
|
||||||
val module2 = module("m2")
|
val module2 = module("m2")
|
||||||
val module3 = module("m3")
|
val module3 = module("m3")
|
||||||
@@ -87,15 +88,82 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
|
|||||||
module3.addDependency(module2)
|
module3.addDependency(module2)
|
||||||
module3.addDependency(module1)
|
module3.addDependency(module1)
|
||||||
|
|
||||||
assertTrue(module1 !in resolversComputed)
|
assertTrue(module1 !in tracker.moduleResolversComputed)
|
||||||
assertTrue(module2 !in resolversComputed)
|
assertTrue(module2 !in tracker.moduleResolversComputed)
|
||||||
assertTrue(module3 !in resolversComputed)
|
assertTrue(module3 !in tracker.moduleResolversComputed)
|
||||||
|
|
||||||
checkHighlightingInAllFiles { "m3" in file.name }
|
checkHighlightingInAllFiles { "m3" in file.name }
|
||||||
|
|
||||||
assertTrue(module1 in resolversComputed)
|
assertTrue(module1 in tracker.moduleResolversComputed)
|
||||||
assertTrue(module2 !in resolversComputed)
|
assertTrue(module2 !in tracker.moduleResolversComputed)
|
||||||
assertTrue(module3 in resolversComputed)
|
assertTrue(module3 in tracker.moduleResolversComputed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResolverTracker : ResolverForModuleComputationTracker {
|
||||||
|
val moduleResolversComputed = mutableListOf<Module>()
|
||||||
|
val sdkResolversComputed = mutableListOf<Sdk>()
|
||||||
|
|
||||||
|
override fun onResolverComputed(moduleInfo: ModuleInfo) {
|
||||||
|
when (moduleInfo) {
|
||||||
|
is ModuleSourceInfo -> moduleResolversComputed.add(moduleInfo.module)
|
||||||
|
is SdkInfo -> sdkResolversComputed.add(moduleInfo.sdk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testRecomputeResolversOnChange() {
|
||||||
|
val tracker = ResolverTracker()
|
||||||
|
|
||||||
|
project.withServiceRegistered<ResolverForModuleComputationTracker, Unit>(tracker) {
|
||||||
|
val module1 = module("m1")
|
||||||
|
val module2 = module("m2")
|
||||||
|
val module3 = module("m3")
|
||||||
|
|
||||||
|
module2.addDependency(module1)
|
||||||
|
module3.addDependency(module2)
|
||||||
|
// Ensure modules have the same SDK instance, and not two distinct SDKs with the same path
|
||||||
|
ModuleRootModificationUtil.setModuleSdk(module2, module1.sdk)
|
||||||
|
|
||||||
|
assertEquals(0, tracker.sdkResolversComputed.size)
|
||||||
|
|
||||||
|
checkHighlightingInAllFiles { "m2" in file.name }
|
||||||
|
|
||||||
|
assertEquals(2, tracker.moduleResolversComputed.size)
|
||||||
|
|
||||||
|
tracker.sdkResolversComputed.clear()
|
||||||
|
tracker.moduleResolversComputed.clear()
|
||||||
|
|
||||||
|
val module1ModCount = KotlinCodeBlockModificationListener.getInstance(myProject).getModificationCount(module1)
|
||||||
|
|
||||||
|
val module1ModTracker = KotlinModuleModificationTracker(module1)
|
||||||
|
val module2ModTracker = KotlinModuleModificationTracker(module2)
|
||||||
|
val module3ModTracker = KotlinModuleModificationTracker(module3)
|
||||||
|
|
||||||
|
val contentRoot = ModuleRootManager.getInstance(module2).contentRoots.single()
|
||||||
|
val m2 = contentRoot.findChild("m2.kt")!!
|
||||||
|
val m2doc = FileDocumentManager.getInstance().getDocument(m2)!!
|
||||||
|
project.executeWriteCommand("a") {
|
||||||
|
m2doc.insertString(m2doc.textLength , "fun foo() = 1")
|
||||||
|
PsiDocumentManager.getInstance(myProject).commitAllDocuments()
|
||||||
|
}
|
||||||
|
val currentModCount = PsiManager.getInstance(project).modificationTracker.outOfCodeBlockModificationCount
|
||||||
|
|
||||||
|
assertEquals(module1ModCount, KotlinCodeBlockModificationListener.getInstance(myProject).getModificationCount(module1))
|
||||||
|
assertEquals(module1ModCount, module1ModTracker.modificationCount)
|
||||||
|
assertEquals(currentModCount, module2ModTracker.modificationCount)
|
||||||
|
assertEquals(currentModCount, module3ModTracker.modificationCount)
|
||||||
|
|
||||||
|
checkHighlightingInAllFiles { "m2" in file.name }
|
||||||
|
|
||||||
|
assertEquals(0, tracker.sdkResolversComputed.size)
|
||||||
|
assertEquals(1, tracker.moduleResolversComputed.size)
|
||||||
|
|
||||||
|
tracker.moduleResolversComputed.clear()
|
||||||
|
(PsiModificationTracker.SERVICE.getInstance(myProject) as PsiModificationTrackerImpl).incOutOfCodeBlockModificationCounter()
|
||||||
|
checkHighlightingInAllFiles { "m2" in file.name }
|
||||||
|
assertEquals(0, tracker.sdkResolversComputed.size)
|
||||||
|
assertEquals(2, tracker.moduleResolversComputed.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user