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
|
package org.jetbrains.kotlin.idea.caches.trackers
|
||||||
|
|
||||||
import com.intellij.lang.ASTNode
|
|
||||||
import com.intellij.openapi.project.Project
|
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.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.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.PsiModificationTracker
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||||
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
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")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tested in OutOfBlockModificationTestGenerated
|
* Tested in OutOfBlockModificationTestGenerated
|
||||||
*/
|
*/
|
||||||
// BUNCH: 193
|
// BUNCH: 193
|
||||||
class KotlinCodeBlockModificationListener(project: Project) : PsiTreeChangePreprocessor {
|
class KotlinCodeBlockModificationListener(project: Project) : KotlinCodeBlockModificationListenerCompat(project) {
|
||||||
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)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val model = PomManager.getModel(project)
|
init(
|
||||||
val messageBusConnection = project.messageBus.connect(project)
|
TreeAspect.getInstance(project),
|
||||||
|
incOCBCounter = { ktFile ->
|
||||||
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
|
|
||||||
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
||||||
} else {
|
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, true)
|
||||||
kotlinModificationTracker = kotlinTrackerInternalIDECount
|
},
|
||||||
|
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 {
|
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()
|
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.impl.PsiTreeChangePreprocessor
|
||||||
import com.intellij.psi.util.PsiModificationTracker
|
import com.intellij.psi.util.PsiModificationTracker
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
|
||||||
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
|
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
|
||||||
@@ -39,46 +38,30 @@ val KOTLIN_CONSOLE_KEY = Key.create<Boolean>("kotlin.console")
|
|||||||
* Tested in OutOfBlockModificationTestGenerated
|
* Tested in OutOfBlockModificationTestGenerated
|
||||||
*/
|
*/
|
||||||
// BUNCH: 193
|
// BUNCH: 193
|
||||||
class KotlinCodeBlockModificationListener(
|
abstract class KotlinCodeBlockModificationListenerCompat(protected val project: Project) : PsiTreeChangePreprocessor {
|
||||||
modificationTracker: PsiModificationTracker,
|
protected val modificationTrackerImpl: PsiModificationTrackerImpl =
|
||||||
project: Project,
|
PsiModificationTracker.SERVICE.getInstance(project) as PsiModificationTrackerImpl
|
||||||
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
|
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var kotlinModificationTracker: Long = 0
|
protected var kotlinModificationTracker: Long = 0
|
||||||
|
|
||||||
private val kotlinOutOfCodeBlockTrackerImpl: SimpleModificationTracker = if (isLanguageTrackerEnabled) {
|
protected lateinit var kotlinOutOfCodeBlockTrackerImpl: SimpleModificationTracker
|
||||||
SimpleModificationTracker()
|
|
||||||
} else {
|
|
||||||
object : SimpleModificationTracker() {
|
|
||||||
override fun getModificationCount(): Long {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
return modificationTracker.outOfCodeBlockModificationCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val kotlinOutOfCodeBlockTracker: ModificationTracker = kotlinOutOfCodeBlockTrackerImpl
|
lateinit var kotlinOutOfCodeBlockTracker: ModificationTracker
|
||||||
|
|
||||||
internal val perModuleOutOfCodeBlockTrackerUpdater = KotlinModuleOutOfCodeBlockModificationTracker.Updater(project)
|
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 model = PomManager.getModel(project)
|
||||||
val messageBusConnection = project.messageBus.connect(project)
|
val messageBusConnection = project.messageBus.connect(project)
|
||||||
|
|
||||||
if (isLanguageTrackerEnabled) {
|
|
||||||
(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
|
||||||
@@ -105,14 +88,7 @@ class KotlinCodeBlockModificationListener(
|
|||||||
messageBusConnection.deliverImmediately()
|
messageBusConnection.deliverImmediately()
|
||||||
|
|
||||||
if (physical && !isReplLine(ktFile.virtualFile) && ktFile !is KtTypeCodeFragment) {
|
if (physical && !isReplLine(ktFile.virtualFile) && ktFile !is KtTypeCodeFragment) {
|
||||||
if (isLanguageTrackerEnabled) {
|
incOCBCounter(ktFile)
|
||||||
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
|
||||||
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, true)
|
|
||||||
} else {
|
|
||||||
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, false)
|
|
||||||
// Increment counter and process changes in PsiModificationTracker.Listener
|
|
||||||
modificationTrackerImpl.incCounter()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ktFile.incOutOfBlockModificationCount()
|
ktFile.incOutOfBlockModificationCount()
|
||||||
@@ -122,26 +98,13 @@ class KotlinCodeBlockModificationListener(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@Suppress("UnstableApiUsage")
|
if (isLanguageTrackerEnabled) {
|
||||||
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, PsiModificationTracker.Listener {
|
(PsiManager.getInstance(project) as PsiManagerImpl).addTreeChangePreprocessor(this)
|
||||||
if (isLanguageTrackerEnabled) {
|
}
|
||||||
val kotlinTrackerInternalIDECount =
|
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, psiModificationTrackerListener)
|
||||||
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) {
|
override fun treeChanged(event: PsiTreeChangeEventImpl) {
|
||||||
assert(isLanguageTrackerEnabled)
|
|
||||||
|
|
||||||
if (!PsiModificationTrackerImpl.canAffectPsi(event)) {
|
if (!PsiModificationTrackerImpl.canAffectPsi(event)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -335,9 +298,6 @@ class KotlinCodeBlockModificationListener(
|
|||||||
KtSecondaryConstructor::class.java,
|
KtSecondaryConstructor::class.java,
|
||||||
KtScriptInitializer::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.codeInsight.daemon.ChangeLocalityDetector
|
||||||
import com.intellij.psi.PsiElement
|
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 {
|
class KotlinChangeLocalityDetector : ChangeLocalityDetector {
|
||||||
override fun getChangeHighlightingDirtyScopeFor(element: PsiElement): PsiElement? {
|
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.CodeFragmentAnalyzer
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.util.analyzeControlFlow
|
import org.jetbrains.kotlin.idea.caches.resolve.util.analyzeControlFlow
|
||||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
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.idea.caches.trackers.inBlockModificationCount
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||||
@@ -58,7 +59,7 @@ class ResolveElementCache(
|
|||||||
// data on any modification of the file
|
// data on any modification of the file
|
||||||
!file.isPhysical -> file.modificationStamp
|
!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
|
resolveElement is KtSuperTypeList -> resolveElement.modificationStamp
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -18,6 +18,7 @@ import org.jetbrains.annotations.TestOnly
|
|||||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
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.resolve.ResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
@@ -95,7 +96,7 @@ class CompletionBindingContextProvider(project: Project) {
|
|||||||
val inStatement = position.findStatementInBlock()
|
val inStatement = position.findStatementInBlock()
|
||||||
val block = inStatement?.parent as KtBlockExpression?
|
val block = inStatement?.parent as KtBlockExpression?
|
||||||
val prevStatement = inStatement?.siblings(forward = false, withItself = false)?.firstIsInstanceOrNull<KtExpression>()
|
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) }
|
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.mock.MockProject
|
||||||
import com.intellij.pom.PomModel
|
import com.intellij.pom.PomModel
|
||||||
import com.intellij.pom.tree.TreeAspect
|
import com.intellij.pom.tree.TreeAspect
|
||||||
import com.intellij.psi.util.PsiModificationTracker
|
|
||||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
||||||
|
|
||||||
internal fun createAndRegisterKotlinCodeBlockModificationListener(project: MockProject, pomModel: PomModel, treeAspect: TreeAspect) {
|
internal fun createAndRegisterKotlinCodeBlockModificationListener(project: MockProject, pomModel: PomModel, treeAspect: TreeAspect) {
|
||||||
project.registerService(PomModel::class.java, pomModel)
|
project.registerService(PomModel::class.java, pomModel)
|
||||||
project.picoContainer.registerComponentInstance(
|
project.picoContainer.registerComponentInstance(
|
||||||
KotlinCodeBlockModificationListener(
|
KotlinCodeBlockModificationListener(project, treeAspect)
|
||||||
PsiModificationTracker.SERVICE.getInstance(project),
|
|
||||||
project,
|
|
||||||
treeAspect
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user