From 674f1d129f6d6e6c7a72d2d24c521f2ab41c3f00 Mon Sep 17 00:00:00 2001 From: Vladimir Dolzhenko Date: Fri, 27 Mar 2020 07:54:14 +0000 Subject: [PATCH] Clean up of plugin-common.xml and convert components to services --- .../caches/PerModulePackageCacheService.kt | 10 +- .../PerModulePackageCacheService.kt.192 | 420 ++ .../project/LibraryModificationTracker.kt | 6 +- .../KotlinCodeBlockModificationListener.kt | 4 +- ...KotlinCodeBlockModificationListener.kt.201 | 362 ++ ...ModuleOutOfCodeBlockModificationTracker.kt | 2 +- .../kotlin/idea/util/ApplicationUtils.kt | 5 +- .../kotlin/idea/util/ApplicationUtils.kt.192 | 6 +- .../kotlin/idea/util/ApplicationUtils.kt.201 | 5 +- .../kotlin/idea/util/ApplicationUtils.kt.as40 | 9 +- .../idea/completion/BasicCompletionSession.kt | 4 +- .../CompletionBindingContextProvider.kt | 3 +- .../idea/completion/LookupCancelService.kt | 100 + .../completion/LookupCancelService.kt.192 | 19 + .../idea/completion/LookupCancelWatcher.kt | 77 +- .../completion/LookupCancelWatcher.kt.192 | 158 + .../AbstractScriptConfigurationManager.kt | 2 +- .../codeInsight/gradle/GradleMigrateTest.kt | 6 +- ...otlinBuildProcessParametersProvider.kt.191 | 39 - .../KotlinExternalSystemSyncListener.kt | 2 +- ...KotlinConfigurationCheckerComponent.kt.192 | 2 +- .../ui/KotlinConfigurationCheckerService.kt | 26 +- .../kotlin/idea/maven/MavenImportListener.kt | 6 +- .../kotlin/idea/maven/MavenMigrateTest.kt | 7 +- idea/resources/META-INF/plugin-common.xml | 35 +- idea/resources/META-INF/plugin-common.xml.192 | 3658 ----------------- idea/resources/META-INF/plugin.xml | 20 + idea/resources/META-INF/plugin.xml.191 | 34 + idea/resources/META-INF/plugin.xml.192 | 34 + idea/resources/META-INF/plugin.xml.201 | 16 + idea/resources/META-INF/plugin.xml.as35 | 34 + idea/resources/META-INF/plugin.xml.as36 | 34 + idea/resources/META-INF/plugin.xml.as40 | 28 +- .../KotlinMigrationProjectComponent.kt | 303 +- .../KotlinMigrationProjectComponent.kt.192 | 314 ++ .../KotlinMigrationProjectService.kt | 308 ++ .../KotlinMigrationProjectService.kt.192 | 10 + .../custom/KotlinCodeHintsModel.kt | 4 +- ...AdditionalResolveDescriptorRendererTest.kt | 14 +- .../jetbrains/kotlin/idea/resolve/compat.kt | 29 + .../kotlin/idea/resolve/compat.kt.201 | 23 + 41 files changed, 2027 insertions(+), 4151 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt.192 create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt.201 create mode 100644 idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelService.kt create mode 100644 idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelService.kt.192 create mode 100644 idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt.192 delete mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/compiler/configuration/KotlinBuildProcessParametersProvider.kt.191 delete mode 100644 idea/resources/META-INF/plugin-common.xml.192 create mode 100644 idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt.192 create mode 100644 idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectService.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectService.kt.192 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/resolve/compat.kt create mode 100644 idea/tests/org/jetbrains/kotlin/idea/resolve/compat.kt.201 diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt index 173d13202cf..baf43a37d7e 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt @@ -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) = onEvents(events, false) override fun after(events: List) = 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 { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt.192 b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt.192 new file mode 100644 index 00000000000..173d13202cf --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt.192 @@ -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) = onEvents(events, false) + override fun after(events: List) = 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, 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(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() + 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> + +class ImplicitPackagePrefixCache(private val project: Project) { + private val implicitPackageCache = ConcurrentHashMap() + + 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> { + val result = mutableMapOf>() + 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>> + */ + private val cache = ContainerUtil.createConcurrentWeakMap>>() + private val implicitPackagePrefixCache = ImplicitPackagePrefixCache(project) + + private val pendingVFileChanges: MutableSet = mutableSetOf() + private val pendingKtFileChanges: MutableSet = 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 MutableCollection.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(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) + } + } +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/LibraryModificationTracker.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/LibraryModificationTracker.kt index 07d2600b7cd..c77c40caace 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/LibraryModificationTracker.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/LibraryModificationTracker.kt @@ -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) { events.filter(::isRelevantEvent).let { createEvents -> diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt index 1437f14c883..3eaea1863d4 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt @@ -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 } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt.201 b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt.201 new file mode 100644 index 00000000000..f3e508858a0 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt.201 @@ -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("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): List { + // 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() + if (lambda is KtLambdaExpression) { + lambda.getTopmostParentOfType()?.getTopmostParentOfType()?.let { + return BlockModificationScopeElement(it, it) + } + } + + val blockDeclaration = + KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) as? KtDeclaration ?: return null +// KtPsiUtil.getTopmostParentOfType(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 {......}` + return blockDeclaration.bodyExpression + ?.takeIf { it.isAncestor(element) } + ?.let { BlockModificationScopeElement(blockDeclaration, it) } + } else if (blockDeclaration.hasDeclaredReturnType()) { + // case like `fun foo(): String = blabla` + 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>( + 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("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("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>("IN_BLOCK_MODIFICATIONS") +private val FILE_IN_BLOCK_MODIFICATION_COUNT = Key("FILE_IN_BLOCK_MODIFICATION_COUNT") + +val KtFile.inBlockModificationCount: Long by NotNullableUserDataProperty(FILE_IN_BLOCK_MODIFICATION_COUNT, 0) + +val KtFile.inBlockModifications: Collection + 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() + } + } +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinModuleOutOfCodeBlockModificationTracker.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinModuleOutOfCodeBlockModificationTracker.kt index c19b92a4b50..59d5480b491 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinModuleOutOfCodeBlockModificationTracker.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinModuleOutOfCodeBlockModificationTracker.kt @@ -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) { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt index f5ec0a7108a..523385bf7c0 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt @@ -50,4 +50,7 @@ inline fun invokeLater(crossinline action: () -> Unit) { ApplicationManager.getApplication().invokeLater { action() } } -inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode \ No newline at end of file +inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode + +fun Project.getServiceSafe(serviceClass: Class): T = + this.getService(serviceClass) ?: error("Unable to locate service ${serviceClass.name}") \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.192 b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.192 index 6c2ded8c6b2..d9edf84986d 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.192 +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.192 @@ -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 \ No newline at end of file +inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode + +fun Project.getServiceSafe(serviceClass: Class): T = + ServiceManager.getService(this, serviceClass) ?: error("Unable to locate service ${serviceClass.name}") diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.201 b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.201 index 12557bf3978..1d7863b65ae 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.201 +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.201 @@ -50,4 +50,7 @@ inline fun invokeLater(crossinline action: () -> Unit) { ApplicationManager.getApplication().invokeLater { action() } } -inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode \ No newline at end of file +inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode + +fun Project.getServiceSafe(serviceClass: Class) = + this.getService(serviceClass) ?: error("Unable to locate service ${serviceClass.name}") \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.as40 b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.as40 index 6c2ded8c6b2..071b7ccf54a 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.as40 +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt.as40 @@ -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 \ No newline at end of file +inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode + +fun Project.getServiceSafe(serviceClass: Class): T = + ServiceManager.getService(this, serviceClass) ?: error("Unable to locate service ${serviceClass.name}") + +fun Project.getServiceIfCreated(serviceClass: Class): T? = + ServiceManager.getServiceIfCreated(this, serviceClass) \ No newline at end of file diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index fac91eccd80..05c83a5e8c7 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -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? diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionBindingContextProvider.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionBindingContextProvider.kt index 168d19273d0..0c4d7167356 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionBindingContextProvider.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionBindingContextProvider.kt @@ -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 } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelService.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelService.kt new file mode 100644 index 00000000000..60a4f4d2fdf --- /dev/null +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelService.kt @@ -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("LookupCancelService.AUTO_POPUP_AT") + } + + fun wasAutoPopupRecentlyCancelled(editor: Editor, offset: Int): Boolean { + return lastReminiscence?.matches(editor, offset) ?: false + } + +} diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelService.kt.192 b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelService.kt.192 new file mode 100644 index 00000000000..00fad96f055 --- /dev/null +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelService.kt.192 @@ -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 \ No newline at end of file diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt index 6a3f3a44076..94935c82c64 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt @@ -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("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()) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt.192 b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt.192 new file mode 100644 index 00000000000..6a3f3a44076 --- /dev/null +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupCancelWatcher.kt.192 @@ -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("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 + } + }) + } + } + } +} diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt index 316e2695873..f7181d06ac3 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/AbstractScriptConfigurationManager.kt @@ -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() diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleMigrateTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleMigrateTest.kt index 95c1474c0ce..37b57109026 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleMigrateTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleMigrateTest.kt @@ -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() - val migrationProjectComponent = KotlinMigrationProjectComponent.getInstanceIfNotDisposed(myProject) + val migrationProjectComponent = KotlinMigrationProjectService.getInstanceIfNotDisposed(myProject) ?: error("Disposed project") migrationProjectComponent.setImportFinishListener { migrationState -> diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/compiler/configuration/KotlinBuildProcessParametersProvider.kt.191 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/compiler/configuration/KotlinBuildProcessParametersProvider.kt.191 deleted file mode 100644 index e3954f77d31..00000000000 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/compiler/configuration/KotlinBuildProcessParametersProvider.kt.191 +++ /dev/null @@ -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 { - val compilerWorkspaceSettings = KotlinCompilerWorkspaceSettings.getInstance(project) - - val res = arrayListOf() - 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 - } -} diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/KotlinExternalSystemSyncListener.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/KotlinExternalSystemSyncListener.kt index d0908375116..a073e50e7f8 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/KotlinExternalSystemSyncListener.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/KotlinExternalSystemSyncListener.kt @@ -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() } diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/ui/KotlinConfigurationCheckerComponent.kt.192 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/ui/KotlinConfigurationCheckerComponent.kt.192 index 2d61f3011bd..3ffb536b6d4 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/ui/KotlinConfigurationCheckerComponent.kt.192 +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/ui/KotlinConfigurationCheckerComponent.kt.192 @@ -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) }) diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/ui/KotlinConfigurationCheckerService.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/ui/KotlinConfigurationCheckerService.kt index e58dc4bfac1..46e882ac324 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/ui/KotlinConfigurationCheckerService.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/ui/KotlinConfigurationCheckerService.kt @@ -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()) } diff --git a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/MavenImportListener.kt b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/MavenImportListener.kt index b4220a86361..938d3635302 100644 --- a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/MavenImportListener.kt +++ b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/MavenImportListener.kt @@ -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, _: List -> 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() } } \ No newline at end of file diff --git a/idea/idea-maven/test/org/jetbrains/kotlin/idea/maven/MavenMigrateTest.kt b/idea/idea-maven/test/org/jetbrains/kotlin/idea/maven/MavenMigrateTest.kt index ce9d1d8c169..290a5c319fd 100644 --- a/idea/idea-maven/test/org/jetbrains/kotlin/idea/maven/MavenMigrateTest.kt +++ b/idea/idea-maven/test/org/jetbrains/kotlin/idea/maven/MavenMigrateTest.kt @@ -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() - val migrationProjectComponent = KotlinMigrationProjectComponent.getInstanceIfNotDisposed(myProject) + val importResult = FutureResult() + val migrationProjectComponent = KotlinMigrationProjectService.getInstanceIfNotDisposed(myProject) ?: error("Disposed project") migrationProjectComponent.setImportFinishListener { migrationState -> diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index 2e440b1e447..33fd3ff74e6 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -1,26 +1,4 @@ - - - org.jetbrains.kotlin.idea.completion.LookupCancelWatcher - - - org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener - - - org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener - - - org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider - org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider - - - org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent - - - org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsModel - - - org.jetbrains.kotlin.idea.versions.KotlinUpdatePluginComponent @@ -216,14 +194,6 @@ - - - - - - - - @@ -319,6 +289,8 @@ + + + + diff --git a/idea/resources/META-INF/plugin-common.xml.192 b/idea/resources/META-INF/plugin-common.xml.192 deleted file mode 100644 index 23c8593d934..00000000000 --- a/idea/resources/META-INF/plugin-common.xml.192 +++ /dev/null @@ -1,3658 +0,0 @@ - - - - org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory - - - org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory - - - org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory - - - - org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory - - - org.jetbrains.kotlin.idea.completion.LookupCancelWatcher - - - org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener - - - org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener - - - org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider - org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider - - - org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent - - - org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsModel - - - - - - org.jetbrains.kotlin.idea.PluginStartupComponent - - - - org.jetbrains.kotlin.idea.versions.KotlinUpdatePluginComponent - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.jetbrains.kotlin.idea.intentions.ImportMemberIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ImportAllMembersIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyInDestructuringAssignmentIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertToBlockBodyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.conventionNameCalls.ReplaceContainsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.conventionNameCalls.ReplaceInvokeIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.conventionNameCalls.ReplaceCallWithUnaryOperatorIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.FoldIfToReturnAsymmetricallyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.UnfoldAssignmentToIfIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.UnfoldPropertyToIfIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.UnfoldAssignmentToWhenIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.UnfoldPropertyToWhenIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.UnfoldReturnToIfIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.UnfoldReturnToWhenIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.DoubleBangToIfThenIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.ElvisToIfThenIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.SafeAccessToIfThenIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IfToWhenIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.WhenToIfIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.FlattenWhenIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.EliminateWhenSubjectIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveUnnecessaryParenthesesIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeArgumentsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveExplicitSuperQualifierIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.InsertCurlyBracesToTemplateIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.MoveLambdaInsideParenthesesIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArgumentsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.declarations.SplitPropertyDeclarationIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.declarations.ConvertMemberToExtensionIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ReconstructTypeInCastOrIsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.InfixCallToOrdinaryIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ToInfixCallIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ReplaceExplicitFunctionLiteralParamWithItIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ReplaceItWithExplicitFunctionLiteralParamIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveBracesIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddBracesIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertBinaryExpressionWithDemorgansLawIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.SimplifyBooleanWithConstantsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddForLoopIndicesIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveForLoopIndicesIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.loopToCallChain.UseWithIndexIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.SwapBinaryExpressionIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.SwapStringEqualsIgnoreCaseIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.SplitIfIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ReplaceWithOrdinaryAssignmentIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.SpecifyExplicitLambdaSignatureIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveExplicitLambdaParameterTypesIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertForEachToForLoopIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertToForEachFunctionCallIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertToStringTemplateIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertToRawStringTemplateIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertToConcatenatedStringIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertFunctionToPropertyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertPropertyToFunctionIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertReceiverToParameterIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertParameterToReceiverIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertPropertyInitializerToGetterIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.InvertIfConditionIntention - Kotlin - - - - org.jetbrains.kotlin.idea.refactoring.move.changePackage.ChangePackageIntention - Kotlin - - - - org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ExtractDeclarationFromCurrentFileIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ChangeVisibilityModifierIntention$Public - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ChangeVisibilityModifierIntention$Private - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ChangeVisibilityModifierIntention$Protected - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ChangeVisibilityModifierIntention$Internal - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddNameToArgumentIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddNamesToFollowingArgumentsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ReplaceUnderscoreWithParameterNameIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddJvmOverloadsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddJvmStaticIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveArgumentNameIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.IterateExpressionIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.UsePropertyAccessSyntaxIntention - Kotlin - - - - org.jetbrains.kotlin.idea.quickfix.AddConstModifierIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.IntroduceBackingPropertyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.JoinDeclarationAndAssignmentIntention - Kotlin - - - - org.jetbrains.kotlin.idea.testIntegration.KotlinCreateTestIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.DestructureIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AnonymousFunctionToLambdaIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ImplementAbstractMemberIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ImplementAbstractMemberAsConstructorParameterIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddValVarToConstructorParameterAction$Intention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.MoveMemberToCompanionObjectIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.MoveMemberOutOfCompanionObjectIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.CreateKotlinSubClassIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ToRawStringLiteralIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.TrailingCommaIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ToOrdinaryStringLiteralIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.IntroduceVariableIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.IntroduceImportAliasIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveSingleExpressionStringTemplateIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ReplaceUntilWithRangeToIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveEmptyParenthesesFromLambdaCallIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertCamelCaseTestFunctionToSpacedIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertSnakeCaseTestFunctionToSpacedIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.copyConcatenatedStringToClipboard.CopyConcatenatedStringToClipboardIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveConstructorKeywordIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertPrimaryConstructorToSecondaryIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertSecondaryConstructorToPrimaryIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ReplaceSizeCheckWithIsNotEmptyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ReplaceSizeZeroCheckWithIsEmptyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveEmptyClassBodyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertEnumToSealedClassIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertSealedClassToEnumIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveRedundantCallsOfConversionMethodsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveEmptyPrimaryConstructorIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveEmptySecondaryConstructorBodyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertTryFinallyToUseCallIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddNamesToCallArgumentsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertFunctionTypeParameterToReceiverIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertFunctionTypeReceiverToParameterIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertRangeCheckToTwoComparisonsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RenameFileToMatchClassIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertObjectLiteralToClassIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.MergeIfsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.MergeElseIfIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddMissingDestructuringIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertToApplyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertToAlsoIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertToWithIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertToRunIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.MovePropertyToClassBodyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.MovePropertyToConstructorIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddOpenModifierIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ValToObjectIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ChopParameterListIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ChopArgumentListIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.NullableBooleanEqualityCheckToElvisIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ReplaceAddWithPlusAssignIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddPropertyAccessorsIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddPropertyGetterIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddPropertySetterIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.MoveMemberToTopLevelIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertUnsafeCastCallToUnsafeCastIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertUnsafeCastToUnsafeCastCallIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveLabeledReturnInLambdaIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddLabeledReturnInLambdaIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddAnnotationUseSiteTargetIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.JoinParameterListIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.JoinArgumentListIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertLineCommentToBlockCommentIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertBlockCommentToLineCommentIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.IndentRawStringIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertVarargParameterToArrayIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertArrayParameterToVarargIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.LambdaToAnonymousFunctionIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddWhenRemainingBranchesIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertPropertyGetterToInitializerIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.SamConversionToAnonymousObjectIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.AddThrowsAnnotationIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertNullablePropertyToLateinitIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertLateinitPropertyToNullableIntention - Kotlin - - - - org.jetbrains.kotlin.idea.parameterInfo.custom.DisableReturnLambdaHintOptionAction - - - - org.jetbrains.kotlin.idea.intentions.AddUnderscoresToNumericLiteralIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ReplaceMapGetOrDefaultIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RemoveUnderscoresFromNumericLiteralIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.RenameClassToContainingFileNameIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertCollectionConstructorToFunction - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertOrdinaryPropertyToLazyIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertLazyPropertyToOrdinaryIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertLambdaToMultiLineIntention - Kotlin - - - - org.jetbrains.kotlin.idea.intentions.ConvertLambdaToSingleLineIntention - Kotlin - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/idea/resources/META-INF/plugin.xml b/idea/resources/META-INF/plugin.xml index cfa2a03dbc8..98c0c50098b 100644 --- a/idea/resources/META-INF/plugin.xml +++ b/idea/resources/META-INF/plugin.xml @@ -57,6 +57,12 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + + org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener + + + @@ -71,6 +77,20 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + + + + + + + + + + + + + diff --git a/idea/resources/META-INF/plugin.xml.191 b/idea/resources/META-INF/plugin.xml.191 index f6fc3694563..b757d535d62 100644 --- a/idea/resources/META-INF/plugin.xml.191 +++ b/idea/resources/META-INF/plugin.xml.191 @@ -56,6 +56,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + + org.jetbrains.kotlin.idea.completion.LookupCancelWatcher + + + org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener + + + org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent + + + org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory + + + org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory + + + org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory + + + + org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory + + + org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener + + + + + + org.jetbrains.kotlin.idea.PluginStartupComponent + + + diff --git a/idea/resources/META-INF/plugin.xml.192 b/idea/resources/META-INF/plugin.xml.192 index d440dd1730c..4753e37ad2e 100644 --- a/idea/resources/META-INF/plugin.xml.192 +++ b/idea/resources/META-INF/plugin.xml.192 @@ -56,6 +56,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + + org.jetbrains.kotlin.idea.completion.LookupCancelWatcher + + + org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener + + + org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent + + + org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory + + + org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory + + + org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory + + + + org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory + + + org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener + + + + + + org.jetbrains.kotlin.idea.PluginStartupComponent + + + diff --git a/idea/resources/META-INF/plugin.xml.201 b/idea/resources/META-INF/plugin.xml.201 index 725e6e8f1e6..b5b049b8573 100644 --- a/idea/resources/META-INF/plugin.xml.201 +++ b/idea/resources/META-INF/plugin.xml.201 @@ -71,6 +71,22 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + + + + + + + + + + + + + + + diff --git a/idea/resources/META-INF/plugin.xml.as35 b/idea/resources/META-INF/plugin.xml.as35 index 34a8a53d4ea..816fc4f071e 100644 --- a/idea/resources/META-INF/plugin.xml.as35 +++ b/idea/resources/META-INF/plugin.xml.as35 @@ -57,6 +57,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + + org.jetbrains.kotlin.idea.completion.LookupCancelWatcher + + + org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener + + + org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent + + + org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory + + + org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory + + + org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory + + + + org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory + + + org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener + + + + + + org.jetbrains.kotlin.idea.PluginStartupComponent + + + diff --git a/idea/resources/META-INF/plugin.xml.as36 b/idea/resources/META-INF/plugin.xml.as36 index 0ed3e561674..f607fdad523 100644 --- a/idea/resources/META-INF/plugin.xml.as36 +++ b/idea/resources/META-INF/plugin.xml.as36 @@ -57,6 +57,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + + org.jetbrains.kotlin.idea.completion.LookupCancelWatcher + + + org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener + + + org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent + + + org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory + + + org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory + + + org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory + + + + org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory + + + org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener + + + + + + org.jetbrains.kotlin.idea.PluginStartupComponent + + + diff --git a/idea/resources/META-INF/plugin.xml.as40 b/idea/resources/META-INF/plugin.xml.as40 index 2d027728d77..a2167a10b80 100644 --- a/idea/resources/META-INF/plugin.xml.as40 +++ b/idea/resources/META-INF/plugin.xml.as40 @@ -56,6 +56,12 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + + org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener + + + @@ -65,16 +71,30 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. - - - - + + + + + + + + + + + + + + + + + + diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt index e1512e9dc00..973a6431f73 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt @@ -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() - - 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 = 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() - 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 -} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt.192 b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt.192 new file mode 100644 index 00000000000..784ac750bff --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt.192 @@ -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() + + 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 = 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() + 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 +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectService.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectService.kt new file mode 100644 index 00000000000..3caf9147626 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectService.kt @@ -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() + + 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 = 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() + 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 +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectService.kt.192 b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectService.kt.192 new file mode 100644 index 00000000000..3b1ba4fe7f0 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectService.kt.192 @@ -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 \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/parameterInfo/custom/KotlinCodeHintsModel.kt b/idea/src/org/jetbrains/kotlin/idea/parameterInfo/custom/KotlinCodeHintsModel.kt index dbbc1e03dad..0ca09cba6c2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/parameterInfo/custom/KotlinCodeHintsModel.kt +++ b/idea/src/org/jetbrains/kotlin/idea/parameterInfo/custom/KotlinCodeHintsModel.kt @@ -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) { diff --git a/idea/tests/org/jetbrains/kotlin/idea/resolve/AbstractAdditionalResolveDescriptorRendererTest.kt b/idea/tests/org/jetbrains/kotlin/idea/resolve/AbstractAdditionalResolveDescriptorRendererTest.kt index c714f053fa3..94a1868b5d8 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/resolve/AbstractAdditionalResolveDescriptorRendererTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/resolve/AbstractAdditionalResolveDescriptorRendererTest.kt @@ -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() } diff --git a/idea/tests/org/jetbrains/kotlin/idea/resolve/compat.kt b/idea/tests/org/jetbrains/kotlin/idea/resolve/compat.kt new file mode 100644 index 00000000000..2d78799a363 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/resolve/compat.kt @@ -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) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/resolve/compat.kt.201 b/idea/tests/org/jetbrains/kotlin/idea/resolve/compat.kt.201 new file mode 100644 index 00000000000..032076246f8 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/resolve/compat.kt.201 @@ -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) +} \ No newline at end of file