Compatify KotlinCodeBlockModificationListener p.2
Relates to #KT-38443
This commit is contained in:
committed by
Vladimir Dolzhenko
parent
5a36da90b6
commit
1cc58518c0
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
|
||||
package org.jetbrains.kotlin.idea.caches.trackers
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.SimpleModificationTracker
|
||||
import com.intellij.pom.tree.TreeAspect
|
||||
import com.intellij.psi.impl.PsiTreeChangeEventImpl
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
|
||||
/**
|
||||
* Tested in OutOfBlockModificationTestGenerated
|
||||
*/
|
||||
// BUNCH: 193
|
||||
class KotlinCodeBlockModificationListener(
|
||||
project: Project,
|
||||
treeAspect: TreeAspect
|
||||
) : KotlinCodeBlockModificationListenerCompat(project) {
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
private val isLanguageTrackerEnabled = modificationTrackerImpl.isEnableLanguageTrackerCompat
|
||||
|
||||
// BUNCH: 191
|
||||
// When there're we no per-language trackers we had to increment global tracker first and process result afterward
|
||||
private val customIncrement = if (isLanguageTrackerEnabled) 0 else 1
|
||||
|
||||
init {
|
||||
init(
|
||||
treeAspect,
|
||||
incOCBCounter = { ktFile ->
|
||||
if (isLanguageTrackerEnabled) {
|
||||
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, true)
|
||||
} else {
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, false)
|
||||
// Increment counter and process changes in PsiModificationTracker.Listener
|
||||
modificationTrackerImpl.incCounter()
|
||||
}
|
||||
},
|
||||
kotlinOutOfCodeBlockTrackerProducer = {
|
||||
if (isLanguageTrackerEnabled) {
|
||||
SimpleModificationTracker()
|
||||
} else {
|
||||
object : SimpleModificationTracker() {
|
||||
override fun getModificationCount(): Long {
|
||||
@Suppress("DEPRECATION")
|
||||
return modificationTrackerImpl.outOfCodeBlockModificationCount
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
psiModificationTrackerListener = {
|
||||
@Suppress("UnstableApiUsage")
|
||||
if (isLanguageTrackerEnabled) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onPsiModificationTrackerUpdate(customIncrement)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun treeChanged(event: PsiTreeChangeEventImpl) {
|
||||
assert(isLanguageTrackerEnabled)
|
||||
super.treeChanged(event)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project): KotlinCodeBlockModificationListener =
|
||||
project.getComponent(KotlinCodeBlockModificationListener::class.java)
|
||||
}
|
||||
}
|
||||
+22
-334
@@ -5,358 +5,46 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.caches.trackers
|
||||
|
||||
import com.intellij.lang.ASTNode
|
||||
import com.intellij.openapi.project.Project
|
||||
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.pom.PomManager
|
||||
import com.intellij.pom.PomModelAspect
|
||||
import com.intellij.pom.event.PomModelEvent
|
||||
import com.intellij.pom.event.PomModelListener
|
||||
import com.intellij.pom.tree.TreeAspect
|
||||
import com.intellij.pom.tree.events.TreeChangeEvent
|
||||
import com.intellij.pom.tree.events.impl.ChangeInfoImpl
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.PsiManagerImpl
|
||||
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.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
|
||||
val KOTLIN_CONSOLE_KEY = Key.create<Boolean>("kotlin.console")
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
/**
|
||||
* Tested in OutOfBlockModificationTestGenerated
|
||||
*/
|
||||
// BUNCH: 193
|
||||
class KotlinCodeBlockModificationListener(project: Project) : PsiTreeChangePreprocessor {
|
||||
private val treeAspect: TreeAspect = TreeAspect.getInstance(project)
|
||||
|
||||
private val modificationTrackerImpl: PsiModificationTrackerImpl =
|
||||
PsiModificationTracker.SERVICE.getInstance(project) as PsiModificationTrackerImpl
|
||||
|
||||
@Volatile
|
||||
private var kotlinModificationTracker: Long = 0
|
||||
|
||||
private val kotlinOutOfCodeBlockTrackerImpl: SimpleModificationTracker = SimpleModificationTracker()
|
||||
|
||||
val kotlinOutOfCodeBlockTracker: ModificationTracker = kotlinOutOfCodeBlockTrackerImpl
|
||||
|
||||
internal val perModuleOutOfCodeBlockTrackerUpdater = KotlinModuleOutOfCodeBlockModificationTracker.Updater(project)
|
||||
class KotlinCodeBlockModificationListener(project: Project) : KotlinCodeBlockModificationListenerCompat(project) {
|
||||
|
||||
init {
|
||||
val model = PomManager.getModel(project)
|
||||
val messageBusConnection = project.messageBus.connect(project)
|
||||
|
||||
val psiManager = PsiManager.getInstance(project) as PsiManagerImpl
|
||||
psiManager.addTreeChangePreprocessor(this)
|
||||
|
||||
model.addModelListener(object : PomModelListener {
|
||||
override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean {
|
||||
return aspect == treeAspect
|
||||
}
|
||||
|
||||
override fun modelChanged(event: PomModelEvent) {
|
||||
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
|
||||
val ktFile = changeSet.rootElement.psi.containingFile as? KtFile ?: return
|
||||
|
||||
incFileModificationCount(ktFile)
|
||||
|
||||
val changedElements = changeSet.changedElements
|
||||
|
||||
// skip change if it contains only virtual/fake change
|
||||
if (changedElements.isNotEmpty()) {
|
||||
// ignore formatting (whitespaces etc)
|
||||
if (isFormattingChange(changeSet) || isCommentChange(changeSet)) return
|
||||
}
|
||||
|
||||
val inBlockElements = inBlockModifications(changedElements)
|
||||
|
||||
val physical = ktFile.isPhysical
|
||||
if (inBlockElements.isEmpty()) {
|
||||
messageBusConnection.deliverImmediately()
|
||||
|
||||
if (physical && !isReplLine(ktFile.virtualFile) && ktFile !is KtTypeCodeFragment) {
|
||||
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, true)
|
||||
}
|
||||
|
||||
ktFile.incOutOfBlockModificationCount()
|
||||
} else if (physical) {
|
||||
inBlockElements.forEach { it.containingKtFile.addInBlockModifiedItem(it) }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, PsiModificationTracker.Listener {
|
||||
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
|
||||
init(
|
||||
TreeAspect.getInstance(project),
|
||||
incOCBCounter = { ktFile ->
|
||||
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
||||
} else {
|
||||
kotlinModificationTracker = kotlinTrackerInternalIDECount
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, true)
|
||||
},
|
||||
kotlinOutOfCodeBlockTrackerProducer = {
|
||||
SimpleModificationTracker()
|
||||
},
|
||||
psiModificationTrackerListener = {
|
||||
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
|
||||
}
|
||||
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onPsiModificationTrackerUpdate()
|
||||
}
|
||||
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onPsiModificationTrackerUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
private fun isReplLine(file: VirtualFile): Boolean {
|
||||
return file.getUserData(KOTLIN_CONSOLE_KEY) == true
|
||||
}
|
||||
|
||||
private fun incFileModificationCount(file: KtFile) {
|
||||
val tracker = file.getUserData(PER_FILE_MODIFICATION_TRACKER)
|
||||
?: file.putUserDataIfAbsent(PER_FILE_MODIFICATION_TRACKER, SimpleModificationTracker())
|
||||
tracker.incModificationCount()
|
||||
}
|
||||
|
||||
private fun inBlockModifications(elements: Array<ASTNode>): List<KtElement> {
|
||||
// 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
|
||||
|
||||
return elements.map { element ->
|
||||
val modificationScope = getInsideCodeBlockModificationScope(element.psi) ?: return emptyList()
|
||||
modificationScope.blockDeclaration
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSpecificChange(changeSet: TreeChangeEvent, precondition: (ASTNode?) -> Boolean): Boolean =
|
||||
changeSet.changedElements.all { changedElement ->
|
||||
val changesByElement = changeSet.getChangesByElement(changedElement)
|
||||
changesByElement.affectedChildren.all { affectedChild ->
|
||||
if (!precondition(affectedChild)) return@all false
|
||||
val changeByChild = changesByElement.getChangeByChild(affectedChild)
|
||||
return@all if (changeByChild is ChangeInfoImpl) {
|
||||
val oldChild = changeByChild.oldChild
|
||||
precondition(oldChild)
|
||||
} else false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCommentChange(changeSet: TreeChangeEvent): Boolean =
|
||||
isSpecificChange(changeSet) { it is PsiComment || it is KDoc }
|
||||
|
||||
private fun isFormattingChange(changeSet: TreeChangeEvent): Boolean =
|
||||
isSpecificChange(changeSet) { it is PsiWhiteSpace }
|
||||
|
||||
/**
|
||||
* Has to be aligned with [getInsideCodeBlockModificationScope] :
|
||||
*
|
||||
* result of analysis has to be reflected in dirty scope,
|
||||
* the only difference is whitespaces and comments
|
||||
*/
|
||||
fun getInsideCodeBlockModificationDirtyScope(element: PsiElement): PsiElement? {
|
||||
if (!element.isPhysical) return null
|
||||
// dirty scope for whitespaces and comments is the element itself
|
||||
if (element is PsiWhiteSpace || element is PsiComment || element is KDoc) return element
|
||||
|
||||
return getInsideCodeBlockModificationScope(element)?.blockDeclaration ?: null
|
||||
}
|
||||
|
||||
fun getInsideCodeBlockModificationScope(element: PsiElement): BlockModificationScopeElement? {
|
||||
val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
|
||||
if (lambda is KtLambdaExpression) {
|
||||
lambda.getTopmostParentOfType<KtSuperTypeCallEntry>()?.getTopmostParentOfType<KtClassOrObject>()?.let {
|
||||
return BlockModificationScopeElement(it, it)
|
||||
}
|
||||
}
|
||||
|
||||
val blockDeclaration =
|
||||
KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) as? KtDeclaration ?: return null
|
||||
// KtPsiUtil.getTopmostParentOfType<KtClassOrObject>(element) as? KtDeclaration ?: return null
|
||||
|
||||
// should not be local declaration
|
||||
if (KtPsiUtil.isLocal(blockDeclaration))
|
||||
return null
|
||||
|
||||
when (blockDeclaration) {
|
||||
is KtNamedFunction -> {
|
||||
// if (blockDeclaration.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE) {
|
||||
// topClassLikeDeclaration(blockDeclaration)?.let {
|
||||
// return BlockModificationScopeElement(it, it)
|
||||
// }
|
||||
// }
|
||||
if (blockDeclaration.hasBlockBody()) {
|
||||
// case like `fun foo(): String {...<caret>...}`
|
||||
return blockDeclaration.bodyExpression
|
||||
?.takeIf { it.isAncestor(element) }
|
||||
?.let { BlockModificationScopeElement(blockDeclaration, it) }
|
||||
} else if (blockDeclaration.hasDeclaredReturnType()) {
|
||||
// case like `fun foo(): String = b<caret>labla`
|
||||
return blockDeclaration.initializer
|
||||
?.takeIf { it.isAncestor(element) }
|
||||
?.let { BlockModificationScopeElement(blockDeclaration, it) }
|
||||
}
|
||||
}
|
||||
|
||||
is KtProperty -> {
|
||||
// if (blockDeclaration.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE) {
|
||||
// topClassLikeDeclaration(blockDeclaration)?.let {
|
||||
// return BlockModificationScopeElement(it, it)
|
||||
// }
|
||||
// }
|
||||
if (blockDeclaration.typeReference != null) {
|
||||
val accessors =
|
||||
blockDeclaration.accessors.map { it.initializer ?: it.bodyExpression } + blockDeclaration.initializer
|
||||
for (accessor in accessors) {
|
||||
accessor?.takeIf {
|
||||
it.isAncestor(element) &&
|
||||
// adding annotations to accessor is the same as change contract of property
|
||||
(element !is KtAnnotated || element.annotationEntries.isEmpty())
|
||||
}
|
||||
?.let { expression ->
|
||||
val declaration =
|
||||
KtPsiUtil.getTopmostParentOfTypes(blockDeclaration, KtClassOrObject::class.java) as? KtElement ?:
|
||||
// ktFile to check top level property declarations
|
||||
return null
|
||||
return BlockModificationScopeElement(declaration, expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is KtScriptInitializer -> {
|
||||
return (blockDeclaration.body as? KtCallExpression)
|
||||
?.lambdaArguments
|
||||
?.lastOrNull()
|
||||
?.getLambdaExpression()
|
||||
?.takeIf { it.isAncestor(element) }
|
||||
?.let { BlockModificationScopeElement(blockDeclaration, it) }
|
||||
}
|
||||
|
||||
is KtClassInitializer -> {
|
||||
blockDeclaration
|
||||
.takeIf { it.isAncestor(element) }
|
||||
?.let { ktClassInitializer ->
|
||||
(PsiTreeUtil.getParentOfType(blockDeclaration, KtClassOrObject::class.java))?.let {
|
||||
return BlockModificationScopeElement(it, ktClassInitializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is KtSecondaryConstructor -> {
|
||||
blockDeclaration
|
||||
?.takeIf {
|
||||
it.bodyExpression?.isAncestor(element) ?: false || it.getDelegationCallOrNull()?.isAncestor(element) ?: false
|
||||
}?.let { ktConstructor ->
|
||||
PsiTreeUtil.getParentOfType(blockDeclaration, KtClassOrObject::class.java)?.let {
|
||||
return BlockModificationScopeElement(it, ktConstructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
// is KtClassOrObject -> {
|
||||
// return when (element) {
|
||||
// is KtProperty, is KtNamedFunction -> {
|
||||
// if ((element as? KtModifierListOwner)?.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE)
|
||||
// BlockModificationScopeElement(blockDeclaration, blockDeclaration) else null
|
||||
// }
|
||||
// else -> null
|
||||
// }
|
||||
// }
|
||||
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
data class BlockModificationScopeElement(val blockDeclaration: KtElement, val element: KtElement)
|
||||
|
||||
fun isBlockDeclaration(declaration: KtDeclaration): Boolean {
|
||||
return BLOCK_DECLARATION_TYPES.any { it.isInstance(declaration) }
|
||||
}
|
||||
|
||||
private val BLOCK_DECLARATION_TYPES = arrayOf<Class<out KtDeclaration>>(
|
||||
KtProperty::class.java,
|
||||
KtNamedFunction::class.java,
|
||||
KtClassInitializer::class.java,
|
||||
KtSecondaryConstructor::class.java,
|
||||
KtScriptInitializer::class.java
|
||||
)
|
||||
|
||||
fun getInstance(project: Project): KotlinCodeBlockModificationListener = project.getServiceSafe()
|
||||
}
|
||||
}
|
||||
|
||||
private val PER_FILE_MODIFICATION_TRACKER = Key<SimpleModificationTracker>("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT")
|
||||
|
||||
val KtFile.perFileModificationTracker: ModificationTracker
|
||||
get() = putUserDataIfAbsent(PER_FILE_MODIFICATION_TRACKER, SimpleModificationTracker())
|
||||
|
||||
private val FILE_OUT_OF_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT")
|
||||
|
||||
val KtFile.outOfBlockModificationCount: Long by NotNullableUserDataProperty(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, 0)
|
||||
|
||||
private fun KtFile.incOutOfBlockModificationCount() {
|
||||
clearInBlockModifications()
|
||||
|
||||
val count = getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
|
||||
putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* inBlockModifications is a collection of block elements those have in-block modifications
|
||||
*/
|
||||
private val IN_BLOCK_MODIFICATIONS = Key<MutableCollection<KtElement>>("IN_BLOCK_MODIFICATIONS")
|
||||
private val FILE_IN_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_IN_BLOCK_MODIFICATION_COUNT")
|
||||
|
||||
val KtFile.inBlockModificationCount: Long by NotNullableUserDataProperty(FILE_IN_BLOCK_MODIFICATION_COUNT, 0)
|
||||
|
||||
val KtFile.inBlockModifications: Collection<KtElement>
|
||||
get() {
|
||||
val collection = getUserData(IN_BLOCK_MODIFICATIONS)
|
||||
return collection ?: emptySet()
|
||||
}
|
||||
|
||||
private fun KtFile.addInBlockModifiedItem(element: KtElement) {
|
||||
val collection = putUserDataIfAbsent(IN_BLOCK_MODIFICATIONS, mutableSetOf())
|
||||
synchronized(collection) {
|
||||
collection.add(element)
|
||||
}
|
||||
val count = getUserData(FILE_IN_BLOCK_MODIFICATION_COUNT) ?: 0
|
||||
putUserData(FILE_IN_BLOCK_MODIFICATION_COUNT, count + 1)
|
||||
}
|
||||
|
||||
fun KtFile.clearInBlockModifications() {
|
||||
val collection = getUserData(IN_BLOCK_MODIFICATIONS)
|
||||
collection?.let {
|
||||
synchronized(it) {
|
||||
it.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-60
@@ -27,7 +27,6 @@ import com.intellij.psi.impl.PsiTreeChangeEventImpl.PsiEventType.PROPERTY_CHANGE
|
||||
import com.intellij.psi.impl.PsiTreeChangePreprocessor
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
|
||||
@@ -39,46 +38,30 @@ val KOTLIN_CONSOLE_KEY = Key.create<Boolean>("kotlin.console")
|
||||
* Tested in OutOfBlockModificationTestGenerated
|
||||
*/
|
||||
// BUNCH: 193
|
||||
class KotlinCodeBlockModificationListener(
|
||||
modificationTracker: PsiModificationTracker,
|
||||
project: Project,
|
||||
private val treeAspect: TreeAspect
|
||||
) : PsiTreeChangePreprocessor {
|
||||
private val modificationTrackerImpl = modificationTracker as PsiModificationTrackerImpl
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
private val isLanguageTrackerEnabled = modificationTrackerImpl.isEnableLanguageTrackerCompat
|
||||
|
||||
// BUNCH: 191
|
||||
// When there're we no per-language trackers we had to increment global tracker first and process result afterward
|
||||
private val customIncrement = if (isLanguageTrackerEnabled) 0 else 1
|
||||
abstract class KotlinCodeBlockModificationListenerCompat(protected val project: Project) : PsiTreeChangePreprocessor {
|
||||
protected val modificationTrackerImpl: PsiModificationTrackerImpl =
|
||||
PsiModificationTracker.SERVICE.getInstance(project) as PsiModificationTrackerImpl
|
||||
|
||||
@Volatile
|
||||
private var kotlinModificationTracker: Long = 0
|
||||
protected var kotlinModificationTracker: Long = 0
|
||||
|
||||
private val kotlinOutOfCodeBlockTrackerImpl: SimpleModificationTracker = if (isLanguageTrackerEnabled) {
|
||||
SimpleModificationTracker()
|
||||
} else {
|
||||
object : SimpleModificationTracker() {
|
||||
override fun getModificationCount(): Long {
|
||||
@Suppress("DEPRECATION")
|
||||
return modificationTracker.outOfCodeBlockModificationCount
|
||||
}
|
||||
}
|
||||
}
|
||||
protected lateinit var kotlinOutOfCodeBlockTrackerImpl: SimpleModificationTracker
|
||||
|
||||
val kotlinOutOfCodeBlockTracker: ModificationTracker = kotlinOutOfCodeBlockTrackerImpl
|
||||
lateinit var kotlinOutOfCodeBlockTracker: ModificationTracker
|
||||
|
||||
internal val perModuleOutOfCodeBlockTrackerUpdater = KotlinModuleOutOfCodeBlockModificationTracker.Updater(project)
|
||||
|
||||
init {
|
||||
protected fun init(
|
||||
treeAspect: TreeAspect,
|
||||
incOCBCounter: (KtFile) -> Unit,
|
||||
psiModificationTrackerListener: PsiModificationTracker.Listener,
|
||||
kotlinOutOfCodeBlockTrackerProducer: () -> SimpleModificationTracker,
|
||||
isLanguageTrackerEnabled: Boolean = true,
|
||||
) {
|
||||
kotlinOutOfCodeBlockTrackerImpl = kotlinOutOfCodeBlockTrackerProducer()
|
||||
kotlinOutOfCodeBlockTracker = kotlinOutOfCodeBlockTrackerImpl
|
||||
val model = PomManager.getModel(project)
|
||||
val messageBusConnection = project.messageBus.connect(project)
|
||||
|
||||
if (isLanguageTrackerEnabled) {
|
||||
(PsiManager.getInstance(project) as PsiManagerImpl).addTreeChangePreprocessor(this)
|
||||
}
|
||||
|
||||
model.addModelListener(object : PomModelListener {
|
||||
override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean {
|
||||
return aspect == treeAspect
|
||||
@@ -105,14 +88,7 @@ class KotlinCodeBlockModificationListener(
|
||||
messageBusConnection.deliverImmediately()
|
||||
|
||||
if (physical && !isReplLine(ktFile.virtualFile) && ktFile !is KtTypeCodeFragment) {
|
||||
if (isLanguageTrackerEnabled) {
|
||||
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, true)
|
||||
} else {
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, false)
|
||||
// Increment counter and process changes in PsiModificationTracker.Listener
|
||||
modificationTrackerImpl.incCounter()
|
||||
}
|
||||
incOCBCounter(ktFile)
|
||||
}
|
||||
|
||||
ktFile.incOutOfBlockModificationCount()
|
||||
@@ -122,26 +98,13 @@ class KotlinCodeBlockModificationListener(
|
||||
}
|
||||
})
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, PsiModificationTracker.Listener {
|
||||
if (isLanguageTrackerEnabled) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onPsiModificationTrackerUpdate(customIncrement)
|
||||
})
|
||||
if (isLanguageTrackerEnabled) {
|
||||
(PsiManager.getInstance(project) as PsiManagerImpl).addTreeChangePreprocessor(this)
|
||||
}
|
||||
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, psiModificationTrackerListener)
|
||||
}
|
||||
|
||||
override fun treeChanged(event: PsiTreeChangeEventImpl) {
|
||||
assert(isLanguageTrackerEnabled)
|
||||
|
||||
if (!PsiModificationTrackerImpl.canAffectPsi(event)) {
|
||||
return
|
||||
}
|
||||
@@ -335,9 +298,6 @@ class KotlinCodeBlockModificationListener(
|
||||
KtSecondaryConstructor::class.java,
|
||||
KtScriptInitializer::class.java
|
||||
)
|
||||
|
||||
fun getInstance(project: Project): KotlinCodeBlockModificationListener =
|
||||
project.getComponent(KotlinCodeBlockModificationListener::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.codeInsight.daemon.ChangeLocalityDetector
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener.Companion.getInsideCodeBlockModificationDirtyScope
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListenerCompat.Companion.getInsideCodeBlockModificationDirtyScope
|
||||
|
||||
class KotlinChangeLocalityDetector : ChangeLocalityDetector {
|
||||
override fun getChangeHighlightingDirtyScopeFor(element: PsiElement): PsiElement? {
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.frontend.di.createContainerForBodyResolve
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.CodeFragmentAnalyzer
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.util.analyzeControlFlow
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListenerCompat
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.inBlockModificationCount
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
@@ -58,7 +59,7 @@ class ResolveElementCache(
|
||||
// data on any modification of the file
|
||||
!file.isPhysical -> file.modificationStamp
|
||||
|
||||
resolveElement is KtDeclaration && KotlinCodeBlockModificationListener.isBlockDeclaration(resolveElement) -> resolveElement.getModificationStamp()
|
||||
resolveElement is KtDeclaration && KotlinCodeBlockModificationListenerCompat.isBlockDeclaration(resolveElement) -> resolveElement.getModificationStamp()
|
||||
resolveElement is KtSuperTypeList -> resolveElement.modificationStamp
|
||||
else -> null
|
||||
}
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@ import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListenerCompat
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
@@ -95,7 +96,7 @@ class CompletionBindingContextProvider(project: Project) {
|
||||
val inStatement = position.findStatementInBlock()
|
||||
val block = inStatement?.parent as KtBlockExpression?
|
||||
val prevStatement = inStatement?.siblings(forward = false, withItself = false)?.firstIsInstanceOrNull<KtExpression>()
|
||||
val modificationScope = inStatement?.let { KotlinCodeBlockModificationListener.getInsideCodeBlockModificationScope(it)?.element }
|
||||
val modificationScope = inStatement?.let { KotlinCodeBlockModificationListenerCompat.getInsideCodeBlockModificationScope(it)?.element }
|
||||
|
||||
val psiElementsBeforeAndAfter = modificationScope?.let { collectPsiElementsBeforeAndAfter(modificationScope, inStatement) }
|
||||
|
||||
|
||||
@@ -8,17 +8,12 @@ package org.jetbrains.kotlin.idea.resolve
|
||||
import com.intellij.mock.MockProject
|
||||
import com.intellij.pom.PomModel
|
||||
import com.intellij.pom.tree.TreeAspect
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
||||
|
||||
internal fun createAndRegisterKotlinCodeBlockModificationListener(project: MockProject, pomModel: PomModel, treeAspect: TreeAspect) {
|
||||
project.registerService(PomModel::class.java, pomModel)
|
||||
project.picoContainer.registerComponentInstance(
|
||||
KotlinCodeBlockModificationListener(
|
||||
PsiModificationTracker.SERVICE.getInstance(project),
|
||||
project,
|
||||
treeAspect
|
||||
)
|
||||
KotlinCodeBlockModificationListener(project, treeAspect)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user