Clean up of plugin-common.xml and convert components to services

This commit is contained in:
Vladimir Dolzhenko
2020-03-27 07:54:14 +00:00
parent 87006036be
commit 674f1d129f
41 changed files with 2027 additions and 4151 deletions
@@ -16,6 +16,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.project.rootManager
import com.intellij.openapi.roots.ModuleRootEvent
import com.intellij.openapi.roots.ModuleRootListener
import com.intellij.openapi.startup.StartupActivity
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
@@ -47,14 +48,14 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
class KotlinPackageContentModificationListener(private val project: Project) : Disposable {
val connection = project.messageBus.connect()
class KotlinPackageContentModificationListener : StartupActivity {
companion object {
val LOG = Logger.getInstance(this::class.java)
}
init {
override fun runActivity(project: Project) {
val connection = project.messageBus.connect(project)
connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
override fun before(events: MutableList<out VFileEvent>) = onEvents(events, false)
override fun after(events: List<VFileEvent>) = onEvents(events, true)
@@ -116,9 +117,6 @@ class KotlinPackageContentModificationListener(private val project: Project) : D
})
}
override fun dispose() {
connection.disconnect()
}
}
class KotlinPackageStatementPsiTreeChangePreprocessor(private val project: Project) : PsiTreeChangePreprocessor {
@@ -0,0 +1,420 @@
/*
* Copyright 2000-2019 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
import com.intellij.ProjectTopics
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.rootManager
import com.intellij.openapi.roots.ModuleRootEvent
import com.intellij.openapi.roots.ModuleRootListener
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.newvfs.BulkFileListener
import com.intellij.openapi.vfs.newvfs.events.*
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.PsiManagerEx
import com.intellij.psi.impl.PsiTreeChangeEventImpl
import com.intellij.psi.impl.PsiTreeChangePreprocessor
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.PerModulePackageCacheService.Companion.DEBUG_LOG_ENABLE_PerModulePackageCache
import org.jetbrains.kotlin.idea.caches.PerModulePackageCacheService.Companion.FULL_DROP_THRESHOLD
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.project.getModuleInfoByVirtualFile
import org.jetbrains.kotlin.idea.caches.project.getNullableModuleInfo
import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil
import org.jetbrains.kotlin.idea.util.getSourceRoot
import org.jetbrains.kotlin.idea.util.sourceRoot
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPackageDirective
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
class KotlinPackageContentModificationListener(private val project: Project) : Disposable {
val connection = project.messageBus.connect()
companion object {
val LOG = Logger.getInstance(this::class.java)
}
init {
connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
override fun before(events: MutableList<out VFileEvent>) = onEvents(events, false)
override fun after(events: List<VFileEvent>) = onEvents(events, true)
private fun isRelevant(event: VFileEvent): Boolean = when (event) {
is VFilePropertyChangeEvent -> false
is VFileCreateEvent -> true
is VFileMoveEvent -> true
is VFileDeleteEvent -> true
is VFileContentChangeEvent -> true
is VFileCopyEvent -> true
else -> {
LOG.warn("Unknown vfs event: ${event.javaClass}")
false
}
}
fun onEvents(events: List<VFileEvent>, isAfter: Boolean) {
val service = PerModulePackageCacheService.getInstance(project)
val fileManager = PsiManagerEx.getInstanceEx(project).fileManager
if (events.size >= FULL_DROP_THRESHOLD) {
service.onTooComplexChange()
} else {
events.asSequence()
.filter(::isRelevant)
.filter {
(it.isValid || it !is VFileCreateEvent) && it.file != null
}
.filter {
val vFile = it.file!!
vFile.isDirectory || vFile.fileType == KotlinFileType.INSTANCE
}
.filter {
// It expected that content change events will be duplicated with more precise PSI events and processed
// in KotlinPackageStatementPsiTreeChangePreprocessor, but events might have been missing if PSI view provider
// is absent.
if (it is VFileContentChangeEvent) {
isAfter && fileManager.findCachedViewProvider(it.file) == null
} else {
true
}
}
.filter {
when (val origin = it.requestor) {
is Project -> origin == project
is PsiManager -> origin.project == project
else -> true
}
}
.forEach { event -> service.notifyPackageChange(event) }
}
}
})
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
override fun rootsChanged(event: ModuleRootEvent) {
PerModulePackageCacheService.getInstance(project).onTooComplexChange()
}
})
}
override fun dispose() {
connection.disconnect()
}
}
class KotlinPackageStatementPsiTreeChangePreprocessor(private val project: Project) : PsiTreeChangePreprocessor {
override fun treeChanged(event: PsiTreeChangeEventImpl) {
val eFile = event.file ?: event.child as? PsiFile
if (eFile == null) {
LOG.debugIfEnabled(project, true) { "Got PsiEvent: $event without file" }
}
val file = eFile as? KtFile ?: return
when (event.code) {
PsiTreeChangeEventImpl.PsiEventType.CHILD_ADDED,
PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED,
PsiTreeChangeEventImpl.PsiEventType.CHILD_REPLACED,
PsiTreeChangeEventImpl.PsiEventType.CHILD_REMOVED -> {
val child = event.child ?: run {
LOG.debugIfEnabled(project, true) { "Got PsiEvent: $event without child" }
return
}
if (child.getParentOfType<KtPackageDirective>(false) != null)
ServiceManager.getService(project, PerModulePackageCacheService::class.java).notifyPackageChange(file)
}
PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED -> {
val parent = event.parent ?: run {
LOG.debugIfEnabled(project, true) { "Got PsiEvent: $event without parent" }
return
}
val childrenOfType = parent.getChildrenOfType<KtPackageDirective>()
if (
(!event.isGenericChange && (childrenOfType.any() || parent is KtPackageDirective)) ||
(childrenOfType.any { it.name.isEmpty() } && parent is KtFile)
) {
ServiceManager.getService(project, PerModulePackageCacheService::class.java).notifyPackageChange(file)
}
}
else -> {
}
}
}
companion object {
val LOG = Logger.getInstance(this::class.java)
}
}
private typealias ImplicitPackageData = MutableMap<FqName, MutableList<VirtualFile>>
class ImplicitPackagePrefixCache(private val project: Project) {
private val implicitPackageCache = ConcurrentHashMap<VirtualFile, ImplicitPackageData>()
fun getPrefix(sourceRoot: VirtualFile): FqName {
val implicitPackageMap = implicitPackageCache.getOrPut(sourceRoot) { analyzeImplicitPackagePrefixes(sourceRoot) }
return implicitPackageMap.keys.singleOrNull() ?: FqName.ROOT
}
internal fun clear() {
implicitPackageCache.clear()
}
private fun analyzeImplicitPackagePrefixes(sourceRoot: VirtualFile): MutableMap<FqName, MutableList<VirtualFile>> {
val result = mutableMapOf<FqName, MutableList<VirtualFile>>()
val ktFiles = sourceRoot.children.filter { it.fileType == KotlinFileType.INSTANCE }
for (ktFile in ktFiles) {
result.addFile(ktFile)
}
return result
}
private fun ImplicitPackageData.addFile(ktFile: VirtualFile) {
synchronized(this) {
val psiFile = PsiManager.getInstance(project).findFile(ktFile) as? KtFile ?: return
addPsiFile(psiFile, ktFile)
}
}
private fun ImplicitPackageData.addPsiFile(
psiFile: KtFile,
ktFile: VirtualFile
) = getOrPut(psiFile.packageFqName) { mutableListOf() }.add(ktFile)
private fun ImplicitPackageData.removeFile(file: VirtualFile) {
synchronized(this) {
for ((key, value) in this) {
if (value.remove(file)) {
if (value.isEmpty()) remove(key)
break
}
}
}
}
private fun ImplicitPackageData.updateFile(file: KtFile) {
synchronized(this) {
removeFile(file.virtualFile)
addPsiFile(file, file.virtualFile)
}
}
internal fun update(event: VFileEvent) {
when (event) {
is VFileCreateEvent -> checkNewFileInSourceRoot(event.file)
is VFileDeleteEvent -> checkDeletedFileInSourceRoot(event.file)
is VFileCopyEvent -> {
val newParent = event.newParent
if (newParent.isValid) {
checkNewFileInSourceRoot(newParent.findChild(event.newChildName))
}
}
is VFileMoveEvent -> {
checkNewFileInSourceRoot(event.file)
if (event.oldParent.getSourceRoot(project) == event.oldParent) {
implicitPackageCache[event.oldParent]?.removeFile(event.file)
}
}
}
}
private fun checkNewFileInSourceRoot(file: VirtualFile?) {
if (file == null) return
if (file.getSourceRoot(project) == file.parent) {
implicitPackageCache[file.parent]?.addFile(file)
}
}
private fun checkDeletedFileInSourceRoot(file: VirtualFile?) {
val directory = file?.parent
if (directory == null || !directory.isValid) return
if (directory.getSourceRoot(project) == directory) {
implicitPackageCache[directory]?.removeFile(file)
}
}
internal fun update(ktFile: KtFile) {
val parent = ktFile.virtualFile?.parent ?: return
if (ktFile.sourceRoot == parent) {
implicitPackageCache[parent]?.updateFile(ktFile)
}
}
}
class PerModulePackageCacheService(private val project: Project) : Disposable {
/*
* Actually an WeakMap<Module, SoftMap<ModuleSourceInfo, SoftMap<FqName, Boolean>>>
*/
private val cache = ContainerUtil.createConcurrentWeakMap<Module, ConcurrentMap<ModuleSourceInfo, ConcurrentMap<FqName, Boolean>>>()
private val implicitPackagePrefixCache = ImplicitPackagePrefixCache(project)
private val pendingVFileChanges: MutableSet<VFileEvent> = mutableSetOf()
private val pendingKtFileChanges: MutableSet<KtFile> = mutableSetOf()
private val projectScope = GlobalSearchScope.projectScope(project)
internal fun onTooComplexChange() {
clear()
}
private fun clear() {
synchronized(this) {
pendingVFileChanges.clear()
pendingKtFileChanges.clear()
cache.clear()
implicitPackagePrefixCache.clear()
}
}
internal fun notifyPackageChange(file: VFileEvent): Unit = synchronized(this) {
pendingVFileChanges += file
}
internal fun notifyPackageChange(file: KtFile): Unit = synchronized(this) {
pendingKtFileChanges += file
}
private fun invalidateCacheForModuleSourceInfo(moduleSourceInfo: ModuleSourceInfo) {
LOG.debugIfEnabled(project) { "Invalidated cache for $moduleSourceInfo" }
val perSourceInfoData = cache[moduleSourceInfo.module] ?: return
val dataForSourceInfo = perSourceInfoData[moduleSourceInfo] ?: return
dataForSourceInfo.clear()
}
private fun checkPendingChanges() = synchronized(this) {
if (pendingVFileChanges.size + pendingKtFileChanges.size >= FULL_DROP_THRESHOLD) {
onTooComplexChange()
} else {
pendingVFileChanges.processPending { event ->
val vfile = event.file ?: return@processPending
// When VirtualFile !isValid (deleted for example), it impossible to use getModuleInfoByVirtualFile
// For directory we must check both is it in some sourceRoot, and is it contains some sourceRoot
if (vfile.isDirectory || !vfile.isValid) {
for ((module, data) in cache) {
val sourceRootUrls = module.rootManager.sourceRootUrls
if (sourceRootUrls.any { url ->
vfile.containedInOrContains(url)
}) {
LOG.debugIfEnabled(project) { "Invalidated cache for $module" }
data.clear()
}
}
} else {
val infoByVirtualFile = getModuleInfoByVirtualFile(project, vfile)
if (infoByVirtualFile == null || infoByVirtualFile !is ModuleSourceInfo) {
LOG.debugIfEnabled(project) { "Skip $vfile as it has mismatched ModuleInfo=$infoByVirtualFile" }
}
(infoByVirtualFile as? ModuleSourceInfo)?.let {
invalidateCacheForModuleSourceInfo(it)
}
}
implicitPackagePrefixCache.update(event)
}
pendingKtFileChanges.processPending { file ->
if (file.virtualFile != null && file.virtualFile !in projectScope) {
LOG.debugIfEnabled(project) {
"Skip $file without vFile, or not in scope: ${file.virtualFile?.let { it !in projectScope }}"
}
return@processPending
}
val nullableModuleInfo = file.getNullableModuleInfo()
(nullableModuleInfo as? ModuleSourceInfo)?.let { invalidateCacheForModuleSourceInfo(it) }
if (nullableModuleInfo == null || nullableModuleInfo !is ModuleSourceInfo) {
LOG.debugIfEnabled(project) { "Skip $file as it has mismatched ModuleInfo=$nullableModuleInfo" }
}
implicitPackagePrefixCache.update(file)
}
}
}
private inline fun <T> MutableCollection<T>.processPending(crossinline body: (T) -> Unit) {
this.removeIf { value ->
try {
body(value)
} catch (pce: ProcessCanceledException) {
throw pce
} catch (exc: Exception) {
// Log and proceed. Otherwise pending object processing won't be cleared and exception will be thrown forever.
LOG.error(exc)
}
return@removeIf true
}
}
private fun VirtualFile.containedInOrContains(root: String) =
(VfsUtilCore.isEqualOrAncestor(url, root)
|| isDirectory && VfsUtilCore.isEqualOrAncestor(root, url))
fun packageExists(packageFqName: FqName, moduleInfo: ModuleSourceInfo): Boolean {
val module = moduleInfo.module
checkPendingChanges()
val perSourceInfoCache = cache.getOrPut(module) {
ContainerUtil.createConcurrentSoftMap()
}
val cacheForCurrentModuleInfo = perSourceInfoCache.getOrPut(moduleInfo) {
ContainerUtil.createConcurrentSoftMap()
}
return cacheForCurrentModuleInfo.getOrPut(packageFqName) {
val packageExists = PackageIndexUtil.packageExists(packageFqName, moduleInfo.contentScope(), project)
LOG.debugIfEnabled(project) { "Computed cache value for $packageFqName in $moduleInfo is $packageExists" }
packageExists
}
}
fun getImplicitPackagePrefix(sourceRoot: VirtualFile): FqName {
checkPendingChanges()
return implicitPackagePrefixCache.getPrefix(sourceRoot)
}
override fun dispose() {
clear()
}
companion object {
const val FULL_DROP_THRESHOLD = 1000
private val LOG = Logger.getInstance(this::class.java)
fun getInstance(project: Project): PerModulePackageCacheService =
ServiceManager.getService(project, PerModulePackageCacheService::class.java)
var Project.DEBUG_LOG_ENABLE_PerModulePackageCache: Boolean
by NotNullableUserDataProperty<Project, Boolean>(Key.create("debug.PerModulePackageCache"), false)
}
}
private fun Logger.debugIfEnabled(project: Project, withCurrentTrace: Boolean = false, message: () -> String) {
if (ApplicationManager.getApplication().isUnitTestMode && project.DEBUG_LOG_ENABLE_PerModulePackageCache) {
val msg = message()
if (withCurrentTrace) {
val e = Exception().apply { fillInStackTrace() }
this.debug(msg, e)
} else {
this.debug(msg)
}
}
}
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.idea.caches.project
import com.intellij.ide.highlighter.ArchiveFileType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.fileTypes.FileTypeEvent
import com.intellij.openapi.fileTypes.FileTypeListener
import com.intellij.openapi.fileTypes.FileTypeManager
@@ -23,15 +22,16 @@ import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
class LibraryModificationTracker(project: Project) : SimpleModificationTracker() {
companion object {
@JvmStatic
fun getInstance(project: Project) = ServiceManager.getService(project, LibraryModificationTracker::class.java)!!
fun getInstance(project: Project) = project.getServiceSafe(LibraryModificationTracker::class.java)
}
init {
val connection = project.messageBus.connect()
val connection = project.messageBus.connect(project)
connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
override fun after(events: List<VFileEvent>) {
events.filter(::isRelevantEvent).let { createEvents ->
@@ -72,7 +72,7 @@ class KotlinCodeBlockModificationListener(
init {
val model = PomManager.getModel(project)
val messageBusConnection = project.messageBus.connect()
val messageBusConnection = project.messageBus.connect(project)
if (isLanguageTrackerEnabled) {
(PsiManager.getInstance(project) as PsiManagerImpl).addTreeChangePreprocessor(this)
@@ -175,7 +175,7 @@ class KotlinCodeBlockModificationListener(
// 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.mapNotNull { element ->
return elements.map { element ->
val modificationScope = getInsideCodeBlockModificationScope(element.psi) ?: return emptyList()
modificationScope.blockDeclaration
}
@@ -0,0 +1,362 @@
/*
* Copyright 2010-2019 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.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")
/**
* Tested in OutOfBlockModificationTestGenerated
*/
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)
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
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
} else {
kotlinModificationTracker = kotlinTrackerInternalIDECount
}
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(KotlinCodeBlockModificationListener::class.java)
}
}
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()
}
}
}
@@ -95,7 +95,7 @@ class KotlinModuleOutOfCodeBlockModificationTracker private constructor(private
}
}
internal fun onPsiModificationTrackerUpdate(customIncrement: Int) {
internal fun onPsiModificationTrackerUpdate(customIncrement: Int = 0) {
val newModCount = kotlinOfOfCodeBlockTracker.modificationCount
val affectedModule = lastAffectedModule
if (affectedModule != null && newModCount == lastAffectedModuleModCount + customIncrement) {
@@ -50,4 +50,7 @@ inline fun invokeLater(crossinline action: () -> Unit) {
ApplicationManager.getApplication().invokeLater { action() }
}
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
fun <T> Project.getServiceSafe(serviceClass: Class<T>): T =
this.getService(serviceClass) ?: error("Unable to locate service ${serviceClass.name}")
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.util.application
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.openapi.progress.ProgressManager
@@ -52,4 +53,7 @@ inline fun invokeLater(crossinline action: () -> Unit) {
ApplicationManager.getApplication().invokeLater { action() }
}
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
fun <T> Project.getServiceSafe(serviceClass: Class<T>): T =
ServiceManager.getService(this, serviceClass) ?: error("Unable to locate service ${serviceClass.name}")
@@ -50,4 +50,7 @@ inline fun invokeLater(crossinline action: () -> Unit) {
ApplicationManager.getApplication().invokeLater { action() }
}
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
fun <T> Project.getServiceSafe(serviceClass: Class<T>) =
this.getService(serviceClass) ?: error("Unable to locate service ${serviceClass.name}")
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.util.application
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.openapi.progress.ProgressManager
@@ -52,4 +53,10 @@ inline fun invokeLater(crossinline action: () -> Unit) {
ApplicationManager.getApplication().invokeLater { action() }
}
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
fun <T> Project.getServiceSafe(serviceClass: Class<T>): T =
ServiceManager.getService(this, serviceClass) ?: error("Unable to locate service ${serviceClass.name}")
fun <T> Project.getServiceIfCreated(serviceClass: Class<T>): T? =
ServiceManager.getServiceIfCreated(this, serviceClass)
@@ -126,7 +126,7 @@ class BasicCompletionSession(
if (parameters.isAutoPopup) {
collector.addLookupElementPostProcessor { lookupElement ->
lookupElement.putUserData(LookupCancelWatcher.AUTO_POPUP_AT, position.startOffset)
lookupElement.putUserData(LookupCancelService.AUTO_POPUP_AT, position.startOffset)
lookupElement
}
@@ -533,7 +533,7 @@ class BasicCompletionSession(
}
private fun wasAutopopupRecentlyCancelled(parameters: CompletionParameters) =
LookupCancelWatcher.getInstance(project).wasAutoPopupRecentlyCancelled(parameters.editor, position.startOffset)
LookupCancelService.getInstance(project).wasAutoPopupRecentlyCancelled(parameters.editor, position.startOffset)
private val KEYWORDS_ONLY = object : CompletionKind {
override val descriptorKindFilter: DescriptorKindFilter?
@@ -19,6 +19,7 @@ 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.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtElement
@@ -46,7 +47,7 @@ class CompletionBindingContextProvider(project: Project) {
companion object {
fun getInstance(project: Project): CompletionBindingContextProvider =
project.getComponent(CompletionBindingContextProvider::class.java)
project.getServiceSafe(CompletionBindingContextProvider::class.java)
var ENABLED = true
}
@@ -0,0 +1,100 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.lookup.LookupEvent
import com.intellij.codeInsight.lookup.LookupListener
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.event.CaretEvent
import com.intellij.openapi.editor.event.CaretListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
class LookupCancelService {
internal class Reminiscence(editor: Editor, offset: Int) {
var editor: Editor? = editor
private var marker: RangeMarker? = editor.document.createRangeMarker(offset, offset)
// forget about auto-popup cancellation when the caret is moved to the start or before it
private var editorListener: CaretListener? = object : CaretListener {
override fun caretPositionChanged(e: CaretEvent) {
if (!marker!!.isValid || editor.logicalPositionToOffset(e.newPosition) <= offset) {
dispose()
}
}
}
init {
ApplicationManager.getApplication()!!.assertIsDispatchThread()
editor.caretModel.addCaretListener(editorListener!!)
}
fun matches(editor: Editor, offset: Int): Boolean {
return editor == this.editor && marker?.startOffset == offset
}
fun dispose() {
ApplicationManager.getApplication()!!.assertIsDispatchThread()
if (marker != null) {
editor!!.caretModel.removeCaretListener(editorListener!!)
marker = null
editor = null
editorListener = null
}
}
}
internal val lookupCancelListener = object : LookupListener {
override fun lookupCanceled(event: LookupEvent) {
val lookup = event.lookup
if (event.isCanceledExplicitly && lookup.isCompletion) {
val offset = lookup.currentItem?.getUserData(LookupCancelService.AUTO_POPUP_AT)
if (offset != null) {
lastReminiscence?.dispose()
if (offset <= lookup.editor.document.textLength) {
lastReminiscence = Reminiscence(lookup.editor, offset)
}
}
}
}
}
internal fun disposeLastReminiscence(editor: Editor) {
if (lastReminiscence?.editor == editor) {
lastReminiscence!!.dispose()
lastReminiscence = null
}
}
private var lastReminiscence: Reminiscence? = null
companion object {
fun getInstance(project: Project): LookupCancelService = project.getServiceSafe(LookupCancelService::class.java)
fun getServiceIfCreated(project: Project): LookupCancelService? = project.getServiceIfCreated(LookupCancelService::class.java)
val AUTO_POPUP_AT = Key<Int>("LookupCancelService.AUTO_POPUP_AT")
}
fun wasAutoPopupRecentlyCancelled(editor: Editor, offset: Int): Boolean {
return lastReminiscence?.matches(editor, offset) ?: false
}
}
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.completion
typealias LookupCancelService = LookupCancelWatcher
@@ -22,83 +22,18 @@ import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupEvent
import com.intellij.codeInsight.lookup.LookupListener
import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ProjectComponent
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.event.CaretEvent
import com.intellij.openapi.editor.event.CaretListener
import com.intellij.openapi.editor.event.EditorFactoryEvent
import com.intellij.openapi.editor.event.EditorFactoryListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.startup.StartupActivity
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector.completionStatsData
import org.jetbrains.kotlin.idea.statistics.FinishReasonStats
class LookupCancelWatcher(val project: Project) : ProjectComponent {
private class Reminiscence(editor: Editor, offset: Int) {
var editor: Editor? = editor
private var marker: RangeMarker? = editor.document.createRangeMarker(offset, offset)
class LookupCancelWatcher : StartupActivity {
// forget about auto-popup cancellation when the caret is moved to the start or before it
private var editorListener: CaretListener? = object : CaretListener {
override fun caretPositionChanged(e: CaretEvent) {
if (!marker!!.isValid || editor.logicalPositionToOffset(e.newPosition) <= offset) {
dispose()
}
}
}
init {
ApplicationManager.getApplication()!!.assertIsDispatchThread()
editor.caretModel.addCaretListener(editorListener!!)
}
fun matches(editor: Editor, offset: Int): Boolean {
return editor == this.editor && marker?.startOffset == offset
}
fun dispose() {
ApplicationManager.getApplication()!!.assertIsDispatchThread()
if (marker != null) {
editor!!.caretModel.removeCaretListener(editorListener!!)
marker = null
editor = null
editorListener = null
}
}
}
private var lastReminiscence: Reminiscence? = null
companion object {
fun getInstance(project: Project): LookupCancelWatcher = project.getComponent(LookupCancelWatcher::class.java)!!
val AUTO_POPUP_AT = Key<Int>("LookupCancelWatcher.AUTO_POPUP_AT")
}
fun wasAutoPopupRecentlyCancelled(editor: Editor, offset: Int): Boolean {
return lastReminiscence?.matches(editor, offset) ?: false
}
private val lookupCancelListener = object : LookupListener {
override fun lookupCanceled(event: LookupEvent) {
val lookup = event.lookup
if (event.isCanceledExplicitly && lookup.isCompletion) {
val offset = lookup.currentItem?.getUserData(AUTO_POPUP_AT)
if (offset != null) {
lastReminiscence?.dispose()
if (offset <= lookup.editor.document.textLength) {
lastReminiscence = Reminiscence(lookup.editor, offset)
}
}
}
}
}
override fun initComponent() {
override fun runActivity(project: Project) {
CompletionPhaseListener.TOPIC.subscribe(project, CompletionPhaseListener { isCompletionRunning ->
if (isCompletionRunning) {
if (completionStatsData != null) {
@@ -117,9 +52,7 @@ class LookupCancelWatcher(val project: Project) : ProjectComponent {
EditorFactory.getInstance().addEditorFactoryListener(
object : EditorFactoryListener {
override fun editorReleased(event: EditorFactoryEvent) {
if (lastReminiscence?.editor == event.editor) {
lastReminiscence!!.dispose()
}
LookupCancelService.getServiceIfCreated(project)?.disposeLastReminiscence(event.editor)
}
},
project
@@ -128,7 +61,7 @@ class LookupCancelWatcher(val project: Project) : ProjectComponent {
LookupManager.getInstance(project).addPropertyChangeListener { event ->
if (event.propertyName == LookupManager.PROP_ACTIVE_LOOKUP) {
val lookup = event.newValue as Lookup?
lookup?.addLookupListener(lookupCancelListener)
lookup?.addLookupListener(LookupCancelService.getInstance(project).lookupCancelListener)
lookup?.addLookupListener(object : LookupListener {
override fun lookupShown(event: LookupEvent) {
completionStatsData = completionStatsData?.copy(shownTime = System.currentTimeMillis())
@@ -0,0 +1,158 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.completion
import com.intellij.application.subscribe
import com.intellij.codeInsight.completion.CompletionPhaseListener
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupEvent
import com.intellij.codeInsight.lookup.LookupListener
import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ProjectComponent
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.event.CaretEvent
import com.intellij.openapi.editor.event.CaretListener
import com.intellij.openapi.editor.event.EditorFactoryEvent
import com.intellij.openapi.editor.event.EditorFactoryListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector.completionStatsData
import org.jetbrains.kotlin.idea.statistics.FinishReasonStats
class LookupCancelWatcher(val project: Project) : ProjectComponent {
private class Reminiscence(editor: Editor, offset: Int) {
var editor: Editor? = editor
private var marker: RangeMarker? = editor.document.createRangeMarker(offset, offset)
// forget about auto-popup cancellation when the caret is moved to the start or before it
private var editorListener: CaretListener? = object : CaretListener {
override fun caretPositionChanged(e: CaretEvent) {
if (!marker!!.isValid || editor.logicalPositionToOffset(e.newPosition) <= offset) {
dispose()
}
}
}
init {
ApplicationManager.getApplication()!!.assertIsDispatchThread()
editor.caretModel.addCaretListener(editorListener!!)
}
fun matches(editor: Editor, offset: Int): Boolean {
return editor == this.editor && marker?.startOffset == offset
}
fun dispose() {
ApplicationManager.getApplication()!!.assertIsDispatchThread()
if (marker != null) {
editor!!.caretModel.removeCaretListener(editorListener!!)
marker = null
editor = null
editorListener = null
}
}
}
private var lastReminiscence: Reminiscence? = null
companion object {
fun getInstance(project: Project): LookupCancelWatcher = project.getComponent(LookupCancelWatcher::class.java)!!
val AUTO_POPUP_AT = Key<Int>("LookupCancelWatcher.AUTO_POPUP_AT")
}
fun wasAutoPopupRecentlyCancelled(editor: Editor, offset: Int): Boolean {
return lastReminiscence?.matches(editor, offset) ?: false
}
private val lookupCancelListener = object : LookupListener {
override fun lookupCanceled(event: LookupEvent) {
val lookup = event.lookup
if (event.isCanceledExplicitly && lookup.isCompletion) {
val offset = lookup.currentItem?.getUserData(AUTO_POPUP_AT)
if (offset != null) {
lastReminiscence?.dispose()
if (offset <= lookup.editor.document.textLength) {
lastReminiscence = Reminiscence(lookup.editor, offset)
}
}
}
}
}
override fun initComponent() {
CompletionPhaseListener.TOPIC.subscribe(project, CompletionPhaseListener { isCompletionRunning ->
if (isCompletionRunning) {
if (completionStatsData != null) {
completionStatsData = completionStatsData?.copy(finishReason = FinishReasonStats.INTERRUPTED)
CompletionFUSCollector.log(completionStatsData)
completionStatsData = null
}
completionStatsData = CompletionFUSCollector.CompletionStatsData(System.currentTimeMillis())
}
if (!isCompletionRunning) {
completionStatsData = completionStatsData?.copy(finishTime = System.currentTimeMillis())
}
})
EditorFactory.getInstance().addEditorFactoryListener(
object : EditorFactoryListener {
override fun editorReleased(event: EditorFactoryEvent) {
if (lastReminiscence?.editor == event.editor) {
lastReminiscence!!.dispose()
}
}
},
project
)
LookupManager.getInstance(project).addPropertyChangeListener { event ->
if (event.propertyName == LookupManager.PROP_ACTIVE_LOOKUP) {
val lookup = event.newValue as Lookup?
lookup?.addLookupListener(lookupCancelListener)
lookup?.addLookupListener(object : LookupListener {
override fun lookupShown(event: LookupEvent) {
completionStatsData = completionStatsData?.copy(shownTime = System.currentTimeMillis())
}
override fun lookupCanceled(event: LookupEvent) {
completionStatsData = completionStatsData?.copy(
finishReason = if (event.isCanceledExplicitly) FinishReasonStats.CANCELLED else FinishReasonStats.HIDDEN
)
CompletionFUSCollector.log(completionStatsData)
completionStatsData = null
}
override fun itemSelected(event: LookupEvent) {
val eventLookup = event.lookup
val lookupIndex = eventLookup.items.indexOf(eventLookup.currentItem)
if (lookupIndex >= 0) completionStatsData = completionStatsData?.copy(selectedItem = lookupIndex)
completionStatsData = completionStatsData?.copy(finishReason = FinishReasonStats.DONE)
CompletionFUSCollector.log(completionStatsData)
completionStatsData = null
}
})
}
}
}
}
@@ -232,7 +232,7 @@ internal abstract class AbstractScriptConfigurationManager(
}
init {
val connection = project.messageBus.connect()
val connection = project.messageBus.connect(project)
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
override fun rootsChanged(event: ModuleRootEvent) {
clearClassRootsCaches()
@@ -13,8 +13,8 @@ import com.intellij.testFramework.runInEdtAndWait
import com.intellij.util.concurrency.FutureResult
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent.MigrationTestState
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService.MigrationTestState
import org.jetbrains.kotlin.idea.configuration.MigrationInfo
import org.jetbrains.plugins.gradle.tooling.annotation.TargetVersions
import org.junit.Assert
@@ -98,7 +98,7 @@ class GradleMigrateTest : GradleImportingTestCase() {
}
val importResult = FutureResult<MigrationTestState?>()
val migrationProjectComponent = KotlinMigrationProjectComponent.getInstanceIfNotDisposed(myProject)
val migrationProjectComponent = KotlinMigrationProjectService.getInstanceIfNotDisposed(myProject)
?: error("Disposed project")
migrationProjectComponent.setImportFinishListener { migrationState ->
@@ -1,39 +0,0 @@
/*
* Copyright 2010-2019 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.compiler.configuration
import com.intellij.compiler.server.BuildProcessParametersProvider
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
import org.jetbrains.kotlin.config.IncrementalCompilation
import org.jetbrains.kotlin.idea.PluginStartupComponent
class KotlinBuildProcessParametersProvider(private val project: Project) : BuildProcessParametersProvider() {
override fun getVMArguments(): MutableList<String> {
val compilerWorkspaceSettings = KotlinCompilerWorkspaceSettings.getInstance(project)
val res = arrayListOf<String>()
if (compilerWorkspaceSettings.preciseIncrementalEnabled) {
res.add("-D" + IncrementalCompilation.INCREMENTAL_COMPILATION_JVM_PROPERTY + "=true")
}
if (compilerWorkspaceSettings.incrementalCompilationForJsEnabled) {
res.add("-D" + IncrementalCompilation.INCREMENTAL_COMPILATION_JS_PROPERTY + "=true")
}
if (compilerWorkspaceSettings.enableDaemon) {
res.add("-Dkotlin.daemon.enabled")
}
if (Registry.`is`("kotlin.jps.instrument.bytecode", false)) {
res.add("-Dkotlin.jps.instrument.bytecode=true")
}
PluginStartupComponent.getInstance().aliveFlagPath.let {
if (!it.isBlank()) {
// TODO: consider taking the property name from compiler/daemon/common (check whether dependency will be not too heavy)
res.add("-Dkotlin.daemon.client.alive.path=\"$it\"")
}
}
return res
}
}
@@ -25,7 +25,7 @@ import org.jetbrains.kotlin.idea.configuration.ui.KotlinConfigurationCheckerServ
class KotlinExternalSystemSyncListener : ExternalSystemTaskNotificationListenerAdapter() {
override fun onStart(id: ExternalSystemTaskId, workingDir: String) {
val project = id.findResolvedProject() ?: return
KotlinMigrationProjectComponent.getInstanceIfNotDisposed(project)?.onImportAboutToStart()
KotlinMigrationProjectService.getInstanceIfNotDisposed(project)?.onImportAboutToStart()
KotlinConfigurationCheckerService.getInstanceIfNotDisposed(project)?.syncStarted()
}
@@ -38,7 +38,7 @@ class KotlinConfigurationCheckerComponent(val project: Project) : ProjectCompone
NotificationsConfiguration.getNotificationsConfiguration()
.register(CONFIGURE_NOTIFICATION_GROUP_ID, NotificationDisplayType.STICKY_BALLOON, true)
val connection = project.messageBus.connect()
val connection = project.messageBus.connect(project)
connection.subscribe(ProjectDataImportListener.TOPIC, ProjectDataImportListener {
notifyOutdatedBundledCompilerIfNecessary(project)
})
@@ -18,6 +18,8 @@ package org.jetbrains.kotlin.idea.configuration.ui
import com.intellij.notification.NotificationDisplayType
import com.intellij.notification.NotificationsConfiguration
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.ReadAction.nonBlocking
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener
@@ -28,6 +30,9 @@ import org.jetbrains.kotlin.idea.configuration.getModulesWithKotlinFiles
import org.jetbrains.kotlin.idea.configuration.notifyOutdatedBundledCompilerIfNecessary
import org.jetbrains.kotlin.idea.configuration.ui.notifications.notifyKotlinStyleUpdateIfNeeded
import org.jetbrains.kotlin.idea.project.getAndCacheLanguageLevelByDependencies
import org.jetbrains.kotlin.idea.util.ProgressIndicatorUtils
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
import java.util.concurrent.Callable
import java.util.concurrent.atomic.AtomicInteger
class KotlinConfigurationCheckerStartupActivity : StartupActivity {
@@ -53,15 +58,24 @@ class KotlinConfigurationCheckerService(val project: Project) {
private val syncDepth = AtomicInteger()
fun performProjectPostOpenActions() {
nonBlocking {
val modulesWithKotlinFiles = getModulesWithKotlinFiles(project)
for (module in modulesWithKotlinFiles) {
module.getAndCacheLanguageLevelByDependencies()
}
}
nonBlocking(Callable {
return@Callable getModulesWithKotlinFiles(project)
})
.inSmartMode(project)
.expireWith(project)
.coalesceBy(this)
.finishOnUiThread(ModalityState.any()) { modulesWithKotlinFiles ->
val promise = ApplicationManager.getApplication().executeOnPooledThread {
for (module in modulesWithKotlinFiles) {
runReadAction {
module.getAndCacheLanguageLevelByDependencies()
}
}
}
if (isUnitTestMode()) {
ProgressIndicatorUtils.awaitWithCheckCanceled(promise)
}
}
.submit(AppExecutorUtil.getAppExecutorService())
}
@@ -10,7 +10,7 @@ import com.intellij.openapi.project.Project
import org.jetbrains.idea.maven.project.MavenImportListener
import org.jetbrains.idea.maven.project.MavenProject
import org.jetbrains.idea.maven.project.MavenProjectsManager
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService
import org.jetbrains.kotlin.idea.configuration.notifyOutdatedBundledCompilerIfNecessary
class MavenImportListener(val project: Project) : MavenProjectsManager.Listener {
@@ -19,7 +19,7 @@ class MavenImportListener(val project: Project) : MavenProjectsManager.Listener
MavenImportListener.TOPIC,
MavenImportListener { _: Collection<MavenProject>, _: List<Module> ->
notifyOutdatedBundledCompilerIfNecessary(project)
KotlinMigrationProjectComponent.getInstanceIfNotDisposed(project)?.onImportFinished()
KotlinMigrationProjectService.getInstanceIfNotDisposed(project)?.onImportFinished()
}
)
@@ -27,6 +27,6 @@ class MavenImportListener(val project: Project) : MavenProjectsManager.Listener
}
override fun projectsScheduled() {
KotlinMigrationProjectComponent.getInstanceIfNotDisposed(project)?.onImportAboutToStart()
KotlinMigrationProjectService.getInstanceIfNotDisposed(project)?.onImportAboutToStart()
}
}
@@ -13,8 +13,9 @@ import com.intellij.testFramework.runInEdtAndWait
import com.intellij.util.concurrency.FutureResult
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService
import org.jetbrains.kotlin.idea.configuration.MigrationInfo
import org.jetbrains.kotlin.idea.configuration.MigrationTestState
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
import org.junit.Assert
import org.junit.runner.RunWith
@@ -106,8 +107,8 @@ class MavenMigrateTest : MavenImportingTestCase() {
}
}
val importResult = FutureResult<KotlinMigrationProjectComponent.MigrationTestState?>()
val migrationProjectComponent = KotlinMigrationProjectComponent.getInstanceIfNotDisposed(myProject)
val importResult = FutureResult<MigrationTestState?>()
val migrationProjectComponent = KotlinMigrationProjectService.getInstanceIfNotDisposed(myProject)
?: error("Disposed project")
migrationProjectComponent.setImportFinishListener { migrationState ->
+5 -30
View File
@@ -1,26 +1,4 @@
<idea-plugin>
<project-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
</component>
<component>
<interface-class>org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider</interface-class>
<implementation-class>org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsModel</implementation-class>
</component>
</project-components>
<application-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.versions.KotlinUpdatePluginComponent</implementation-class>
@@ -216,14 +194,6 @@
</actions>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService" />
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
<applicationService serviceInterface="org.jetbrains.kotlin.idea.KotlinIconProviderService"
serviceImplementation="org.jetbrains.kotlin.idea.KotlinIdeFileIconProviderService"/>
@@ -319,6 +289,8 @@
<projectService serviceInterface="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettings"
serviceImplementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettings"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsModel"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerWorkspaceSettings"/>
<projectService serviceInterface="org.jetbrains.kotlin.asJava.classes.FacadeCache"
@@ -336,6 +308,9 @@
<projectService serviceInterface="org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker"
serviceImplementation="org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider"
serviceImplementation="org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider"/>
<projectService serviceInterface="org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade"
serviceImplementation="org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade"/>
File diff suppressed because it is too large Load Diff
+20
View File
@@ -57,6 +57,12 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
<project-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
</component>
</project-components>
<extensionPoints>
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
@@ -71,6 +77,20 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
</extensions>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService" />
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
<postStartupActivity implementation="org.jetbrains.kotlin.idea.completion.LookupCancelWatcher"/>
<postStartupActivity implementation="org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener"/>
<postStartupActivity implementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.completion.LookupCancelService"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
<statistics.counterUsagesCollector groupId="kotlin.gradle.target" version="2"/>
<statistics.counterUsagesCollector groupId="kotlin.maven.target" version="3"/>
<statistics.counterUsagesCollector groupId="kotlin.jps.target" version="3"/>
+34
View File
@@ -56,6 +56,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
<project-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory</implementation-class>
<skipForDefaultProject/>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
</component>
</project-components>
<application-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.PluginStartupComponent</implementation-class>
</component>
</application-components>
<extensionPoints>
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
+34
View File
@@ -56,6 +56,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
<project-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory</implementation-class>
<skipForDefaultProject/>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
</component>
</project-components>
<application-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.PluginStartupComponent</implementation-class>
</component>
</application-components>
<extensionPoints>
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
+16
View File
@@ -71,6 +71,22 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
</extensions>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService" />
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
<postStartupActivity implementation="org.jetbrains.kotlin.idea.completion.LookupCancelWatcher"/>
<postStartupActivity implementation="org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener"/>
<postStartupActivity implementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.completion.LookupCancelService"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener"/>
<statistics.counterUsagesCollector groupId="kotlin.gradle.target" version="2"/>
<statistics.counterUsagesCollector groupId="kotlin.maven.target" version="3"/>
<statistics.counterUsagesCollector groupId="kotlin.jps.target" version="3"/>
+34
View File
@@ -57,6 +57,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
<project-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory</implementation-class>
<skipForDefaultProject/>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
</component>
</project-components>
<application-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.PluginStartupComponent</implementation-class>
</component>
</application-components>
<extensionPoints>
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
+34
View File
@@ -57,6 +57,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
<project-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory</implementation-class>
<skipForDefaultProject/>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
</component>
</project-components>
<application-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.PluginStartupComponent</implementation-class>
</component>
</application-components>
<extensionPoints>
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
+24 -4
View File
@@ -56,6 +56,12 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
<project-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
</component>
</project-components>
<extensionPoints>
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
@@ -65,16 +71,30 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<xi:include href="plugin-kotlin-extensions.xml" xpointer="xpointer(/idea-plugin/*)"/>
<extensions defaultExtensionNs="org.jetbrains.kotlin">
<pluginUpdateVerifier implementation="org.jetbrains.kotlin.idea.update.GooglePluginUpdateVerifier"/>
</extensions>
<extensions defaultExtensionNs="com.intellij.jvm">
<declarationSearcher language="kotlin" implementationClass="org.jetbrains.kotlin.idea.jvm.KotlinDeclarationSearcher"/>
</extensions>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService" />
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
<postStartupActivity implementation="org.jetbrains.kotlin.idea.completion.LookupCancelWatcher"/>
<postStartupActivity implementation="org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener"/>
<postStartupActivity implementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.completion.LookupCancelService"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Registrar"/>
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
<fileTypeUsageSchemaDescriptor schema="Gradle Script" implementationClass="org.jetbrains.kotlin.idea.core.script.KotlinGradleScriptFileTypeSchemaDetector"/>
<completion.ml.model implementation="org.jetbrains.kotlin.idea.completion.ml.KotlinMLRankingProvider"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.kotlin">
<pluginUpdateVerifier implementation="org.jetbrains.kotlin.idea.update.GooglePluginUpdateVerifier"/>
</extensions>
</idea-plugin>
@@ -5,310 +5,17 @@
package org.jetbrains.kotlin.idea.configuration
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.vcs.changes.ChangeListManager
import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx
import com.intellij.util.CommonProcessors
import com.intellij.util.text.VersionComparatorUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.idea.configuration.ui.showMigrationNotification
import org.jetbrains.kotlin.idea.facet.KotlinFacet
import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID
import org.jetbrains.kotlin.idea.framework.MAVEN_SYSTEM_ID
import org.jetbrains.kotlin.idea.migration.CodeMigrationToggleAction
import org.jetbrains.kotlin.idea.migration.applicableMigrationTools
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.runReadActionInSmartMode
import org.jetbrains.kotlin.idea.versions.LibInfo
import java.io.File
import com.intellij.openapi.startup.StartupActivity
class KotlinMigrationProjectComponent(val project: Project) {
@Volatile
private var old: MigrationState? = null
class KotlinMigrationProjectComponent : StartupActivity {
@Volatile
private var importFinishListener: ((MigrationTestState?) -> Unit)? = null
init {
val connection = project.messageBus.connect()
override fun runActivity(project: Project) {
val connection = project.messageBus.connect(project)
connection.subscribe(ProjectDataImportListener.TOPIC, ProjectDataImportListener {
getInstanceIfNotDisposed(project)?.onImportFinished()
KotlinMigrationProjectService.getInstanceIfNotDisposed(project)?.onImportFinished()
})
}
class MigrationTestState(val migrationInfo: MigrationInfo?, val hasApplicableTools: Boolean)
@TestOnly
fun setImportFinishListener(newListener: ((MigrationTestState?) -> Unit)?) {
synchronized(this) {
if (newListener != null && importFinishListener != null) {
importFinishListener!!.invoke(null)
}
importFinishListener = newListener
}
}
private fun notifyFinish(migrationInfo: MigrationInfo?, hasApplicableTools: Boolean) {
importFinishListener?.invoke(MigrationTestState(migrationInfo, hasApplicableTools))
}
fun onImportAboutToStart() {
if (!CodeMigrationToggleAction.isEnabled(project) || !hasChangesInProjectFiles(project)) {
old = null
return
}
old = MigrationState.build(project)
}
fun onImportFinished() {
if (!CodeMigrationToggleAction.isEnabled(project) || old == null) {
notifyFinish(null, false)
return
}
ApplicationManager.getApplication().executeOnPooledThread {
var migrationInfo: MigrationInfo? = null
var hasApplicableTools = false
try {
val new = project.runReadActionInSmartMode {
MigrationState.build(project)
}
val localOld = old.also {
old = null
} ?: return@executeOnPooledThread
migrationInfo = prepareMigrationInfo(localOld, new) ?: return@executeOnPooledThread
if (applicableMigrationTools(migrationInfo).isEmpty()) {
hasApplicableTools = false
return@executeOnPooledThread
} else {
hasApplicableTools = true
}
if (ApplicationManager.getApplication().isUnitTestMode) {
return@executeOnPooledThread
}
ApplicationManager.getApplication().invokeLater {
showMigrationNotification(project, migrationInfo)
}
} finally {
notifyFinish(migrationInfo, hasApplicableTools)
}
}
}
companion object {
fun getInstanceIfNotDisposed(project: Project): KotlinMigrationProjectComponent? {
return runReadAction {
if (!project.isDisposed) {
project.getComponent(KotlinMigrationProjectComponent::class.java)
?: error("Can't find ${KotlinMigrationProjectComponent::class.qualifiedName} component")
} else {
null
}
}
}
private fun prepareMigrationInfo(old: MigrationState?, new: MigrationState?): MigrationInfo? {
if (old == null || new == null) {
return null
}
val oldLibraryVersion = old.stdlibInfo?.version
val newLibraryVersion = new.stdlibInfo?.version
if (oldLibraryVersion == null || newLibraryVersion == null) {
return null
}
if (VersionComparatorUtil.COMPARATOR.compare(newLibraryVersion, oldLibraryVersion) > 0 ||
old.apiVersion < new.apiVersion || old.languageVersion < new.languageVersion
) {
return MigrationInfo(
oldLibraryVersion, newLibraryVersion,
old.apiVersion, new.apiVersion,
old.languageVersion, new.languageVersion
)
}
return null
}
private fun hasChangesInProjectFiles(project: Project): Boolean {
if (ProjectLevelVcsManagerEx.getInstance(project).allVcsRoots.isEmpty()) {
return true
}
val checkedFiles = HashSet<File>()
project.basePath?.let { projectBasePath ->
checkedFiles.add(File(projectBasePath))
}
val changedFiles = ChangeListManager.getInstance(project).affectedPaths
for (changedFile in changedFiles) {
when (changedFile.extension) {
"gradle" -> return true
"properties" -> return true
"kts" -> return true
"iml" -> return true
"xml" -> {
if (changedFile.name == "pom.xml") return true
val parentDir = changedFile.parentFile
if (parentDir.isDirectory && parentDir.name == Project.DIRECTORY_STORE_FOLDER) {
return true
}
}
"kt", "java", "groovy" -> {
val dirs: Sequence<File> = generateSequence(changedFile) { it.parentFile }
.drop(1) // Drop original file
.takeWhile { it.isDirectory }
val isInBuildSrc = dirs
.takeWhile { checkedFiles.add(it) }
.any { it.name == BUILD_SRC_FOLDER_NAME }
if (isInBuildSrc) {
return true
}
}
}
}
return false
}
}
}
private class MigrationState(
var stdlibInfo: LibInfo?,
var apiVersion: ApiVersion,
var languageVersion: LanguageVersion
) {
companion object {
fun build(project: Project): MigrationState {
val libraries = maxKotlinLibVersion(project)
val languageVersionSettings = collectMaxCompilerSettings(project)
return MigrationState(libraries, languageVersionSettings.apiVersion, languageVersionSettings.languageVersion)
}
}
}
data class MigrationInfo(
val oldStdlibVersion: String,
val newStdlibVersion: String,
val oldApiVersion: ApiVersion,
val newApiVersion: ApiVersion,
val oldLanguageVersion: LanguageVersion,
val newLanguageVersion: LanguageVersion
) {
companion object {
fun create(
oldStdlibVersion: String,
oldApiVersion: ApiVersion,
oldLanguageVersion: LanguageVersion,
newStdlibVersion: String = oldStdlibVersion,
newApiVersion: ApiVersion = oldApiVersion,
newLanguageVersion: LanguageVersion = oldLanguageVersion
): MigrationInfo {
return MigrationInfo(
oldStdlibVersion, newStdlibVersion,
oldApiVersion, newApiVersion,
oldLanguageVersion, newLanguageVersion
)
}
}
}
fun MigrationInfo.isLanguageVersionUpdate(old: LanguageVersion, new: LanguageVersion): Boolean {
return oldLanguageVersion <= old && newLanguageVersion >= new
}
private const val BUILD_SRC_FOLDER_NAME = "buildSrc"
private const val KOTLIN_GROUP_ID = "org.jetbrains.kotlin"
private fun maxKotlinLibVersion(project: Project): LibInfo? {
return runReadAction {
var maxStdlibInfo: LibInfo? = null
val allLibProcessor = CommonProcessors.CollectUniquesProcessor<Library>()
ProjectRootManager.getInstance(project).orderEntries().forEachLibrary(allLibProcessor)
for (library in allLibProcessor.results) {
if (!ExternalSystemApiUtil.isExternalSystemLibrary(library, GRADLE_SYSTEM_ID) &&
!ExternalSystemApiUtil.isExternalSystemLibrary(library, MAVEN_SYSTEM_ID)
) {
continue
}
if (library.name?.contains(" $KOTLIN_GROUP_ID:kotlin-stdlib") != true) {
continue
}
val libraryInfo = parseExternalLibraryName(library) ?: continue
if (maxStdlibInfo == null || VersionComparatorUtil.COMPARATOR.compare(libraryInfo.version, maxStdlibInfo.version) > 0) {
maxStdlibInfo = LibInfo(KOTLIN_GROUP_ID, libraryInfo.artifactId, libraryInfo.version)
}
}
maxStdlibInfo
}
}
private fun collectMaxCompilerSettings(project: Project): LanguageVersionSettings {
return runReadAction {
var maxApiVersion: ApiVersion? = null
var maxLanguageVersion: LanguageVersion? = null
for (module in ModuleManager.getInstance(project).modules) {
if (!module.isKotlinModule()) {
// Otherwise project compiler settings will give unreliable maximum for compiler settings
continue
}
val languageVersionSettings = module.languageVersionSettings
if (maxApiVersion == null || languageVersionSettings.apiVersion > maxApiVersion) {
maxApiVersion = languageVersionSettings.apiVersion
}
if (maxLanguageVersion == null || languageVersionSettings.languageVersion > maxLanguageVersion) {
maxLanguageVersion = languageVersionSettings.languageVersion
}
}
LanguageVersionSettingsImpl(maxLanguageVersion ?: LanguageVersion.LATEST_STABLE, maxApiVersion ?: ApiVersion.LATEST_STABLE)
}
}
private fun Module.isKotlinModule(): Boolean {
if (isDisposed) return false
if (KotlinFacet.get(this) != null) {
return true
}
// This code works only for Maven and Gradle import, and it's expected that Kotlin facets are configured for
// all modules with external system.
return false
}
@@ -0,0 +1,314 @@
/*
* Copyright 2010-2018 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.configuration
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.vcs.changes.ChangeListManager
import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx
import com.intellij.util.CommonProcessors
import com.intellij.util.text.VersionComparatorUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.idea.configuration.ui.showMigrationNotification
import org.jetbrains.kotlin.idea.facet.KotlinFacet
import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID
import org.jetbrains.kotlin.idea.framework.MAVEN_SYSTEM_ID
import org.jetbrains.kotlin.idea.migration.CodeMigrationToggleAction
import org.jetbrains.kotlin.idea.migration.applicableMigrationTools
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.runReadActionInSmartMode
import org.jetbrains.kotlin.idea.versions.LibInfo
import java.io.File
class KotlinMigrationProjectComponent(val project: Project) {
@Volatile
private var old: MigrationState? = null
@Volatile
private var importFinishListener: ((MigrationTestState?) -> Unit)? = null
init {
val connection = project.messageBus.connect(project)
connection.subscribe(ProjectDataImportListener.TOPIC, ProjectDataImportListener {
getInstanceIfNotDisposed(project)?.onImportFinished()
})
}
class MigrationTestState(val migrationInfo: MigrationInfo?, val hasApplicableTools: Boolean)
@TestOnly
fun setImportFinishListener(newListener: ((MigrationTestState?) -> Unit)?) {
synchronized(this) {
if (newListener != null && importFinishListener != null) {
importFinishListener!!.invoke(null)
}
importFinishListener = newListener
}
}
private fun notifyFinish(migrationInfo: MigrationInfo?, hasApplicableTools: Boolean) {
importFinishListener?.invoke(MigrationTestState(migrationInfo, hasApplicableTools))
}
fun onImportAboutToStart() {
if (!CodeMigrationToggleAction.isEnabled(project) || !hasChangesInProjectFiles(project)) {
old = null
return
}
old = MigrationState.build(project)
}
fun onImportFinished() {
if (!CodeMigrationToggleAction.isEnabled(project) || old == null) {
notifyFinish(null, false)
return
}
ApplicationManager.getApplication().executeOnPooledThread {
var migrationInfo: MigrationInfo? = null
var hasApplicableTools = false
try {
val new = project.runReadActionInSmartMode {
MigrationState.build(project)
}
val localOld = old.also {
old = null
} ?: return@executeOnPooledThread
migrationInfo = prepareMigrationInfo(localOld, new) ?: return@executeOnPooledThread
if (applicableMigrationTools(migrationInfo).isEmpty()) {
hasApplicableTools = false
return@executeOnPooledThread
} else {
hasApplicableTools = true
}
if (ApplicationManager.getApplication().isUnitTestMode) {
return@executeOnPooledThread
}
ApplicationManager.getApplication().invokeLater {
showMigrationNotification(project, migrationInfo)
}
} finally {
notifyFinish(migrationInfo, hasApplicableTools)
}
}
}
companion object {
fun getInstanceIfNotDisposed(project: Project): KotlinMigrationProjectComponent? {
return runReadAction {
if (!project.isDisposed) {
project.getComponent(KotlinMigrationProjectComponent::class.java)
?: error("Can't find ${KotlinMigrationProjectComponent::class.qualifiedName} component")
} else {
null
}
}
}
private fun prepareMigrationInfo(old: MigrationState?, new: MigrationState?): MigrationInfo? {
if (old == null || new == null) {
return null
}
val oldLibraryVersion = old.stdlibInfo?.version
val newLibraryVersion = new.stdlibInfo?.version
if (oldLibraryVersion == null || newLibraryVersion == null) {
return null
}
if (VersionComparatorUtil.COMPARATOR.compare(newLibraryVersion, oldLibraryVersion) > 0 ||
old.apiVersion < new.apiVersion || old.languageVersion < new.languageVersion
) {
return MigrationInfo(
oldLibraryVersion, newLibraryVersion,
old.apiVersion, new.apiVersion,
old.languageVersion, new.languageVersion
)
}
return null
}
private fun hasChangesInProjectFiles(project: Project): Boolean {
if (ProjectLevelVcsManagerEx.getInstance(project).allVcsRoots.isEmpty()) {
return true
}
val checkedFiles = HashSet<File>()
project.basePath?.let { projectBasePath ->
checkedFiles.add(File(projectBasePath))
}
val changedFiles = ChangeListManager.getInstance(project).affectedPaths
for (changedFile in changedFiles) {
when (changedFile.extension) {
"gradle" -> return true
"properties" -> return true
"kts" -> return true
"iml" -> return true
"xml" -> {
if (changedFile.name == "pom.xml") return true
val parentDir = changedFile.parentFile
if (parentDir.isDirectory && parentDir.name == Project.DIRECTORY_STORE_FOLDER) {
return true
}
}
"kt", "java", "groovy" -> {
val dirs: Sequence<File> = generateSequence(changedFile) { it.parentFile }
.drop(1) // Drop original file
.takeWhile { it.isDirectory }
val isInBuildSrc = dirs
.takeWhile { checkedFiles.add(it) }
.any { it.name == BUILD_SRC_FOLDER_NAME }
if (isInBuildSrc) {
return true
}
}
}
}
return false
}
}
}
private class MigrationState(
var stdlibInfo: LibInfo?,
var apiVersion: ApiVersion,
var languageVersion: LanguageVersion
) {
companion object {
fun build(project: Project): MigrationState {
val libraries = maxKotlinLibVersion(project)
val languageVersionSettings = collectMaxCompilerSettings(project)
return MigrationState(libraries, languageVersionSettings.apiVersion, languageVersionSettings.languageVersion)
}
}
}
data class MigrationInfo(
val oldStdlibVersion: String,
val newStdlibVersion: String,
val oldApiVersion: ApiVersion,
val newApiVersion: ApiVersion,
val oldLanguageVersion: LanguageVersion,
val newLanguageVersion: LanguageVersion
) {
companion object {
fun create(
oldStdlibVersion: String,
oldApiVersion: ApiVersion,
oldLanguageVersion: LanguageVersion,
newStdlibVersion: String = oldStdlibVersion,
newApiVersion: ApiVersion = oldApiVersion,
newLanguageVersion: LanguageVersion = oldLanguageVersion
): MigrationInfo {
return MigrationInfo(
oldStdlibVersion, newStdlibVersion,
oldApiVersion, newApiVersion,
oldLanguageVersion, newLanguageVersion
)
}
}
}
fun MigrationInfo.isLanguageVersionUpdate(old: LanguageVersion, new: LanguageVersion): Boolean {
return oldLanguageVersion <= old && newLanguageVersion >= new
}
private const val BUILD_SRC_FOLDER_NAME = "buildSrc"
private const val KOTLIN_GROUP_ID = "org.jetbrains.kotlin"
private fun maxKotlinLibVersion(project: Project): LibInfo? {
return runReadAction {
var maxStdlibInfo: LibInfo? = null
val allLibProcessor = CommonProcessors.CollectUniquesProcessor<Library>()
ProjectRootManager.getInstance(project).orderEntries().forEachLibrary(allLibProcessor)
for (library in allLibProcessor.results) {
if (!ExternalSystemApiUtil.isExternalSystemLibrary(library, GRADLE_SYSTEM_ID) &&
!ExternalSystemApiUtil.isExternalSystemLibrary(library, MAVEN_SYSTEM_ID)
) {
continue
}
if (library.name?.contains(" $KOTLIN_GROUP_ID:kotlin-stdlib") != true) {
continue
}
val libraryInfo = parseExternalLibraryName(library) ?: continue
if (maxStdlibInfo == null || VersionComparatorUtil.COMPARATOR.compare(libraryInfo.version, maxStdlibInfo.version) > 0) {
maxStdlibInfo = LibInfo(KOTLIN_GROUP_ID, libraryInfo.artifactId, libraryInfo.version)
}
}
maxStdlibInfo
}
}
private fun collectMaxCompilerSettings(project: Project): LanguageVersionSettings {
return runReadAction {
var maxApiVersion: ApiVersion? = null
var maxLanguageVersion: LanguageVersion? = null
for (module in ModuleManager.getInstance(project).modules) {
if (!module.isKotlinModule()) {
// Otherwise project compiler settings will give unreliable maximum for compiler settings
continue
}
val languageVersionSettings = module.languageVersionSettings
if (maxApiVersion == null || languageVersionSettings.apiVersion > maxApiVersion) {
maxApiVersion = languageVersionSettings.apiVersion
}
if (maxLanguageVersion == null || languageVersionSettings.languageVersion > maxLanguageVersion) {
maxLanguageVersion = languageVersionSettings.languageVersion
}
}
LanguageVersionSettingsImpl(maxLanguageVersion ?: LanguageVersion.LATEST_STABLE, maxApiVersion ?: ApiVersion.LATEST_STABLE)
}
}
private fun Module.isKotlinModule(): Boolean {
if (isDisposed) return false
if (KotlinFacet.get(this) != null) {
return true
}
// This code works only for Maven and Gradle import, and it's expected that Kotlin facets are configured for
// all modules with external system.
return false
}
@@ -0,0 +1,308 @@
/*
* Copyright 2010-2018 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.configuration
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.vcs.changes.ChangeListManager
import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx
import com.intellij.util.CommonProcessors
import com.intellij.util.text.VersionComparatorUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.idea.configuration.ui.showMigrationNotification
import org.jetbrains.kotlin.idea.facet.KotlinFacet
import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID
import org.jetbrains.kotlin.idea.framework.MAVEN_SYSTEM_ID
import org.jetbrains.kotlin.idea.migration.CodeMigrationToggleAction
import org.jetbrains.kotlin.idea.migration.applicableMigrationTools
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.runReadActionInSmartMode
import org.jetbrains.kotlin.idea.versions.LibInfo
import java.io.File
typealias MigrationTestState = KotlinMigrationProjectService.MigrationTestState
class KotlinMigrationProjectService(val project: Project) {
@Volatile
private var old: MigrationState? = null
@Volatile
private var importFinishListener: ((MigrationTestState?) -> Unit)? = null
class MigrationTestState(val migrationInfo: MigrationInfo?, val hasApplicableTools: Boolean)
@TestOnly
fun setImportFinishListener(newListener: ((MigrationTestState?) -> Unit)?) {
synchronized(this) {
if (newListener != null && importFinishListener != null) {
importFinishListener!!.invoke(null)
}
importFinishListener = newListener
}
}
private fun notifyFinish(migrationInfo: MigrationInfo?, hasApplicableTools: Boolean) {
importFinishListener?.invoke(MigrationTestState(migrationInfo, hasApplicableTools))
}
fun onImportAboutToStart() {
if (!CodeMigrationToggleAction.isEnabled(project) || !hasChangesInProjectFiles(project)) {
old = null
return
}
old = MigrationState.build(project)
}
fun onImportFinished() {
if (!CodeMigrationToggleAction.isEnabled(project) || old == null) {
notifyFinish(null, false)
return
}
ApplicationManager.getApplication().executeOnPooledThread {
var migrationInfo: MigrationInfo? = null
var hasApplicableTools = false
try {
val new = project.runReadActionInSmartMode {
MigrationState.build(project)
}
val localOld = old.also {
old = null
} ?: return@executeOnPooledThread
migrationInfo = prepareMigrationInfo(localOld, new) ?: return@executeOnPooledThread
if (applicableMigrationTools(migrationInfo).isEmpty()) {
hasApplicableTools = false
return@executeOnPooledThread
} else {
hasApplicableTools = true
}
if (ApplicationManager.getApplication().isUnitTestMode) {
return@executeOnPooledThread
}
ApplicationManager.getApplication().invokeLater {
showMigrationNotification(project, migrationInfo)
}
} finally {
notifyFinish(migrationInfo, hasApplicableTools)
}
}
}
companion object {
fun getInstanceIfNotDisposed(project: Project): KotlinMigrationProjectService? {
return runReadAction {
if (!project.isDisposed) {
project.getServiceSafe(KotlinMigrationProjectService::class.java)
} else {
null
}
}
}
private fun prepareMigrationInfo(old: MigrationState?, new: MigrationState?): MigrationInfo? {
if (old == null || new == null) {
return null
}
val oldLibraryVersion = old.stdlibInfo?.version
val newLibraryVersion = new.stdlibInfo?.version
if (oldLibraryVersion == null || newLibraryVersion == null) {
return null
}
if (VersionComparatorUtil.COMPARATOR.compare(newLibraryVersion, oldLibraryVersion) > 0 ||
old.apiVersion < new.apiVersion || old.languageVersion < new.languageVersion
) {
return MigrationInfo(
oldLibraryVersion, newLibraryVersion,
old.apiVersion, new.apiVersion,
old.languageVersion, new.languageVersion
)
}
return null
}
private fun hasChangesInProjectFiles(project: Project): Boolean {
if (ProjectLevelVcsManagerEx.getInstance(project).allVcsRoots.isEmpty()) {
return true
}
val checkedFiles = HashSet<File>()
project.basePath?.let { projectBasePath ->
checkedFiles.add(File(projectBasePath))
}
val changedFiles = ChangeListManager.getInstance(project).affectedPaths
for (changedFile in changedFiles) {
when (changedFile.extension) {
"gradle" -> return true
"properties" -> return true
"kts" -> return true
"iml" -> return true
"xml" -> {
if (changedFile.name == "pom.xml") return true
val parentDir = changedFile.parentFile
if (parentDir.isDirectory && parentDir.name == Project.DIRECTORY_STORE_FOLDER) {
return true
}
}
"kt", "java", "groovy" -> {
val dirs: Sequence<File> = generateSequence(changedFile) { it.parentFile }
.drop(1) // Drop original file
.takeWhile { it.isDirectory }
val isInBuildSrc = dirs
.takeWhile { checkedFiles.add(it) }
.any { it.name == BUILD_SRC_FOLDER_NAME }
if (isInBuildSrc) {
return true
}
}
}
}
return false
}
}
}
private class MigrationState(
var stdlibInfo: LibInfo?,
var apiVersion: ApiVersion,
var languageVersion: LanguageVersion
) {
companion object {
fun build(project: Project): MigrationState {
val libraries = maxKotlinLibVersion(project)
val languageVersionSettings = collectMaxCompilerSettings(project)
return MigrationState(libraries, languageVersionSettings.apiVersion, languageVersionSettings.languageVersion)
}
}
}
data class MigrationInfo(
val oldStdlibVersion: String,
val newStdlibVersion: String,
val oldApiVersion: ApiVersion,
val newApiVersion: ApiVersion,
val oldLanguageVersion: LanguageVersion,
val newLanguageVersion: LanguageVersion
) {
companion object {
fun create(
oldStdlibVersion: String,
oldApiVersion: ApiVersion,
oldLanguageVersion: LanguageVersion,
newStdlibVersion: String = oldStdlibVersion,
newApiVersion: ApiVersion = oldApiVersion,
newLanguageVersion: LanguageVersion = oldLanguageVersion
): MigrationInfo {
return MigrationInfo(
oldStdlibVersion, newStdlibVersion,
oldApiVersion, newApiVersion,
oldLanguageVersion, newLanguageVersion
)
}
}
}
fun MigrationInfo.isLanguageVersionUpdate(old: LanguageVersion, new: LanguageVersion): Boolean {
return oldLanguageVersion <= old && newLanguageVersion >= new
}
private const val BUILD_SRC_FOLDER_NAME = "buildSrc"
private const val KOTLIN_GROUP_ID = "org.jetbrains.kotlin"
private fun maxKotlinLibVersion(project: Project): LibInfo? {
return runReadAction {
var maxStdlibInfo: LibInfo? = null
val allLibProcessor = CommonProcessors.CollectUniquesProcessor<Library>()
ProjectRootManager.getInstance(project).orderEntries().forEachLibrary(allLibProcessor)
for (library in allLibProcessor.results) {
if (!ExternalSystemApiUtil.isExternalSystemLibrary(library, GRADLE_SYSTEM_ID) &&
!ExternalSystemApiUtil.isExternalSystemLibrary(library, MAVEN_SYSTEM_ID)
) {
continue
}
if (library.name?.contains(" $KOTLIN_GROUP_ID:kotlin-stdlib") != true) {
continue
}
val libraryInfo = parseExternalLibraryName(library) ?: continue
if (maxStdlibInfo == null || VersionComparatorUtil.COMPARATOR.compare(libraryInfo.version, maxStdlibInfo.version) > 0) {
maxStdlibInfo = LibInfo(KOTLIN_GROUP_ID, libraryInfo.artifactId, libraryInfo.version)
}
}
maxStdlibInfo
}
}
private fun collectMaxCompilerSettings(project: Project): LanguageVersionSettings {
return runReadAction {
var maxApiVersion: ApiVersion? = null
var maxLanguageVersion: LanguageVersion? = null
for (module in ModuleManager.getInstance(project).modules) {
if (!module.isKotlinModule()) {
// Otherwise project compiler settings will give unreliable maximum for compiler settings
continue
}
val languageVersionSettings = module.languageVersionSettings
if (maxApiVersion == null || languageVersionSettings.apiVersion > maxApiVersion) {
maxApiVersion = languageVersionSettings.apiVersion
}
if (maxLanguageVersion == null || languageVersionSettings.languageVersion > maxLanguageVersion) {
maxLanguageVersion = languageVersionSettings.languageVersion
}
}
LanguageVersionSettingsImpl(maxLanguageVersion ?: LanguageVersion.LATEST_STABLE, maxApiVersion ?: ApiVersion.LATEST_STABLE)
}
}
private fun Module.isKotlinModule(): Boolean {
if (isDisposed) return false
if (KotlinFacet.get(this) != null) {
return true
}
// This code works only for Maven and Gradle import, and it's expected that Kotlin facets are configured for
// all modules with external system.
return false
}
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2018 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.configuration
// BUNCH: 192
typealias KotlinMigrationProjectService = KotlinMigrationProjectComponent
typealias MigrationTestState = KotlinMigrationProjectComponent.MigrationTestState
@@ -17,14 +17,14 @@ import com.intellij.psi.PsiElement
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.idea.core.util.end
import org.jetbrains.kotlin.idea.core.util.range
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import java.util.concurrent.ConcurrentHashMap
class KotlinCodeHintsModel(val project: Project) : EditorFactoryListener {
companion object {
fun getInstance(project: Project): KotlinCodeHintsModel =
project.getComponent(KotlinCodeHintsModel::class.java) ?: error("Component `KotlinCodeHintsModel` is expected to be registered")
fun getInstance(project: Project): KotlinCodeHintsModel = project.getServiceSafe(KotlinCodeHintsModel::class.java)
}
private class DocumentExtensionInfoModel(val document: Document) {
@@ -9,7 +9,6 @@ import com.intellij.mock.MockProject
import com.intellij.pom.PomModel
import com.intellij.pom.core.impl.PomModelImpl
import com.intellij.pom.tree.TreeAspect
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
@@ -33,19 +32,12 @@ abstract class AbstractAdditionalResolveDescriptorRendererTest : AbstractDescrip
val pomModelImpl = PomModelImpl(project)
val treeAspect = TreeAspect(pomModelImpl)
(project as MockProject).registerService(PomModel::class.java, pomModelImpl)
(project.picoContainer as MutablePicoContainer).registerComponentInstance(
KotlinCodeBlockModificationListener(
PsiModificationTracker.SERVICE.getInstance(project),
project,
treeAspect
)
)
val mockProject = project as MockProject
createAndRegisterKotlinCodeBlockModificationListener(mockProject, pomModelImpl, treeAspect)
}
override fun tearDown() {
(project.picoContainer as MutablePicoContainer).unregisterComponent(KotlinCodeBlockModificationListener::class.java)
(project.picoContainer as MutablePicoContainer).unregisterComponent(PomModel::class.java)
unregisterKotlinCodeBlockModificationListener(project as MockProject)
super.tearDown()
}
@@ -0,0 +1,29 @@
/*
* 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.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
)
)
}
internal fun unregisterKotlinCodeBlockModificationListener(project: MockProject) {
val picoContainer = project.picoContainer
picoContainer.unregisterComponent(KotlinCodeBlockModificationListener::class.java)
picoContainer.unregisterComponent(PomModel::class.java)
}
@@ -0,0 +1,23 @@
/*
* 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.resolve
import com.intellij.mock.MockProject
import com.intellij.pom.PomModel
import com.intellij.pom.tree.TreeAspect
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
internal fun createAndRegisterKotlinCodeBlockModificationListener(project: MockProject, pomModel: PomModel, treeAspect: TreeAspect) {
project.registerService(PomModel::class.java, pomModel)
project.registerService(TreeAspect::class.java, treeAspect)
project.registerService(KotlinCodeBlockModificationListener::class.java, KotlinCodeBlockModificationListener(project))
}
internal fun unregisterKotlinCodeBlockModificationListener(project: MockProject) {
val picoContainer = project.picoContainer
picoContainer.unregisterComponent(KotlinCodeBlockModificationListener::class.java)
picoContainer.unregisterComponent(PomModel::class.java)
}