Introduce Kotlin specific out-of-code-block tracker (KT-32364)
This commit is contained in:
+70
-13
@@ -9,6 +9,8 @@ import com.intellij.openapi.module.Module
|
|||||||
import com.intellij.openapi.module.ModuleUtil
|
import com.intellij.openapi.module.ModuleUtil
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
|
import com.intellij.openapi.util.ModificationTracker
|
||||||
|
import com.intellij.openapi.util.SimpleModificationTracker
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.pom.PomManager
|
import com.intellij.pom.PomManager
|
||||||
import com.intellij.pom.PomModelAspect
|
import com.intellij.pom.PomModelAspect
|
||||||
@@ -16,9 +18,18 @@ import com.intellij.pom.event.PomModelEvent
|
|||||||
import com.intellij.pom.event.PomModelListener
|
import com.intellij.pom.event.PomModelListener
|
||||||
import com.intellij.pom.tree.TreeAspect
|
import com.intellij.pom.tree.TreeAspect
|
||||||
import com.intellij.pom.tree.events.TreeChangeEvent
|
import com.intellij.pom.tree.events.TreeChangeEvent
|
||||||
|
import com.intellij.psi.PsiDirectory
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiManager
|
||||||
|
import com.intellij.psi.PsiTreeChangeEvent
|
||||||
|
import com.intellij.psi.impl.PsiManagerImpl
|
||||||
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
||||||
|
import com.intellij.psi.impl.PsiTreeChangeEventImpl
|
||||||
|
import com.intellij.psi.impl.PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED
|
||||||
|
import com.intellij.psi.impl.PsiTreeChangeEventImpl.PsiEventType.PROPERTY_CHANGED
|
||||||
|
import com.intellij.psi.impl.PsiTreeChangePreprocessor
|
||||||
import com.intellij.psi.util.PsiModificationTracker
|
import com.intellij.psi.util.PsiModificationTracker
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
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
|
||||||
@@ -32,19 +43,30 @@ class KotlinCodeBlockModificationListener(
|
|||||||
modificationTracker: PsiModificationTracker,
|
modificationTracker: PsiModificationTracker,
|
||||||
project: Project,
|
project: Project,
|
||||||
private val treeAspect: TreeAspect
|
private val treeAspect: TreeAspect
|
||||||
) {
|
): PsiTreeChangePreprocessor {
|
||||||
private val perModuleModCount = mutableMapOf<Module, Long>()
|
private val perModuleModCount = mutableMapOf<Module, Long>()
|
||||||
|
|
||||||
private val modificationTrackerImpl = modificationTracker as PsiModificationTrackerImpl
|
private val modificationTrackerImpl = modificationTracker as PsiModificationTrackerImpl
|
||||||
|
|
||||||
private var lastAffectedModule: Module? = null
|
private var lastAffectedModule: Module? = null
|
||||||
private var lastAffectedModuleModCount = -1L
|
|
||||||
|
|
||||||
|
private var lastAffectedModuleModCount = -1L
|
||||||
// All modifications since that count are known to be single-module modifications reflected in
|
// All modifications since that count are known to be single-module modifications reflected in
|
||||||
// perModuleModCount map
|
// perModuleModCount map
|
||||||
private var perModuleChangesHighWatermark: Long? = null
|
private var perModuleChangesHighWatermark: Long? = null
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var kotlinModificationTracker: Long = 0
|
||||||
|
|
||||||
|
private val kotlinOutOfCodeBlockTrackerImpl = object : SimpleModificationTracker() {
|
||||||
|
override fun incModificationCount() {
|
||||||
|
super.incModificationCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val kotlinOutOfCodeBlockTracker: ModificationTracker = kotlinOutOfCodeBlockTrackerImpl
|
||||||
|
|
||||||
fun getModificationCount(module: Module): Long {
|
fun getModificationCount(module: Module): Long {
|
||||||
return perModuleModCount[module] ?: perModuleChangesHighWatermark ?: modificationTrackerImpl.outOfCodeBlockModificationCount
|
return perModuleModCount[module] ?: perModuleChangesHighWatermark ?: kotlinOutOfCodeBlockTracker.modificationCount
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasPerModuleModificationCounts() = perModuleChangesHighWatermark != null
|
fun hasPerModuleModificationCounts() = perModuleChangesHighWatermark != null
|
||||||
@@ -52,6 +74,9 @@ class KotlinCodeBlockModificationListener(
|
|||||||
init {
|
init {
|
||||||
val model = PomManager.getModel(project)
|
val model = PomManager.getModel(project)
|
||||||
val messageBusConnection = project.messageBus.connect()
|
val messageBusConnection = project.messageBus.connect()
|
||||||
|
|
||||||
|
(PsiManager.getInstance(project) as PsiManagerImpl).addTreeChangePreprocessor(this)
|
||||||
|
|
||||||
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
|
||||||
@@ -59,26 +84,38 @@ class KotlinCodeBlockModificationListener(
|
|||||||
|
|
||||||
override fun modelChanged(event: PomModelEvent) {
|
override fun modelChanged(event: PomModelEvent) {
|
||||||
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
|
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
|
||||||
val file = changeSet.rootElement.psi.containingFile as? KtFile ?: return
|
val ktFile = changeSet.rootElement.psi.containingFile as? KtFile ?: return
|
||||||
|
|
||||||
val changedElements = changeSet.changedElements
|
val changedElements = changeSet.changedElements
|
||||||
// When a code fragment is reparsed, Intellij doesn't do an AST diff and considers the entire
|
// When a code fragment is reparsed, Intellij doesn't do an AST diff and considers the entire
|
||||||
// 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 } || changedElements.isEmpty()) {
|
if (changedElements.any { getInsideCodeBlockModificationScope(it.psi) == null } || changedElements.isEmpty()) {
|
||||||
messageBusConnection.deliverImmediately()
|
messageBusConnection.deliverImmediately()
|
||||||
if (file.isPhysical && !isReplLine(file.virtualFile)) {
|
|
||||||
lastAffectedModule = ModuleUtil.findModuleForPsiElement(file)
|
if (ktFile.isPhysical && !isReplLine(ktFile.virtualFile)) {
|
||||||
lastAffectedModuleModCount = modificationTrackerImpl.outOfCodeBlockModificationCount
|
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
||||||
modificationTrackerImpl.incCounter()
|
lastAffectedModule = ModuleUtil.findModuleForPsiElement(ktFile)
|
||||||
|
lastAffectedModuleModCount = kotlinOutOfCodeBlockTracker.modificationCount
|
||||||
|
// TODO: Revise perModuleModCount
|
||||||
|
// modificationTrackerImpl.incCounter()
|
||||||
}
|
}
|
||||||
incOutOfBlockModificationCount(
|
|
||||||
file
|
incOutOfBlockModificationCount(ktFile)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, PsiModificationTracker.Listener {
|
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, PsiModificationTracker.Listener {
|
||||||
val newModCount = modificationTrackerImpl.outOfCodeBlockModificationCount
|
@Suppress("UnstableApiUsage") val kotlinTrackerInternalIDECount =
|
||||||
|
modificationTrackerImpl.forLanguage(KotlinLanguage.INSTANCE).modificationCount
|
||||||
|
if (kotlinModificationTracker == kotlinTrackerInternalIDECount) {
|
||||||
|
// Some update that we are not sure is from Kotlin language, as Kotlin language tracker wasn't changed
|
||||||
|
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
||||||
|
} else {
|
||||||
|
kotlinModificationTracker = kotlinTrackerInternalIDECount
|
||||||
|
}
|
||||||
|
|
||||||
|
val newModCount = kotlinOutOfCodeBlockTracker.modificationCount
|
||||||
val affectedModule = lastAffectedModule
|
val affectedModule = lastAffectedModule
|
||||||
if (affectedModule != null && newModCount == lastAffectedModuleModCount + 1) {
|
if (affectedModule != null && newModCount == lastAffectedModuleModCount + 1) {
|
||||||
if (perModuleChangesHighWatermark == null) {
|
if (perModuleChangesHighWatermark == null) {
|
||||||
@@ -92,6 +129,26 @@ class KotlinCodeBlockModificationListener(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun treeChanged(event: PsiTreeChangeEventImpl) {
|
||||||
|
if (!PsiModificationTrackerImpl.canAffectPsi(event)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy logic from PsiModificationTrackerImpl.treeChanged(). Some out-of-code-block events are written to language modification
|
||||||
|
// tracker in PsiModificationTrackerImpl but don't have correspondent PomModelEvent. Increase kotlinOutOfCodeBlockTracker
|
||||||
|
// manually if needed.
|
||||||
|
val outOfCodeBlock = when (event.code) {
|
||||||
|
PROPERTY_CHANGED ->
|
||||||
|
event.propertyName === PsiTreeChangeEvent.PROP_UNLOADED_PSI || event.propertyName === PsiTreeChangeEvent.PROP_ROOTS
|
||||||
|
CHILD_MOVED -> event.oldParent is PsiDirectory || event.newParent is PsiDirectory
|
||||||
|
else -> event.parent is PsiDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outOfCodeBlock) {
|
||||||
|
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun isReplLine(file: VirtualFile): Boolean {
|
private fun isReplLine(file: VirtualFile): Boolean {
|
||||||
return file.getUserData(KOTLIN_CONSOLE_KEY) == true
|
return file.getUserData(KOTLIN_CONSOLE_KEY) == true
|
||||||
|
|||||||
+4
-3
@@ -10,14 +10,13 @@ import com.intellij.openapi.roots.ModuleRootManager
|
|||||||
import com.intellij.openapi.roots.ProjectRootModificationTracker
|
import com.intellij.openapi.roots.ProjectRootModificationTracker
|
||||||
import com.intellij.openapi.util.ModificationTracker
|
import com.intellij.openapi.util.ModificationTracker
|
||||||
import com.intellij.psi.util.CachedValueProvider
|
import com.intellij.psi.util.CachedValueProvider
|
||||||
import com.intellij.psi.util.PsiModificationTracker
|
|
||||||
import com.intellij.util.CommonProcessors
|
import com.intellij.util.CommonProcessors
|
||||||
import org.jetbrains.kotlin.idea.caches.project.cached
|
import org.jetbrains.kotlin.idea.caches.project.cached
|
||||||
|
|
||||||
class KotlinModuleModificationTracker(val module: Module) : ModificationTracker {
|
class KotlinModuleModificationTracker(val module: Module) : ModificationTracker {
|
||||||
private val kotlinModCountListener =
|
private val kotlinModCountListener =
|
||||||
KotlinCodeBlockModificationListener.getInstance(module.project)
|
KotlinCodeBlockModificationListener.getInstance(module.project)
|
||||||
private val psiModificationTracker = PsiModificationTracker.SERVICE.getInstance(module.project)
|
|
||||||
private val dependencies by lazy {
|
private val dependencies by lazy {
|
||||||
module.cached(CachedValueProvider {
|
module.cached(CachedValueProvider {
|
||||||
CachedValueProvider.Result.create(
|
CachedValueProvider.Result.create(
|
||||||
@@ -32,11 +31,12 @@ class KotlinModuleModificationTracker(val module: Module) : ModificationTracker
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getModificationCount(): Long {
|
override fun getModificationCount(): Long {
|
||||||
val currentGlobalCount = psiModificationTracker.outOfCodeBlockModificationCount
|
val currentGlobalCount = kotlinModCountListener.kotlinOutOfCodeBlockTracker.modificationCount
|
||||||
|
|
||||||
if (kotlinModCountListener.hasPerModuleModificationCounts()) {
|
if (kotlinModCountListener.hasPerModuleModificationCounts()) {
|
||||||
val selfCount = kotlinModCountListener.getModificationCount(module)
|
val selfCount = kotlinModCountListener.getModificationCount(module)
|
||||||
if (selfCount == currentGlobalCount) return selfCount
|
if (selfCount == currentGlobalCount) return selfCount
|
||||||
|
|
||||||
var maxCount = selfCount
|
var maxCount = selfCount
|
||||||
for (dependency in dependencies) {
|
for (dependency in dependencies) {
|
||||||
val depCount = kotlinModCountListener.getModificationCount(dependency)
|
val depCount = kotlinModCountListener.getModificationCount(dependency)
|
||||||
@@ -45,6 +45,7 @@ class KotlinModuleModificationTracker(val module: Module) : ModificationTracker
|
|||||||
}
|
}
|
||||||
return maxCount
|
return maxCount
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentGlobalCount
|
return currentGlobalCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user