Clean up of plugin-common.xml and convert components to services
This commit is contained in:
+4
-6
@@ -16,6 +16,7 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.rootManager
|
||||
import com.intellij.openapi.roots.ModuleRootEvent
|
||||
import com.intellij.openapi.roots.ModuleRootListener
|
||||
import com.intellij.openapi.startup.StartupActivity
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
@@ -47,14 +48,14 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
|
||||
class KotlinPackageContentModificationListener(private val project: Project) : Disposable {
|
||||
val connection = project.messageBus.connect()
|
||||
class KotlinPackageContentModificationListener : StartupActivity {
|
||||
|
||||
companion object {
|
||||
val LOG = Logger.getInstance(this::class.java)
|
||||
}
|
||||
|
||||
init {
|
||||
override fun runActivity(project: Project) {
|
||||
val connection = project.messageBus.connect(project)
|
||||
connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
|
||||
override fun before(events: MutableList<out VFileEvent>) = onEvents(events, false)
|
||||
override fun after(events: List<VFileEvent>) = onEvents(events, true)
|
||||
@@ -116,9 +117,6 @@ class KotlinPackageContentModificationListener(private val project: Project) : D
|
||||
})
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
connection.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinPackageStatementPsiTreeChangePreprocessor(private val project: Project) : PsiTreeChangePreprocessor {
|
||||
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.caches
|
||||
|
||||
import com.intellij.ProjectTopics
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.rootManager
|
||||
import com.intellij.openapi.roots.ModuleRootEvent
|
||||
import com.intellij.openapi.roots.ModuleRootListener
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.openapi.vfs.newvfs.BulkFileListener
|
||||
import com.intellij.openapi.vfs.newvfs.events.*
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.impl.PsiManagerEx
|
||||
import com.intellij.psi.impl.PsiTreeChangeEventImpl
|
||||
import com.intellij.psi.impl.PsiTreeChangePreprocessor
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.caches.PerModulePackageCacheService.Companion.DEBUG_LOG_ENABLE_PerModulePackageCache
|
||||
import org.jetbrains.kotlin.idea.caches.PerModulePackageCacheService.Companion.FULL_DROP_THRESHOLD
|
||||
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.getModuleInfoByVirtualFile
|
||||
import org.jetbrains.kotlin.idea.caches.project.getNullableModuleInfo
|
||||
import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil
|
||||
import org.jetbrains.kotlin.idea.util.getSourceRoot
|
||||
import org.jetbrains.kotlin.idea.util.sourceRoot
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtPackageDirective
|
||||
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
|
||||
class KotlinPackageContentModificationListener(private val project: Project) : Disposable {
|
||||
val connection = project.messageBus.connect()
|
||||
|
||||
companion object {
|
||||
val LOG = Logger.getInstance(this::class.java)
|
||||
}
|
||||
|
||||
init {
|
||||
connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
|
||||
override fun before(events: MutableList<out VFileEvent>) = onEvents(events, false)
|
||||
override fun after(events: List<VFileEvent>) = onEvents(events, true)
|
||||
|
||||
private fun isRelevant(event: VFileEvent): Boolean = when (event) {
|
||||
is VFilePropertyChangeEvent -> false
|
||||
is VFileCreateEvent -> true
|
||||
is VFileMoveEvent -> true
|
||||
is VFileDeleteEvent -> true
|
||||
is VFileContentChangeEvent -> true
|
||||
is VFileCopyEvent -> true
|
||||
else -> {
|
||||
LOG.warn("Unknown vfs event: ${event.javaClass}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun onEvents(events: List<VFileEvent>, isAfter: Boolean) {
|
||||
val service = PerModulePackageCacheService.getInstance(project)
|
||||
val fileManager = PsiManagerEx.getInstanceEx(project).fileManager
|
||||
if (events.size >= FULL_DROP_THRESHOLD) {
|
||||
service.onTooComplexChange()
|
||||
} else {
|
||||
events.asSequence()
|
||||
.filter(::isRelevant)
|
||||
.filter {
|
||||
(it.isValid || it !is VFileCreateEvent) && it.file != null
|
||||
}
|
||||
.filter {
|
||||
val vFile = it.file!!
|
||||
vFile.isDirectory || vFile.fileType == KotlinFileType.INSTANCE
|
||||
}
|
||||
.filter {
|
||||
// It expected that content change events will be duplicated with more precise PSI events and processed
|
||||
// in KotlinPackageStatementPsiTreeChangePreprocessor, but events might have been missing if PSI view provider
|
||||
// is absent.
|
||||
if (it is VFileContentChangeEvent) {
|
||||
isAfter && fileManager.findCachedViewProvider(it.file) == null
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
.filter {
|
||||
when (val origin = it.requestor) {
|
||||
is Project -> origin == project
|
||||
is PsiManager -> origin.project == project
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
.forEach { event -> service.notifyPackageChange(event) }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
|
||||
override fun rootsChanged(event: ModuleRootEvent) {
|
||||
PerModulePackageCacheService.getInstance(project).onTooComplexChange()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
connection.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinPackageStatementPsiTreeChangePreprocessor(private val project: Project) : PsiTreeChangePreprocessor {
|
||||
override fun treeChanged(event: PsiTreeChangeEventImpl) {
|
||||
val eFile = event.file ?: event.child as? PsiFile
|
||||
if (eFile == null) {
|
||||
LOG.debugIfEnabled(project, true) { "Got PsiEvent: $event without file" }
|
||||
}
|
||||
val file = eFile as? KtFile ?: return
|
||||
|
||||
when (event.code) {
|
||||
PsiTreeChangeEventImpl.PsiEventType.CHILD_ADDED,
|
||||
PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED,
|
||||
PsiTreeChangeEventImpl.PsiEventType.CHILD_REPLACED,
|
||||
PsiTreeChangeEventImpl.PsiEventType.CHILD_REMOVED -> {
|
||||
val child = event.child ?: run {
|
||||
LOG.debugIfEnabled(project, true) { "Got PsiEvent: $event without child" }
|
||||
return
|
||||
}
|
||||
if (child.getParentOfType<KtPackageDirective>(false) != null)
|
||||
ServiceManager.getService(project, PerModulePackageCacheService::class.java).notifyPackageChange(file)
|
||||
}
|
||||
PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED -> {
|
||||
val parent = event.parent ?: run {
|
||||
LOG.debugIfEnabled(project, true) { "Got PsiEvent: $event without parent" }
|
||||
return
|
||||
}
|
||||
val childrenOfType = parent.getChildrenOfType<KtPackageDirective>()
|
||||
if (
|
||||
(!event.isGenericChange && (childrenOfType.any() || parent is KtPackageDirective)) ||
|
||||
(childrenOfType.any { it.name.isEmpty() } && parent is KtFile)
|
||||
) {
|
||||
ServiceManager.getService(project, PerModulePackageCacheService::class.java).notifyPackageChange(file)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOG = Logger.getInstance(this::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private typealias ImplicitPackageData = MutableMap<FqName, MutableList<VirtualFile>>
|
||||
|
||||
class ImplicitPackagePrefixCache(private val project: Project) {
|
||||
private val implicitPackageCache = ConcurrentHashMap<VirtualFile, ImplicitPackageData>()
|
||||
|
||||
fun getPrefix(sourceRoot: VirtualFile): FqName {
|
||||
val implicitPackageMap = implicitPackageCache.getOrPut(sourceRoot) { analyzeImplicitPackagePrefixes(sourceRoot) }
|
||||
return implicitPackageMap.keys.singleOrNull() ?: FqName.ROOT
|
||||
}
|
||||
|
||||
internal fun clear() {
|
||||
implicitPackageCache.clear()
|
||||
}
|
||||
|
||||
private fun analyzeImplicitPackagePrefixes(sourceRoot: VirtualFile): MutableMap<FqName, MutableList<VirtualFile>> {
|
||||
val result = mutableMapOf<FqName, MutableList<VirtualFile>>()
|
||||
val ktFiles = sourceRoot.children.filter { it.fileType == KotlinFileType.INSTANCE }
|
||||
for (ktFile in ktFiles) {
|
||||
result.addFile(ktFile)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun ImplicitPackageData.addFile(ktFile: VirtualFile) {
|
||||
synchronized(this) {
|
||||
val psiFile = PsiManager.getInstance(project).findFile(ktFile) as? KtFile ?: return
|
||||
addPsiFile(psiFile, ktFile)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ImplicitPackageData.addPsiFile(
|
||||
psiFile: KtFile,
|
||||
ktFile: VirtualFile
|
||||
) = getOrPut(psiFile.packageFqName) { mutableListOf() }.add(ktFile)
|
||||
|
||||
private fun ImplicitPackageData.removeFile(file: VirtualFile) {
|
||||
synchronized(this) {
|
||||
for ((key, value) in this) {
|
||||
if (value.remove(file)) {
|
||||
if (value.isEmpty()) remove(key)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ImplicitPackageData.updateFile(file: KtFile) {
|
||||
synchronized(this) {
|
||||
removeFile(file.virtualFile)
|
||||
addPsiFile(file, file.virtualFile)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun update(event: VFileEvent) {
|
||||
when (event) {
|
||||
is VFileCreateEvent -> checkNewFileInSourceRoot(event.file)
|
||||
is VFileDeleteEvent -> checkDeletedFileInSourceRoot(event.file)
|
||||
is VFileCopyEvent -> {
|
||||
val newParent = event.newParent
|
||||
if (newParent.isValid) {
|
||||
checkNewFileInSourceRoot(newParent.findChild(event.newChildName))
|
||||
}
|
||||
}
|
||||
is VFileMoveEvent -> {
|
||||
checkNewFileInSourceRoot(event.file)
|
||||
if (event.oldParent.getSourceRoot(project) == event.oldParent) {
|
||||
implicitPackageCache[event.oldParent]?.removeFile(event.file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkNewFileInSourceRoot(file: VirtualFile?) {
|
||||
if (file == null) return
|
||||
if (file.getSourceRoot(project) == file.parent) {
|
||||
implicitPackageCache[file.parent]?.addFile(file)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDeletedFileInSourceRoot(file: VirtualFile?) {
|
||||
val directory = file?.parent
|
||||
if (directory == null || !directory.isValid) return
|
||||
if (directory.getSourceRoot(project) == directory) {
|
||||
implicitPackageCache[directory]?.removeFile(file)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun update(ktFile: KtFile) {
|
||||
val parent = ktFile.virtualFile?.parent ?: return
|
||||
if (ktFile.sourceRoot == parent) {
|
||||
implicitPackageCache[parent]?.updateFile(ktFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PerModulePackageCacheService(private val project: Project) : Disposable {
|
||||
|
||||
/*
|
||||
* Actually an WeakMap<Module, SoftMap<ModuleSourceInfo, SoftMap<FqName, Boolean>>>
|
||||
*/
|
||||
private val cache = ContainerUtil.createConcurrentWeakMap<Module, ConcurrentMap<ModuleSourceInfo, ConcurrentMap<FqName, Boolean>>>()
|
||||
private val implicitPackagePrefixCache = ImplicitPackagePrefixCache(project)
|
||||
|
||||
private val pendingVFileChanges: MutableSet<VFileEvent> = mutableSetOf()
|
||||
private val pendingKtFileChanges: MutableSet<KtFile> = mutableSetOf()
|
||||
|
||||
private val projectScope = GlobalSearchScope.projectScope(project)
|
||||
|
||||
internal fun onTooComplexChange() {
|
||||
clear()
|
||||
}
|
||||
|
||||
private fun clear() {
|
||||
synchronized(this) {
|
||||
pendingVFileChanges.clear()
|
||||
pendingKtFileChanges.clear()
|
||||
cache.clear()
|
||||
implicitPackagePrefixCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun notifyPackageChange(file: VFileEvent): Unit = synchronized(this) {
|
||||
pendingVFileChanges += file
|
||||
}
|
||||
|
||||
internal fun notifyPackageChange(file: KtFile): Unit = synchronized(this) {
|
||||
pendingKtFileChanges += file
|
||||
}
|
||||
|
||||
private fun invalidateCacheForModuleSourceInfo(moduleSourceInfo: ModuleSourceInfo) {
|
||||
LOG.debugIfEnabled(project) { "Invalidated cache for $moduleSourceInfo" }
|
||||
val perSourceInfoData = cache[moduleSourceInfo.module] ?: return
|
||||
val dataForSourceInfo = perSourceInfoData[moduleSourceInfo] ?: return
|
||||
dataForSourceInfo.clear()
|
||||
}
|
||||
|
||||
private fun checkPendingChanges() = synchronized(this) {
|
||||
if (pendingVFileChanges.size + pendingKtFileChanges.size >= FULL_DROP_THRESHOLD) {
|
||||
onTooComplexChange()
|
||||
} else {
|
||||
pendingVFileChanges.processPending { event ->
|
||||
val vfile = event.file ?: return@processPending
|
||||
// When VirtualFile !isValid (deleted for example), it impossible to use getModuleInfoByVirtualFile
|
||||
// For directory we must check both is it in some sourceRoot, and is it contains some sourceRoot
|
||||
if (vfile.isDirectory || !vfile.isValid) {
|
||||
for ((module, data) in cache) {
|
||||
val sourceRootUrls = module.rootManager.sourceRootUrls
|
||||
if (sourceRootUrls.any { url ->
|
||||
vfile.containedInOrContains(url)
|
||||
}) {
|
||||
LOG.debugIfEnabled(project) { "Invalidated cache for $module" }
|
||||
data.clear()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val infoByVirtualFile = getModuleInfoByVirtualFile(project, vfile)
|
||||
if (infoByVirtualFile == null || infoByVirtualFile !is ModuleSourceInfo) {
|
||||
LOG.debugIfEnabled(project) { "Skip $vfile as it has mismatched ModuleInfo=$infoByVirtualFile" }
|
||||
}
|
||||
(infoByVirtualFile as? ModuleSourceInfo)?.let {
|
||||
invalidateCacheForModuleSourceInfo(it)
|
||||
}
|
||||
}
|
||||
|
||||
implicitPackagePrefixCache.update(event)
|
||||
}
|
||||
|
||||
pendingKtFileChanges.processPending { file ->
|
||||
if (file.virtualFile != null && file.virtualFile !in projectScope) {
|
||||
LOG.debugIfEnabled(project) {
|
||||
"Skip $file without vFile, or not in scope: ${file.virtualFile?.let { it !in projectScope }}"
|
||||
}
|
||||
return@processPending
|
||||
}
|
||||
val nullableModuleInfo = file.getNullableModuleInfo()
|
||||
(nullableModuleInfo as? ModuleSourceInfo)?.let { invalidateCacheForModuleSourceInfo(it) }
|
||||
if (nullableModuleInfo == null || nullableModuleInfo !is ModuleSourceInfo) {
|
||||
LOG.debugIfEnabled(project) { "Skip $file as it has mismatched ModuleInfo=$nullableModuleInfo" }
|
||||
}
|
||||
implicitPackagePrefixCache.update(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> MutableCollection<T>.processPending(crossinline body: (T) -> Unit) {
|
||||
this.removeIf { value ->
|
||||
try {
|
||||
body(value)
|
||||
} catch (pce: ProcessCanceledException) {
|
||||
throw pce
|
||||
} catch (exc: Exception) {
|
||||
// Log and proceed. Otherwise pending object processing won't be cleared and exception will be thrown forever.
|
||||
LOG.error(exc)
|
||||
}
|
||||
|
||||
return@removeIf true
|
||||
}
|
||||
}
|
||||
|
||||
private fun VirtualFile.containedInOrContains(root: String) =
|
||||
(VfsUtilCore.isEqualOrAncestor(url, root)
|
||||
|| isDirectory && VfsUtilCore.isEqualOrAncestor(root, url))
|
||||
|
||||
|
||||
fun packageExists(packageFqName: FqName, moduleInfo: ModuleSourceInfo): Boolean {
|
||||
val module = moduleInfo.module
|
||||
checkPendingChanges()
|
||||
|
||||
val perSourceInfoCache = cache.getOrPut(module) {
|
||||
ContainerUtil.createConcurrentSoftMap()
|
||||
}
|
||||
val cacheForCurrentModuleInfo = perSourceInfoCache.getOrPut(moduleInfo) {
|
||||
ContainerUtil.createConcurrentSoftMap()
|
||||
}
|
||||
|
||||
return cacheForCurrentModuleInfo.getOrPut(packageFqName) {
|
||||
val packageExists = PackageIndexUtil.packageExists(packageFqName, moduleInfo.contentScope(), project)
|
||||
LOG.debugIfEnabled(project) { "Computed cache value for $packageFqName in $moduleInfo is $packageExists" }
|
||||
packageExists
|
||||
}
|
||||
}
|
||||
|
||||
fun getImplicitPackagePrefix(sourceRoot: VirtualFile): FqName {
|
||||
checkPendingChanges()
|
||||
return implicitPackagePrefixCache.getPrefix(sourceRoot)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val FULL_DROP_THRESHOLD = 1000
|
||||
private val LOG = Logger.getInstance(this::class.java)
|
||||
|
||||
fun getInstance(project: Project): PerModulePackageCacheService =
|
||||
ServiceManager.getService(project, PerModulePackageCacheService::class.java)
|
||||
|
||||
var Project.DEBUG_LOG_ENABLE_PerModulePackageCache: Boolean
|
||||
by NotNullableUserDataProperty<Project, Boolean>(Key.create("debug.PerModulePackageCache"), false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Logger.debugIfEnabled(project: Project, withCurrentTrace: Boolean = false, message: () -> String) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode && project.DEBUG_LOG_ENABLE_PerModulePackageCache) {
|
||||
val msg = message()
|
||||
if (withCurrentTrace) {
|
||||
val e = Exception().apply { fillInStackTrace() }
|
||||
this.debug(msg, e)
|
||||
} else {
|
||||
this.debug(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.idea.caches.project
|
||||
|
||||
import com.intellij.ide.highlighter.ArchiveFileType
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.fileTypes.FileTypeEvent
|
||||
import com.intellij.openapi.fileTypes.FileTypeListener
|
||||
import com.intellij.openapi.fileTypes.FileTypeManager
|
||||
@@ -23,15 +22,16 @@ import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent
|
||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||
|
||||
class LibraryModificationTracker(project: Project) : SimpleModificationTracker() {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(project: Project) = ServiceManager.getService(project, LibraryModificationTracker::class.java)!!
|
||||
fun getInstance(project: Project) = project.getServiceSafe(LibraryModificationTracker::class.java)
|
||||
}
|
||||
|
||||
init {
|
||||
val connection = project.messageBus.connect()
|
||||
val connection = project.messageBus.connect(project)
|
||||
connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
|
||||
override fun after(events: List<VFileEvent>) {
|
||||
events.filter(::isRelevantEvent).let { createEvents ->
|
||||
|
||||
+2
-2
@@ -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
|
||||
}
|
||||
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.caches.trackers
|
||||
|
||||
import com.intellij.lang.ASTNode
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.ModificationTracker
|
||||
import com.intellij.openapi.util.SimpleModificationTracker
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.pom.PomManager
|
||||
import com.intellij.pom.PomModelAspect
|
||||
import com.intellij.pom.event.PomModelEvent
|
||||
import com.intellij.pom.event.PomModelListener
|
||||
import com.intellij.pom.tree.TreeAspect
|
||||
import com.intellij.pom.tree.events.TreeChangeEvent
|
||||
import com.intellij.pom.tree.events.impl.ChangeInfoImpl
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.PsiManagerImpl
|
||||
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
||||
import com.intellij.psi.impl.PsiTreeChangeEventImpl
|
||||
import com.intellij.psi.impl.PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED
|
||||
import com.intellij.psi.impl.PsiTreeChangeEventImpl.PsiEventType.PROPERTY_CHANGED
|
||||
import com.intellij.psi.impl.PsiTreeChangePreprocessor
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
|
||||
val KOTLIN_CONSOLE_KEY = Key.create<Boolean>("kotlin.console")
|
||||
|
||||
/**
|
||||
* Tested in OutOfBlockModificationTestGenerated
|
||||
*/
|
||||
class KotlinCodeBlockModificationListener(project: Project) : PsiTreeChangePreprocessor {
|
||||
private val treeAspect: TreeAspect = TreeAspect.getInstance(project)
|
||||
|
||||
private val modificationTrackerImpl: PsiModificationTrackerImpl =
|
||||
PsiModificationTracker.SERVICE.getInstance(project) as PsiModificationTrackerImpl
|
||||
|
||||
@Volatile
|
||||
private var kotlinModificationTracker: Long = 0
|
||||
|
||||
private val kotlinOutOfCodeBlockTrackerImpl: SimpleModificationTracker = SimpleModificationTracker()
|
||||
|
||||
val kotlinOutOfCodeBlockTracker: ModificationTracker = kotlinOutOfCodeBlockTrackerImpl
|
||||
|
||||
internal val perModuleOutOfCodeBlockTrackerUpdater = KotlinModuleOutOfCodeBlockModificationTracker.Updater(project)
|
||||
|
||||
init {
|
||||
val model = PomManager.getModel(project)
|
||||
val messageBusConnection = project.messageBus.connect(project)
|
||||
|
||||
val psiManager = PsiManager.getInstance(project) as PsiManagerImpl
|
||||
psiManager.addTreeChangePreprocessor(this)
|
||||
|
||||
model.addModelListener(object : PomModelListener {
|
||||
override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean {
|
||||
return aspect == treeAspect
|
||||
}
|
||||
|
||||
override fun modelChanged(event: PomModelEvent) {
|
||||
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
|
||||
val ktFile = changeSet.rootElement.psi.containingFile as? KtFile ?: return
|
||||
|
||||
incFileModificationCount(ktFile)
|
||||
|
||||
val changedElements = changeSet.changedElements
|
||||
|
||||
// skip change if it contains only virtual/fake change
|
||||
if (changedElements.isNotEmpty()) {
|
||||
// ignore formatting (whitespaces etc)
|
||||
if (isFormattingChange(changeSet) || isCommentChange(changeSet)) return
|
||||
}
|
||||
|
||||
val inBlockElements = inBlockModifications(changedElements)
|
||||
|
||||
val physical = ktFile.isPhysical
|
||||
if (inBlockElements.isEmpty()) {
|
||||
messageBusConnection.deliverImmediately()
|
||||
|
||||
if (physical && !isReplLine(ktFile.virtualFile) && ktFile !is KtTypeCodeFragment) {
|
||||
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, true)
|
||||
}
|
||||
|
||||
ktFile.incOutOfBlockModificationCount()
|
||||
} else if (physical) {
|
||||
inBlockElements.forEach { it.containingKtFile.addInBlockModifiedItem(it) }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, PsiModificationTracker.Listener {
|
||||
val kotlinTrackerInternalIDECount =
|
||||
modificationTrackerImpl.forLanguage(KotlinLanguage.INSTANCE).modificationCount
|
||||
if (kotlinModificationTracker == kotlinTrackerInternalIDECount) {
|
||||
// Some update that we are not sure is from Kotlin language, as Kotlin language tracker wasn't changed
|
||||
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
||||
} else {
|
||||
kotlinModificationTracker = kotlinTrackerInternalIDECount
|
||||
}
|
||||
|
||||
perModuleOutOfCodeBlockTrackerUpdater.onPsiModificationTrackerUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
override fun treeChanged(event: PsiTreeChangeEventImpl) {
|
||||
if (!PsiModificationTrackerImpl.canAffectPsi(event)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Copy logic from PsiModificationTrackerImpl.treeChanged(). Some out-of-code-block events are written to language modification
|
||||
// tracker in PsiModificationTrackerImpl but don't have correspondent PomModelEvent. Increase kotlinOutOfCodeBlockTracker
|
||||
// manually if needed.
|
||||
val outOfCodeBlock = when (event.code) {
|
||||
PROPERTY_CHANGED ->
|
||||
event.propertyName === PsiTreeChangeEvent.PROP_UNLOADED_PSI || event.propertyName === PsiTreeChangeEvent.PROP_ROOTS
|
||||
CHILD_MOVED -> event.oldParent is PsiDirectory || event.newParent is PsiDirectory
|
||||
else -> event.parent is PsiDirectory
|
||||
}
|
||||
|
||||
if (outOfCodeBlock) {
|
||||
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun isReplLine(file: VirtualFile): Boolean {
|
||||
return file.getUserData(KOTLIN_CONSOLE_KEY) == true
|
||||
}
|
||||
|
||||
private fun incFileModificationCount(file: KtFile) {
|
||||
val tracker = file.getUserData(PER_FILE_MODIFICATION_TRACKER)
|
||||
?: file.putUserDataIfAbsent(PER_FILE_MODIFICATION_TRACKER, SimpleModificationTracker())
|
||||
tracker.incModificationCount()
|
||||
}
|
||||
|
||||
private fun inBlockModifications(elements: Array<ASTNode>): List<KtElement> {
|
||||
// When a code fragment is reparsed, Intellij doesn't do an AST diff and considers the entire
|
||||
// contents to be replaced, which is represented in a POM event as an empty list of changed elements
|
||||
|
||||
return elements.map { element ->
|
||||
val modificationScope = getInsideCodeBlockModificationScope(element.psi) ?: return emptyList()
|
||||
modificationScope.blockDeclaration
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSpecificChange(changeSet: TreeChangeEvent, precondition: (ASTNode?) -> Boolean): Boolean =
|
||||
changeSet.changedElements.all { changedElement ->
|
||||
val changesByElement = changeSet.getChangesByElement(changedElement)
|
||||
changesByElement.affectedChildren.all { affectedChild ->
|
||||
if (!precondition(affectedChild)) return@all false
|
||||
val changeByChild = changesByElement.getChangeByChild(affectedChild)
|
||||
return@all if (changeByChild is ChangeInfoImpl) {
|
||||
val oldChild = changeByChild.oldChild
|
||||
precondition(oldChild)
|
||||
} else false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCommentChange(changeSet: TreeChangeEvent): Boolean =
|
||||
isSpecificChange(changeSet) { it is PsiComment || it is KDoc }
|
||||
|
||||
private fun isFormattingChange(changeSet: TreeChangeEvent): Boolean =
|
||||
isSpecificChange(changeSet) { it is PsiWhiteSpace }
|
||||
|
||||
/**
|
||||
* Has to be aligned with [getInsideCodeBlockModificationScope] :
|
||||
*
|
||||
* result of analysis has to be reflected in dirty scope,
|
||||
* the only difference is whitespaces and comments
|
||||
*/
|
||||
fun getInsideCodeBlockModificationDirtyScope(element: PsiElement): PsiElement? {
|
||||
if (!element.isPhysical) return null
|
||||
// dirty scope for whitespaces and comments is the element itself
|
||||
if (element is PsiWhiteSpace || element is PsiComment || element is KDoc) return element
|
||||
|
||||
return getInsideCodeBlockModificationScope(element)?.blockDeclaration ?: null
|
||||
}
|
||||
|
||||
fun getInsideCodeBlockModificationScope(element: PsiElement): BlockModificationScopeElement? {
|
||||
val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
|
||||
if (lambda is KtLambdaExpression) {
|
||||
lambda.getTopmostParentOfType<KtSuperTypeCallEntry>()?.getTopmostParentOfType<KtClassOrObject>()?.let {
|
||||
return BlockModificationScopeElement(it, it)
|
||||
}
|
||||
}
|
||||
|
||||
val blockDeclaration =
|
||||
KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) as? KtDeclaration ?: return null
|
||||
// KtPsiUtil.getTopmostParentOfType<KtClassOrObject>(element) as? KtDeclaration ?: return null
|
||||
|
||||
// should not be local declaration
|
||||
if (KtPsiUtil.isLocal(blockDeclaration))
|
||||
return null
|
||||
|
||||
when (blockDeclaration) {
|
||||
is KtNamedFunction -> {
|
||||
// if (blockDeclaration.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE) {
|
||||
// topClassLikeDeclaration(blockDeclaration)?.let {
|
||||
// return BlockModificationScopeElement(it, it)
|
||||
// }
|
||||
// }
|
||||
if (blockDeclaration.hasBlockBody()) {
|
||||
// case like `fun foo(): String {...<caret>...}`
|
||||
return blockDeclaration.bodyExpression
|
||||
?.takeIf { it.isAncestor(element) }
|
||||
?.let { BlockModificationScopeElement(blockDeclaration, it) }
|
||||
} else if (blockDeclaration.hasDeclaredReturnType()) {
|
||||
// case like `fun foo(): String = b<caret>labla`
|
||||
return blockDeclaration.initializer
|
||||
?.takeIf { it.isAncestor(element) }
|
||||
?.let { BlockModificationScopeElement(blockDeclaration, it) }
|
||||
}
|
||||
}
|
||||
|
||||
is KtProperty -> {
|
||||
// if (blockDeclaration.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE) {
|
||||
// topClassLikeDeclaration(blockDeclaration)?.let {
|
||||
// return BlockModificationScopeElement(it, it)
|
||||
// }
|
||||
// }
|
||||
if (blockDeclaration.typeReference != null) {
|
||||
val accessors =
|
||||
blockDeclaration.accessors.map { it.initializer ?: it.bodyExpression } + blockDeclaration.initializer
|
||||
for (accessor in accessors) {
|
||||
accessor?.takeIf {
|
||||
it.isAncestor(element) &&
|
||||
// adding annotations to accessor is the same as change contract of property
|
||||
(element !is KtAnnotated || element.annotationEntries.isEmpty())
|
||||
}
|
||||
?.let { expression ->
|
||||
val declaration =
|
||||
KtPsiUtil.getTopmostParentOfTypes(blockDeclaration, KtClassOrObject::class.java) as? KtElement ?:
|
||||
// ktFile to check top level property declarations
|
||||
return null
|
||||
return BlockModificationScopeElement(declaration, expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is KtScriptInitializer -> {
|
||||
return (blockDeclaration.body as? KtCallExpression)
|
||||
?.lambdaArguments
|
||||
?.lastOrNull()
|
||||
?.getLambdaExpression()
|
||||
?.takeIf { it.isAncestor(element) }
|
||||
?.let { BlockModificationScopeElement(blockDeclaration, it) }
|
||||
}
|
||||
|
||||
is KtClassInitializer -> {
|
||||
blockDeclaration
|
||||
.takeIf { it.isAncestor(element) }
|
||||
?.let { ktClassInitializer ->
|
||||
(PsiTreeUtil.getParentOfType(blockDeclaration, KtClassOrObject::class.java))?.let {
|
||||
return BlockModificationScopeElement(it, ktClassInitializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is KtSecondaryConstructor -> {
|
||||
blockDeclaration
|
||||
?.takeIf {
|
||||
it.bodyExpression?.isAncestor(element) ?: false || it.getDelegationCallOrNull()?.isAncestor(element) ?: false
|
||||
}?.let { ktConstructor ->
|
||||
PsiTreeUtil.getParentOfType(blockDeclaration, KtClassOrObject::class.java)?.let {
|
||||
return BlockModificationScopeElement(it, ktConstructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
// is KtClassOrObject -> {
|
||||
// return when (element) {
|
||||
// is KtProperty, is KtNamedFunction -> {
|
||||
// if ((element as? KtModifierListOwner)?.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE)
|
||||
// BlockModificationScopeElement(blockDeclaration, blockDeclaration) else null
|
||||
// }
|
||||
// else -> null
|
||||
// }
|
||||
// }
|
||||
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
data class BlockModificationScopeElement(val blockDeclaration: KtElement, val element: KtElement)
|
||||
|
||||
fun isBlockDeclaration(declaration: KtDeclaration): Boolean {
|
||||
return BLOCK_DECLARATION_TYPES.any { it.isInstance(declaration) }
|
||||
}
|
||||
|
||||
private val BLOCK_DECLARATION_TYPES = arrayOf<Class<out KtDeclaration>>(
|
||||
KtProperty::class.java,
|
||||
KtNamedFunction::class.java,
|
||||
KtClassInitializer::class.java,
|
||||
KtSecondaryConstructor::class.java,
|
||||
KtScriptInitializer::class.java
|
||||
)
|
||||
|
||||
fun getInstance(project: Project): KotlinCodeBlockModificationListener =
|
||||
project.getServiceSafe(KotlinCodeBlockModificationListener::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private val PER_FILE_MODIFICATION_TRACKER = Key<SimpleModificationTracker>("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT")
|
||||
|
||||
val KtFile.perFileModificationTracker: ModificationTracker
|
||||
get() = putUserDataIfAbsent(PER_FILE_MODIFICATION_TRACKER, SimpleModificationTracker())
|
||||
|
||||
private val FILE_OUT_OF_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT")
|
||||
|
||||
val KtFile.outOfBlockModificationCount: Long by NotNullableUserDataProperty(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, 0)
|
||||
|
||||
private fun KtFile.incOutOfBlockModificationCount() {
|
||||
clearInBlockModifications()
|
||||
|
||||
val count = getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
|
||||
putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* inBlockModifications is a collection of block elements those have in-block modifications
|
||||
*/
|
||||
private val IN_BLOCK_MODIFICATIONS = Key<MutableCollection<KtElement>>("IN_BLOCK_MODIFICATIONS")
|
||||
private val FILE_IN_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_IN_BLOCK_MODIFICATION_COUNT")
|
||||
|
||||
val KtFile.inBlockModificationCount: Long by NotNullableUserDataProperty(FILE_IN_BLOCK_MODIFICATION_COUNT, 0)
|
||||
|
||||
val KtFile.inBlockModifications: Collection<KtElement>
|
||||
get() {
|
||||
val collection = getUserData(IN_BLOCK_MODIFICATIONS)
|
||||
return collection ?: emptySet()
|
||||
}
|
||||
|
||||
private fun KtFile.addInBlockModifiedItem(element: KtElement) {
|
||||
val collection = putUserDataIfAbsent(IN_BLOCK_MODIFICATIONS, mutableSetOf())
|
||||
synchronized(collection) {
|
||||
collection.add(element)
|
||||
}
|
||||
val count = getUserData(FILE_IN_BLOCK_MODIFICATION_COUNT) ?: 0
|
||||
putUserData(FILE_IN_BLOCK_MODIFICATION_COUNT, count + 1)
|
||||
}
|
||||
|
||||
fun KtFile.clearInBlockModifications() {
|
||||
val collection = getUserData(IN_BLOCK_MODIFICATIONS)
|
||||
collection?.let {
|
||||
synchronized(it) {
|
||||
it.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -95,7 +95,7 @@ class KotlinModuleOutOfCodeBlockModificationTracker private constructor(private
|
||||
}
|
||||
}
|
||||
|
||||
internal fun onPsiModificationTrackerUpdate(customIncrement: Int) {
|
||||
internal fun onPsiModificationTrackerUpdate(customIncrement: Int = 0) {
|
||||
val newModCount = kotlinOfOfCodeBlockTracker.modificationCount
|
||||
val affectedModule = lastAffectedModule
|
||||
if (affectedModule != null && newModCount == lastAffectedModuleModCount + customIncrement) {
|
||||
|
||||
@@ -50,4 +50,7 @@ inline fun invokeLater(crossinline action: () -> Unit) {
|
||||
ApplicationManager.getApplication().invokeLater { action() }
|
||||
}
|
||||
|
||||
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
|
||||
fun <T> Project.getServiceSafe(serviceClass: Class<T>): T =
|
||||
this.getService(serviceClass) ?: error("Unable to locate service ${serviceClass.name}")
|
||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.util.application
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressIndicatorProvider
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
@@ -52,4 +53,7 @@ inline fun invokeLater(crossinline action: () -> Unit) {
|
||||
ApplicationManager.getApplication().invokeLater { action() }
|
||||
}
|
||||
|
||||
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
|
||||
fun <T> Project.getServiceSafe(serviceClass: Class<T>): T =
|
||||
ServiceManager.getService(this, serviceClass) ?: error("Unable to locate service ${serviceClass.name}")
|
||||
|
||||
@@ -50,4 +50,7 @@ inline fun invokeLater(crossinline action: () -> Unit) {
|
||||
ApplicationManager.getApplication().invokeLater { action() }
|
||||
}
|
||||
|
||||
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
|
||||
fun <T> Project.getServiceSafe(serviceClass: Class<T>) =
|
||||
this.getService(serviceClass) ?: error("Unable to locate service ${serviceClass.name}")
|
||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.util.application
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressIndicatorProvider
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
@@ -52,4 +53,10 @@ inline fun invokeLater(crossinline action: () -> Unit) {
|
||||
ApplicationManager.getApplication().invokeLater { action() }
|
||||
}
|
||||
|
||||
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
inline fun isUnitTestMode(): Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
|
||||
fun <T> Project.getServiceSafe(serviceClass: Class<T>): T =
|
||||
ServiceManager.getService(this, serviceClass) ?: error("Unable to locate service ${serviceClass.name}")
|
||||
|
||||
fun <T> Project.getServiceIfCreated(serviceClass: Class<T>): T? =
|
||||
ServiceManager.getServiceIfCreated(this, serviceClass)
|
||||
+2
-2
@@ -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?
|
||||
|
||||
+2
-1
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
@@ -46,7 +47,7 @@ class CompletionBindingContextProvider(project: Project) {
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project): CompletionBindingContextProvider =
|
||||
project.getComponent(CompletionBindingContextProvider::class.java)
|
||||
project.getServiceSafe(CompletionBindingContextProvider::class.java)
|
||||
|
||||
var ENABLED = true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.completion
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupEvent
|
||||
import com.intellij.codeInsight.lookup.LookupListener
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.RangeMarker
|
||||
import com.intellij.openapi.editor.event.CaretEvent
|
||||
import com.intellij.openapi.editor.event.CaretListener
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||
|
||||
class LookupCancelService {
|
||||
internal class Reminiscence(editor: Editor, offset: Int) {
|
||||
var editor: Editor? = editor
|
||||
private var marker: RangeMarker? = editor.document.createRangeMarker(offset, offset)
|
||||
|
||||
// forget about auto-popup cancellation when the caret is moved to the start or before it
|
||||
private var editorListener: CaretListener? = object : CaretListener {
|
||||
override fun caretPositionChanged(e: CaretEvent) {
|
||||
if (!marker!!.isValid || editor.logicalPositionToOffset(e.newPosition) <= offset) {
|
||||
dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
ApplicationManager.getApplication()!!.assertIsDispatchThread()
|
||||
editor.caretModel.addCaretListener(editorListener!!)
|
||||
}
|
||||
|
||||
fun matches(editor: Editor, offset: Int): Boolean {
|
||||
return editor == this.editor && marker?.startOffset == offset
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
ApplicationManager.getApplication()!!.assertIsDispatchThread()
|
||||
if (marker != null) {
|
||||
editor!!.caretModel.removeCaretListener(editorListener!!)
|
||||
marker = null
|
||||
editor = null
|
||||
editorListener = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val lookupCancelListener = object : LookupListener {
|
||||
override fun lookupCanceled(event: LookupEvent) {
|
||||
val lookup = event.lookup
|
||||
if (event.isCanceledExplicitly && lookup.isCompletion) {
|
||||
val offset = lookup.currentItem?.getUserData(LookupCancelService.AUTO_POPUP_AT)
|
||||
if (offset != null) {
|
||||
lastReminiscence?.dispose()
|
||||
if (offset <= lookup.editor.document.textLength) {
|
||||
lastReminiscence = Reminiscence(lookup.editor, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun disposeLastReminiscence(editor: Editor) {
|
||||
if (lastReminiscence?.editor == editor) {
|
||||
lastReminiscence!!.dispose()
|
||||
lastReminiscence = null
|
||||
}
|
||||
}
|
||||
|
||||
private var lastReminiscence: Reminiscence? = null
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project): LookupCancelService = project.getServiceSafe(LookupCancelService::class.java)
|
||||
|
||||
fun getServiceIfCreated(project: Project): LookupCancelService? = project.getServiceIfCreated(LookupCancelService::class.java)
|
||||
|
||||
val AUTO_POPUP_AT = Key<Int>("LookupCancelService.AUTO_POPUP_AT")
|
||||
}
|
||||
|
||||
fun wasAutoPopupRecentlyCancelled(editor: Editor, offset: Int): Boolean {
|
||||
return lastReminiscence?.matches(editor, offset) ?: false
|
||||
}
|
||||
|
||||
}
|
||||
+19
@@ -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
|
||||
+5
-72
@@ -22,83 +22,18 @@ import com.intellij.codeInsight.lookup.Lookup
|
||||
import com.intellij.codeInsight.lookup.LookupEvent
|
||||
import com.intellij.codeInsight.lookup.LookupListener
|
||||
import com.intellij.codeInsight.lookup.LookupManager
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ProjectComponent
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.editor.RangeMarker
|
||||
import com.intellij.openapi.editor.event.CaretEvent
|
||||
import com.intellij.openapi.editor.event.CaretListener
|
||||
import com.intellij.openapi.editor.event.EditorFactoryEvent
|
||||
import com.intellij.openapi.editor.event.EditorFactoryListener
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.startup.StartupActivity
|
||||
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector
|
||||
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector.completionStatsData
|
||||
import org.jetbrains.kotlin.idea.statistics.FinishReasonStats
|
||||
|
||||
class LookupCancelWatcher(val project: Project) : ProjectComponent {
|
||||
private class Reminiscence(editor: Editor, offset: Int) {
|
||||
var editor: Editor? = editor
|
||||
private var marker: RangeMarker? = editor.document.createRangeMarker(offset, offset)
|
||||
class LookupCancelWatcher : StartupActivity {
|
||||
|
||||
// forget about auto-popup cancellation when the caret is moved to the start or before it
|
||||
private var editorListener: CaretListener? = object : CaretListener {
|
||||
override fun caretPositionChanged(e: CaretEvent) {
|
||||
if (!marker!!.isValid || editor.logicalPositionToOffset(e.newPosition) <= offset) {
|
||||
dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
ApplicationManager.getApplication()!!.assertIsDispatchThread()
|
||||
editor.caretModel.addCaretListener(editorListener!!)
|
||||
}
|
||||
|
||||
fun matches(editor: Editor, offset: Int): Boolean {
|
||||
return editor == this.editor && marker?.startOffset == offset
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
ApplicationManager.getApplication()!!.assertIsDispatchThread()
|
||||
if (marker != null) {
|
||||
editor!!.caretModel.removeCaretListener(editorListener!!)
|
||||
marker = null
|
||||
editor = null
|
||||
editorListener = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var lastReminiscence: Reminiscence? = null
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project): LookupCancelWatcher = project.getComponent(LookupCancelWatcher::class.java)!!
|
||||
|
||||
val AUTO_POPUP_AT = Key<Int>("LookupCancelWatcher.AUTO_POPUP_AT")
|
||||
}
|
||||
|
||||
fun wasAutoPopupRecentlyCancelled(editor: Editor, offset: Int): Boolean {
|
||||
return lastReminiscence?.matches(editor, offset) ?: false
|
||||
}
|
||||
|
||||
private val lookupCancelListener = object : LookupListener {
|
||||
override fun lookupCanceled(event: LookupEvent) {
|
||||
val lookup = event.lookup
|
||||
if (event.isCanceledExplicitly && lookup.isCompletion) {
|
||||
val offset = lookup.currentItem?.getUserData(AUTO_POPUP_AT)
|
||||
if (offset != null) {
|
||||
lastReminiscence?.dispose()
|
||||
if (offset <= lookup.editor.document.textLength) {
|
||||
lastReminiscence = Reminiscence(lookup.editor, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun initComponent() {
|
||||
override fun runActivity(project: Project) {
|
||||
CompletionPhaseListener.TOPIC.subscribe(project, CompletionPhaseListener { isCompletionRunning ->
|
||||
if (isCompletionRunning) {
|
||||
if (completionStatsData != null) {
|
||||
@@ -117,9 +52,7 @@ class LookupCancelWatcher(val project: Project) : ProjectComponent {
|
||||
EditorFactory.getInstance().addEditorFactoryListener(
|
||||
object : EditorFactoryListener {
|
||||
override fun editorReleased(event: EditorFactoryEvent) {
|
||||
if (lastReminiscence?.editor == event.editor) {
|
||||
lastReminiscence!!.dispose()
|
||||
}
|
||||
LookupCancelService.getServiceIfCreated(project)?.disposeLastReminiscence(event.editor)
|
||||
}
|
||||
},
|
||||
project
|
||||
@@ -128,7 +61,7 @@ class LookupCancelWatcher(val project: Project) : ProjectComponent {
|
||||
LookupManager.getInstance(project).addPropertyChangeListener { event ->
|
||||
if (event.propertyName == LookupManager.PROP_ACTIVE_LOOKUP) {
|
||||
val lookup = event.newValue as Lookup?
|
||||
lookup?.addLookupListener(lookupCancelListener)
|
||||
lookup?.addLookupListener(LookupCancelService.getInstance(project).lookupCancelListener)
|
||||
lookup?.addLookupListener(object : LookupListener {
|
||||
override fun lookupShown(event: LookupEvent) {
|
||||
completionStatsData = completionStatsData?.copy(shownTime = System.currentTimeMillis())
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.completion
|
||||
|
||||
import com.intellij.application.subscribe
|
||||
import com.intellij.codeInsight.completion.CompletionPhaseListener
|
||||
import com.intellij.codeInsight.lookup.Lookup
|
||||
import com.intellij.codeInsight.lookup.LookupEvent
|
||||
import com.intellij.codeInsight.lookup.LookupListener
|
||||
import com.intellij.codeInsight.lookup.LookupManager
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ProjectComponent
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.editor.RangeMarker
|
||||
import com.intellij.openapi.editor.event.CaretEvent
|
||||
import com.intellij.openapi.editor.event.CaretListener
|
||||
import com.intellij.openapi.editor.event.EditorFactoryEvent
|
||||
import com.intellij.openapi.editor.event.EditorFactoryListener
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector
|
||||
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector.completionStatsData
|
||||
import org.jetbrains.kotlin.idea.statistics.FinishReasonStats
|
||||
|
||||
class LookupCancelWatcher(val project: Project) : ProjectComponent {
|
||||
private class Reminiscence(editor: Editor, offset: Int) {
|
||||
var editor: Editor? = editor
|
||||
private var marker: RangeMarker? = editor.document.createRangeMarker(offset, offset)
|
||||
|
||||
// forget about auto-popup cancellation when the caret is moved to the start or before it
|
||||
private var editorListener: CaretListener? = object : CaretListener {
|
||||
override fun caretPositionChanged(e: CaretEvent) {
|
||||
if (!marker!!.isValid || editor.logicalPositionToOffset(e.newPosition) <= offset) {
|
||||
dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
ApplicationManager.getApplication()!!.assertIsDispatchThread()
|
||||
editor.caretModel.addCaretListener(editorListener!!)
|
||||
}
|
||||
|
||||
fun matches(editor: Editor, offset: Int): Boolean {
|
||||
return editor == this.editor && marker?.startOffset == offset
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
ApplicationManager.getApplication()!!.assertIsDispatchThread()
|
||||
if (marker != null) {
|
||||
editor!!.caretModel.removeCaretListener(editorListener!!)
|
||||
marker = null
|
||||
editor = null
|
||||
editorListener = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var lastReminiscence: Reminiscence? = null
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project): LookupCancelWatcher = project.getComponent(LookupCancelWatcher::class.java)!!
|
||||
|
||||
val AUTO_POPUP_AT = Key<Int>("LookupCancelWatcher.AUTO_POPUP_AT")
|
||||
}
|
||||
|
||||
fun wasAutoPopupRecentlyCancelled(editor: Editor, offset: Int): Boolean {
|
||||
return lastReminiscence?.matches(editor, offset) ?: false
|
||||
}
|
||||
|
||||
private val lookupCancelListener = object : LookupListener {
|
||||
override fun lookupCanceled(event: LookupEvent) {
|
||||
val lookup = event.lookup
|
||||
if (event.isCanceledExplicitly && lookup.isCompletion) {
|
||||
val offset = lookup.currentItem?.getUserData(AUTO_POPUP_AT)
|
||||
if (offset != null) {
|
||||
lastReminiscence?.dispose()
|
||||
if (offset <= lookup.editor.document.textLength) {
|
||||
lastReminiscence = Reminiscence(lookup.editor, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun initComponent() {
|
||||
CompletionPhaseListener.TOPIC.subscribe(project, CompletionPhaseListener { isCompletionRunning ->
|
||||
if (isCompletionRunning) {
|
||||
if (completionStatsData != null) {
|
||||
completionStatsData = completionStatsData?.copy(finishReason = FinishReasonStats.INTERRUPTED)
|
||||
CompletionFUSCollector.log(completionStatsData)
|
||||
completionStatsData = null
|
||||
}
|
||||
completionStatsData = CompletionFUSCollector.CompletionStatsData(System.currentTimeMillis())
|
||||
}
|
||||
|
||||
if (!isCompletionRunning) {
|
||||
completionStatsData = completionStatsData?.copy(finishTime = System.currentTimeMillis())
|
||||
}
|
||||
})
|
||||
|
||||
EditorFactory.getInstance().addEditorFactoryListener(
|
||||
object : EditorFactoryListener {
|
||||
override fun editorReleased(event: EditorFactoryEvent) {
|
||||
if (lastReminiscence?.editor == event.editor) {
|
||||
lastReminiscence!!.dispose()
|
||||
}
|
||||
}
|
||||
},
|
||||
project
|
||||
)
|
||||
|
||||
LookupManager.getInstance(project).addPropertyChangeListener { event ->
|
||||
if (event.propertyName == LookupManager.PROP_ACTIVE_LOOKUP) {
|
||||
val lookup = event.newValue as Lookup?
|
||||
lookup?.addLookupListener(lookupCancelListener)
|
||||
lookup?.addLookupListener(object : LookupListener {
|
||||
override fun lookupShown(event: LookupEvent) {
|
||||
completionStatsData = completionStatsData?.copy(shownTime = System.currentTimeMillis())
|
||||
}
|
||||
|
||||
override fun lookupCanceled(event: LookupEvent) {
|
||||
completionStatsData = completionStatsData?.copy(
|
||||
finishReason = if (event.isCanceledExplicitly) FinishReasonStats.CANCELLED else FinishReasonStats.HIDDEN
|
||||
)
|
||||
CompletionFUSCollector.log(completionStatsData)
|
||||
completionStatsData = null
|
||||
}
|
||||
|
||||
override fun itemSelected(event: LookupEvent) {
|
||||
val eventLookup = event.lookup
|
||||
val lookupIndex = eventLookup.items.indexOf(eventLookup.currentItem)
|
||||
if (lookupIndex >= 0) completionStatsData = completionStatsData?.copy(selectedItem = lookupIndex)
|
||||
|
||||
completionStatsData = completionStatsData?.copy(finishReason = FinishReasonStats.DONE)
|
||||
CompletionFUSCollector.log(completionStatsData)
|
||||
completionStatsData = null
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+3
-3
@@ -13,8 +13,8 @@ import com.intellij.testFramework.runInEdtAndWait
|
||||
import com.intellij.util.concurrency.FutureResult
|
||||
import org.jetbrains.kotlin.config.ApiVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersion
|
||||
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent
|
||||
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent.MigrationTestState
|
||||
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService
|
||||
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService.MigrationTestState
|
||||
import org.jetbrains.kotlin.idea.configuration.MigrationInfo
|
||||
import org.jetbrains.plugins.gradle.tooling.annotation.TargetVersions
|
||||
import org.junit.Assert
|
||||
@@ -98,7 +98,7 @@ class GradleMigrateTest : GradleImportingTestCase() {
|
||||
}
|
||||
|
||||
val importResult = FutureResult<MigrationTestState?>()
|
||||
val migrationProjectComponent = KotlinMigrationProjectComponent.getInstanceIfNotDisposed(myProject)
|
||||
val migrationProjectComponent = KotlinMigrationProjectService.getInstanceIfNotDisposed(myProject)
|
||||
?: error("Disposed project")
|
||||
|
||||
migrationProjectComponent.setImportFinishListener { migrationState ->
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.compiler.configuration
|
||||
|
||||
import com.intellij.compiler.server.BuildProcessParametersProvider
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import org.jetbrains.kotlin.config.IncrementalCompilation
|
||||
import org.jetbrains.kotlin.idea.PluginStartupComponent
|
||||
|
||||
class KotlinBuildProcessParametersProvider(private val project: Project) : BuildProcessParametersProvider() {
|
||||
override fun getVMArguments(): MutableList<String> {
|
||||
val compilerWorkspaceSettings = KotlinCompilerWorkspaceSettings.getInstance(project)
|
||||
|
||||
val res = arrayListOf<String>()
|
||||
if (compilerWorkspaceSettings.preciseIncrementalEnabled) {
|
||||
res.add("-D" + IncrementalCompilation.INCREMENTAL_COMPILATION_JVM_PROPERTY + "=true")
|
||||
}
|
||||
if (compilerWorkspaceSettings.incrementalCompilationForJsEnabled) {
|
||||
res.add("-D" + IncrementalCompilation.INCREMENTAL_COMPILATION_JS_PROPERTY + "=true")
|
||||
}
|
||||
if (compilerWorkspaceSettings.enableDaemon) {
|
||||
res.add("-Dkotlin.daemon.enabled")
|
||||
}
|
||||
if (Registry.`is`("kotlin.jps.instrument.bytecode", false)) {
|
||||
res.add("-Dkotlin.jps.instrument.bytecode=true")
|
||||
}
|
||||
PluginStartupComponent.getInstance().aliveFlagPath.let {
|
||||
if (!it.isBlank()) {
|
||||
// TODO: consider taking the property name from compiler/daemon/common (check whether dependency will be not too heavy)
|
||||
res.add("-Dkotlin.daemon.client.alive.path=\"$it\"")
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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)
|
||||
})
|
||||
|
||||
+20
-6
@@ -18,6 +18,8 @@ package org.jetbrains.kotlin.idea.configuration.ui
|
||||
|
||||
import com.intellij.notification.NotificationDisplayType
|
||||
import com.intellij.notification.NotificationsConfiguration
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.ReadAction.nonBlocking
|
||||
import com.intellij.openapi.application.runReadAction
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener
|
||||
@@ -28,6 +30,9 @@ import org.jetbrains.kotlin.idea.configuration.getModulesWithKotlinFiles
|
||||
import org.jetbrains.kotlin.idea.configuration.notifyOutdatedBundledCompilerIfNecessary
|
||||
import org.jetbrains.kotlin.idea.configuration.ui.notifications.notifyKotlinStyleUpdateIfNeeded
|
||||
import org.jetbrains.kotlin.idea.project.getAndCacheLanguageLevelByDependencies
|
||||
import org.jetbrains.kotlin.idea.util.ProgressIndicatorUtils
|
||||
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class KotlinConfigurationCheckerStartupActivity : StartupActivity {
|
||||
@@ -53,15 +58,24 @@ class KotlinConfigurationCheckerService(val project: Project) {
|
||||
private val syncDepth = AtomicInteger()
|
||||
|
||||
fun performProjectPostOpenActions() {
|
||||
nonBlocking {
|
||||
val modulesWithKotlinFiles = getModulesWithKotlinFiles(project)
|
||||
for (module in modulesWithKotlinFiles) {
|
||||
module.getAndCacheLanguageLevelByDependencies()
|
||||
}
|
||||
}
|
||||
nonBlocking(Callable {
|
||||
return@Callable getModulesWithKotlinFiles(project)
|
||||
})
|
||||
.inSmartMode(project)
|
||||
.expireWith(project)
|
||||
.coalesceBy(this)
|
||||
.finishOnUiThread(ModalityState.any()) { modulesWithKotlinFiles ->
|
||||
val promise = ApplicationManager.getApplication().executeOnPooledThread {
|
||||
for (module in modulesWithKotlinFiles) {
|
||||
runReadAction {
|
||||
module.getAndCacheLanguageLevelByDependencies()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isUnitTestMode()) {
|
||||
ProgressIndicatorUtils.awaitWithCheckCanceled(promise)
|
||||
}
|
||||
}
|
||||
.submit(AppExecutorUtil.getAppExecutorService())
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.idea.maven.project.MavenImportListener
|
||||
import org.jetbrains.idea.maven.project.MavenProject
|
||||
import org.jetbrains.idea.maven.project.MavenProjectsManager
|
||||
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent
|
||||
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService
|
||||
import org.jetbrains.kotlin.idea.configuration.notifyOutdatedBundledCompilerIfNecessary
|
||||
|
||||
class MavenImportListener(val project: Project) : MavenProjectsManager.Listener {
|
||||
@@ -19,7 +19,7 @@ class MavenImportListener(val project: Project) : MavenProjectsManager.Listener
|
||||
MavenImportListener.TOPIC,
|
||||
MavenImportListener { _: Collection<MavenProject>, _: List<Module> ->
|
||||
notifyOutdatedBundledCompilerIfNecessary(project)
|
||||
KotlinMigrationProjectComponent.getInstanceIfNotDisposed(project)?.onImportFinished()
|
||||
KotlinMigrationProjectService.getInstanceIfNotDisposed(project)?.onImportFinished()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -27,6 +27,6 @@ class MavenImportListener(val project: Project) : MavenProjectsManager.Listener
|
||||
}
|
||||
|
||||
override fun projectsScheduled() {
|
||||
KotlinMigrationProjectComponent.getInstanceIfNotDisposed(project)?.onImportAboutToStart()
|
||||
KotlinMigrationProjectService.getInstanceIfNotDisposed(project)?.onImportAboutToStart()
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,9 @@ import com.intellij.testFramework.runInEdtAndWait
|
||||
import com.intellij.util.concurrency.FutureResult
|
||||
import org.jetbrains.kotlin.config.ApiVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersion
|
||||
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent
|
||||
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService
|
||||
import org.jetbrains.kotlin.idea.configuration.MigrationInfo
|
||||
import org.jetbrains.kotlin.idea.configuration.MigrationTestState
|
||||
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
|
||||
import org.junit.Assert
|
||||
import org.junit.runner.RunWith
|
||||
@@ -106,8 +107,8 @@ class MavenMigrateTest : MavenImportingTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
val importResult = FutureResult<KotlinMigrationProjectComponent.MigrationTestState?>()
|
||||
val migrationProjectComponent = KotlinMigrationProjectComponent.getInstanceIfNotDisposed(myProject)
|
||||
val importResult = FutureResult<MigrationTestState?>()
|
||||
val migrationProjectComponent = KotlinMigrationProjectService.getInstanceIfNotDisposed(myProject)
|
||||
?: error("Disposed project")
|
||||
|
||||
migrationProjectComponent.setImportFinishListener { migrationState ->
|
||||
|
||||
@@ -1,26 +1,4 @@
|
||||
<idea-plugin>
|
||||
<project-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<interface-class>org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider</interface-class>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsModel</implementation-class>
|
||||
</component>
|
||||
</project-components>
|
||||
|
||||
<application-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.versions.KotlinUpdatePluginComponent</implementation-class>
|
||||
@@ -216,14 +194,6 @@
|
||||
</actions>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService" />
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
|
||||
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
|
||||
|
||||
<applicationService serviceInterface="org.jetbrains.kotlin.idea.KotlinIconProviderService"
|
||||
serviceImplementation="org.jetbrains.kotlin.idea.KotlinIdeFileIconProviderService"/>
|
||||
|
||||
@@ -319,6 +289,8 @@
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettings"
|
||||
serviceImplementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettings"/>
|
||||
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsModel"/>
|
||||
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerWorkspaceSettings"/>
|
||||
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.asJava.classes.FacadeCache"
|
||||
@@ -336,6 +308,9 @@
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker"
|
||||
serviceImplementation="org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker"/>
|
||||
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider"
|
||||
serviceImplementation="org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider"/>
|
||||
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade"
|
||||
serviceImplementation="org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade"/>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -57,6 +57,12 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
|
||||
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
||||
<project-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
|
||||
</component>
|
||||
</project-components>
|
||||
|
||||
<extensionPoints>
|
||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
|
||||
|
||||
@@ -71,6 +77,20 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService" />
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.completion.LookupCancelWatcher"/>
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener"/>
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent"/>
|
||||
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.completion.LookupCancelService"/>
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService"/>
|
||||
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
|
||||
|
||||
<statistics.counterUsagesCollector groupId="kotlin.gradle.target" version="2"/>
|
||||
<statistics.counterUsagesCollector groupId="kotlin.maven.target" version="3"/>
|
||||
<statistics.counterUsagesCollector groupId="kotlin.jps.target" version="3"/>
|
||||
|
||||
@@ -56,6 +56,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
|
||||
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
||||
<project-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory</implementation-class>
|
||||
<skipForDefaultProject/>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
|
||||
</component>
|
||||
</project-components>
|
||||
|
||||
<application-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.PluginStartupComponent</implementation-class>
|
||||
</component>
|
||||
</application-components>
|
||||
|
||||
<extensionPoints>
|
||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
|
||||
|
||||
|
||||
@@ -56,6 +56,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
|
||||
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
||||
<project-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory</implementation-class>
|
||||
<skipForDefaultProject/>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
|
||||
</component>
|
||||
</project-components>
|
||||
|
||||
<application-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.PluginStartupComponent</implementation-class>
|
||||
</component>
|
||||
</application-components>
|
||||
|
||||
<extensionPoints>
|
||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
|
||||
|
||||
|
||||
@@ -71,6 +71,22 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService" />
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.completion.LookupCancelWatcher"/>
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener"/>
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent"/>
|
||||
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.completion.LookupCancelService"/>
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService"/>
|
||||
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
|
||||
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener"/>
|
||||
|
||||
<statistics.counterUsagesCollector groupId="kotlin.gradle.target" version="2"/>
|
||||
<statistics.counterUsagesCollector groupId="kotlin.maven.target" version="3"/>
|
||||
<statistics.counterUsagesCollector groupId="kotlin.jps.target" version="3"/>
|
||||
|
||||
@@ -57,6 +57,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
|
||||
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
||||
<project-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory</implementation-class>
|
||||
<skipForDefaultProject/>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
|
||||
</component>
|
||||
</project-components>
|
||||
|
||||
<application-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.PluginStartupComponent</implementation-class>
|
||||
</component>
|
||||
</application-components>
|
||||
|
||||
<extensionPoints>
|
||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
|
||||
|
||||
|
||||
@@ -57,6 +57,40 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
|
||||
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
||||
<project-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.completion.LookupCancelWatcher</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Companion$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory</implementation-class>
|
||||
<skipForDefaultProject/>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Factory</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
|
||||
</component>
|
||||
</project-components>
|
||||
|
||||
<application-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.PluginStartupComponent</implementation-class>
|
||||
</component>
|
||||
</application-components>
|
||||
|
||||
<extensionPoints>
|
||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
|
||||
|
||||
|
||||
@@ -56,6 +56,12 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
|
||||
<xi:include href="scripting-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
||||
<project-components>
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener</implementation-class>
|
||||
</component>
|
||||
</project-components>
|
||||
|
||||
<extensionPoints>
|
||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/extensionPoints/*)"/>
|
||||
|
||||
@@ -65,16 +71,30 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
|
||||
<xi:include href="plugin-kotlin-extensions.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
||||
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
||||
<pluginUpdateVerifier implementation="org.jetbrains.kotlin.idea.update.GooglePluginUpdateVerifier"/>
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij.jvm">
|
||||
<declarationSearcher language="kotlin" implementationClass="org.jetbrains.kotlin.idea.jvm.KotlinDeclarationSearcher"/>
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.PluginStartupService" />
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.PluginStartupActivity"/>
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.completion.LookupCancelWatcher"/>
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener"/>
|
||||
<postStartupActivity implementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent"/>
|
||||
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.completion.LookupCancelService"/>
|
||||
<projectService serviceImplementation="org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService"/>
|
||||
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.KotlinBeforeResolveHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.highlighter.ScriptExternalHighlightingPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.parameterInfo.custom.KotlinCodeHintsPass$Registrar"/>
|
||||
<highlightingPassFactory implementation="org.jetbrains.kotlin.idea.refactoring.cutPaste.MoveDeclarationsPassFactory$Registrar"/>
|
||||
|
||||
<fileTypeUsageSchemaDescriptor schema="Gradle Script" implementationClass="org.jetbrains.kotlin.idea.core.script.KotlinGradleScriptFileTypeSchemaDetector"/>
|
||||
<completion.ml.model implementation="org.jetbrains.kotlin.idea.completion.ml.KotlinMLRankingProvider"/>
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
||||
<pluginUpdateVerifier implementation="org.jetbrains.kotlin.idea.update.GooglePluginUpdateVerifier"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
+5
-298
@@ -5,310 +5,17 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.configuration
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.roots.libraries.Library
|
||||
import com.intellij.openapi.vcs.changes.ChangeListManager
|
||||
import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx
|
||||
import com.intellij.util.CommonProcessors
|
||||
import com.intellij.util.text.VersionComparatorUtil
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.config.ApiVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
|
||||
import org.jetbrains.kotlin.idea.configuration.ui.showMigrationNotification
|
||||
import org.jetbrains.kotlin.idea.facet.KotlinFacet
|
||||
import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID
|
||||
import org.jetbrains.kotlin.idea.framework.MAVEN_SYSTEM_ID
|
||||
import org.jetbrains.kotlin.idea.migration.CodeMigrationToggleAction
|
||||
import org.jetbrains.kotlin.idea.migration.applicableMigrationTools
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.util.runReadActionInSmartMode
|
||||
import org.jetbrains.kotlin.idea.versions.LibInfo
|
||||
import java.io.File
|
||||
import com.intellij.openapi.startup.StartupActivity
|
||||
|
||||
class KotlinMigrationProjectComponent(val project: Project) {
|
||||
@Volatile
|
||||
private var old: MigrationState? = null
|
||||
class KotlinMigrationProjectComponent : StartupActivity {
|
||||
|
||||
@Volatile
|
||||
private var importFinishListener: ((MigrationTestState?) -> Unit)? = null
|
||||
|
||||
init {
|
||||
val connection = project.messageBus.connect()
|
||||
override fun runActivity(project: Project) {
|
||||
val connection = project.messageBus.connect(project)
|
||||
connection.subscribe(ProjectDataImportListener.TOPIC, ProjectDataImportListener {
|
||||
getInstanceIfNotDisposed(project)?.onImportFinished()
|
||||
KotlinMigrationProjectService.getInstanceIfNotDisposed(project)?.onImportFinished()
|
||||
})
|
||||
}
|
||||
|
||||
class MigrationTestState(val migrationInfo: MigrationInfo?, val hasApplicableTools: Boolean)
|
||||
|
||||
@TestOnly
|
||||
fun setImportFinishListener(newListener: ((MigrationTestState?) -> Unit)?) {
|
||||
synchronized(this) {
|
||||
if (newListener != null && importFinishListener != null) {
|
||||
importFinishListener!!.invoke(null)
|
||||
}
|
||||
|
||||
importFinishListener = newListener
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyFinish(migrationInfo: MigrationInfo?, hasApplicableTools: Boolean) {
|
||||
importFinishListener?.invoke(MigrationTestState(migrationInfo, hasApplicableTools))
|
||||
}
|
||||
|
||||
fun onImportAboutToStart() {
|
||||
if (!CodeMigrationToggleAction.isEnabled(project) || !hasChangesInProjectFiles(project)) {
|
||||
old = null
|
||||
return
|
||||
}
|
||||
|
||||
old = MigrationState.build(project)
|
||||
}
|
||||
|
||||
fun onImportFinished() {
|
||||
if (!CodeMigrationToggleAction.isEnabled(project) || old == null) {
|
||||
notifyFinish(null, false)
|
||||
return
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
var migrationInfo: MigrationInfo? = null
|
||||
var hasApplicableTools = false
|
||||
|
||||
try {
|
||||
val new = project.runReadActionInSmartMode {
|
||||
MigrationState.build(project)
|
||||
}
|
||||
|
||||
val localOld = old.also {
|
||||
old = null
|
||||
} ?: return@executeOnPooledThread
|
||||
|
||||
migrationInfo = prepareMigrationInfo(localOld, new) ?: return@executeOnPooledThread
|
||||
|
||||
if (applicableMigrationTools(migrationInfo).isEmpty()) {
|
||||
hasApplicableTools = false
|
||||
return@executeOnPooledThread
|
||||
} else {
|
||||
hasApplicableTools = true
|
||||
}
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
return@executeOnPooledThread
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
showMigrationNotification(project, migrationInfo)
|
||||
}
|
||||
} finally {
|
||||
notifyFinish(migrationInfo, hasApplicableTools)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstanceIfNotDisposed(project: Project): KotlinMigrationProjectComponent? {
|
||||
return runReadAction {
|
||||
if (!project.isDisposed) {
|
||||
project.getComponent(KotlinMigrationProjectComponent::class.java)
|
||||
?: error("Can't find ${KotlinMigrationProjectComponent::class.qualifiedName} component")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareMigrationInfo(old: MigrationState?, new: MigrationState?): MigrationInfo? {
|
||||
if (old == null || new == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val oldLibraryVersion = old.stdlibInfo?.version
|
||||
val newLibraryVersion = new.stdlibInfo?.version
|
||||
|
||||
if (oldLibraryVersion == null || newLibraryVersion == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (VersionComparatorUtil.COMPARATOR.compare(newLibraryVersion, oldLibraryVersion) > 0 ||
|
||||
old.apiVersion < new.apiVersion || old.languageVersion < new.languageVersion
|
||||
) {
|
||||
return MigrationInfo(
|
||||
oldLibraryVersion, newLibraryVersion,
|
||||
old.apiVersion, new.apiVersion,
|
||||
old.languageVersion, new.languageVersion
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun hasChangesInProjectFiles(project: Project): Boolean {
|
||||
if (ProjectLevelVcsManagerEx.getInstance(project).allVcsRoots.isEmpty()) {
|
||||
return true
|
||||
}
|
||||
|
||||
val checkedFiles = HashSet<File>()
|
||||
|
||||
project.basePath?.let { projectBasePath ->
|
||||
checkedFiles.add(File(projectBasePath))
|
||||
}
|
||||
|
||||
val changedFiles = ChangeListManager.getInstance(project).affectedPaths
|
||||
for (changedFile in changedFiles) {
|
||||
when (changedFile.extension) {
|
||||
"gradle" -> return true
|
||||
"properties" -> return true
|
||||
"kts" -> return true
|
||||
"iml" -> return true
|
||||
"xml" -> {
|
||||
if (changedFile.name == "pom.xml") return true
|
||||
val parentDir = changedFile.parentFile
|
||||
if (parentDir.isDirectory && parentDir.name == Project.DIRECTORY_STORE_FOLDER) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
"kt", "java", "groovy" -> {
|
||||
val dirs: Sequence<File> = generateSequence(changedFile) { it.parentFile }
|
||||
.drop(1) // Drop original file
|
||||
.takeWhile { it.isDirectory }
|
||||
|
||||
val isInBuildSrc = dirs
|
||||
.takeWhile { checkedFiles.add(it) }
|
||||
.any { it.name == BUILD_SRC_FOLDER_NAME }
|
||||
|
||||
if (isInBuildSrc) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MigrationState(
|
||||
var stdlibInfo: LibInfo?,
|
||||
var apiVersion: ApiVersion,
|
||||
var languageVersion: LanguageVersion
|
||||
) {
|
||||
companion object {
|
||||
fun build(project: Project): MigrationState {
|
||||
val libraries = maxKotlinLibVersion(project)
|
||||
val languageVersionSettings = collectMaxCompilerSettings(project)
|
||||
return MigrationState(libraries, languageVersionSettings.apiVersion, languageVersionSettings.languageVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MigrationInfo(
|
||||
val oldStdlibVersion: String,
|
||||
val newStdlibVersion: String,
|
||||
val oldApiVersion: ApiVersion,
|
||||
val newApiVersion: ApiVersion,
|
||||
val oldLanguageVersion: LanguageVersion,
|
||||
val newLanguageVersion: LanguageVersion
|
||||
) {
|
||||
companion object {
|
||||
fun create(
|
||||
oldStdlibVersion: String,
|
||||
oldApiVersion: ApiVersion,
|
||||
oldLanguageVersion: LanguageVersion,
|
||||
newStdlibVersion: String = oldStdlibVersion,
|
||||
newApiVersion: ApiVersion = oldApiVersion,
|
||||
newLanguageVersion: LanguageVersion = oldLanguageVersion
|
||||
): MigrationInfo {
|
||||
return MigrationInfo(
|
||||
oldStdlibVersion, newStdlibVersion,
|
||||
oldApiVersion, newApiVersion,
|
||||
oldLanguageVersion, newLanguageVersion
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MigrationInfo.isLanguageVersionUpdate(old: LanguageVersion, new: LanguageVersion): Boolean {
|
||||
return oldLanguageVersion <= old && newLanguageVersion >= new
|
||||
}
|
||||
|
||||
|
||||
private const val BUILD_SRC_FOLDER_NAME = "buildSrc"
|
||||
private const val KOTLIN_GROUP_ID = "org.jetbrains.kotlin"
|
||||
|
||||
private fun maxKotlinLibVersion(project: Project): LibInfo? {
|
||||
return runReadAction {
|
||||
var maxStdlibInfo: LibInfo? = null
|
||||
|
||||
val allLibProcessor = CommonProcessors.CollectUniquesProcessor<Library>()
|
||||
ProjectRootManager.getInstance(project).orderEntries().forEachLibrary(allLibProcessor)
|
||||
|
||||
for (library in allLibProcessor.results) {
|
||||
if (!ExternalSystemApiUtil.isExternalSystemLibrary(library, GRADLE_SYSTEM_ID) &&
|
||||
!ExternalSystemApiUtil.isExternalSystemLibrary(library, MAVEN_SYSTEM_ID)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (library.name?.contains(" $KOTLIN_GROUP_ID:kotlin-stdlib") != true) {
|
||||
continue
|
||||
}
|
||||
|
||||
val libraryInfo = parseExternalLibraryName(library) ?: continue
|
||||
|
||||
if (maxStdlibInfo == null || VersionComparatorUtil.COMPARATOR.compare(libraryInfo.version, maxStdlibInfo.version) > 0) {
|
||||
maxStdlibInfo = LibInfo(KOTLIN_GROUP_ID, libraryInfo.artifactId, libraryInfo.version)
|
||||
}
|
||||
}
|
||||
|
||||
maxStdlibInfo
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectMaxCompilerSettings(project: Project): LanguageVersionSettings {
|
||||
return runReadAction {
|
||||
var maxApiVersion: ApiVersion? = null
|
||||
var maxLanguageVersion: LanguageVersion? = null
|
||||
|
||||
for (module in ModuleManager.getInstance(project).modules) {
|
||||
if (!module.isKotlinModule()) {
|
||||
// Otherwise project compiler settings will give unreliable maximum for compiler settings
|
||||
continue
|
||||
}
|
||||
|
||||
val languageVersionSettings = module.languageVersionSettings
|
||||
|
||||
if (maxApiVersion == null || languageVersionSettings.apiVersion > maxApiVersion) {
|
||||
maxApiVersion = languageVersionSettings.apiVersion
|
||||
}
|
||||
|
||||
if (maxLanguageVersion == null || languageVersionSettings.languageVersion > maxLanguageVersion) {
|
||||
maxLanguageVersion = languageVersionSettings.languageVersion
|
||||
}
|
||||
}
|
||||
|
||||
LanguageVersionSettingsImpl(maxLanguageVersion ?: LanguageVersion.LATEST_STABLE, maxApiVersion ?: ApiVersion.LATEST_STABLE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Module.isKotlinModule(): Boolean {
|
||||
if (isDisposed) return false
|
||||
|
||||
if (KotlinFacet.get(this) != null) {
|
||||
return true
|
||||
}
|
||||
|
||||
// This code works only for Maven and Gradle import, and it's expected that Kotlin facets are configured for
|
||||
// all modules with external system.
|
||||
return false
|
||||
}
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.configuration
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.roots.libraries.Library
|
||||
import com.intellij.openapi.vcs.changes.ChangeListManager
|
||||
import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx
|
||||
import com.intellij.util.CommonProcessors
|
||||
import com.intellij.util.text.VersionComparatorUtil
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.config.ApiVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
|
||||
import org.jetbrains.kotlin.idea.configuration.ui.showMigrationNotification
|
||||
import org.jetbrains.kotlin.idea.facet.KotlinFacet
|
||||
import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID
|
||||
import org.jetbrains.kotlin.idea.framework.MAVEN_SYSTEM_ID
|
||||
import org.jetbrains.kotlin.idea.migration.CodeMigrationToggleAction
|
||||
import org.jetbrains.kotlin.idea.migration.applicableMigrationTools
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.util.runReadActionInSmartMode
|
||||
import org.jetbrains.kotlin.idea.versions.LibInfo
|
||||
import java.io.File
|
||||
|
||||
class KotlinMigrationProjectComponent(val project: Project) {
|
||||
@Volatile
|
||||
private var old: MigrationState? = null
|
||||
|
||||
@Volatile
|
||||
private var importFinishListener: ((MigrationTestState?) -> Unit)? = null
|
||||
|
||||
init {
|
||||
val connection = project.messageBus.connect(project)
|
||||
connection.subscribe(ProjectDataImportListener.TOPIC, ProjectDataImportListener {
|
||||
getInstanceIfNotDisposed(project)?.onImportFinished()
|
||||
})
|
||||
}
|
||||
|
||||
class MigrationTestState(val migrationInfo: MigrationInfo?, val hasApplicableTools: Boolean)
|
||||
|
||||
@TestOnly
|
||||
fun setImportFinishListener(newListener: ((MigrationTestState?) -> Unit)?) {
|
||||
synchronized(this) {
|
||||
if (newListener != null && importFinishListener != null) {
|
||||
importFinishListener!!.invoke(null)
|
||||
}
|
||||
|
||||
importFinishListener = newListener
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyFinish(migrationInfo: MigrationInfo?, hasApplicableTools: Boolean) {
|
||||
importFinishListener?.invoke(MigrationTestState(migrationInfo, hasApplicableTools))
|
||||
}
|
||||
|
||||
fun onImportAboutToStart() {
|
||||
if (!CodeMigrationToggleAction.isEnabled(project) || !hasChangesInProjectFiles(project)) {
|
||||
old = null
|
||||
return
|
||||
}
|
||||
|
||||
old = MigrationState.build(project)
|
||||
}
|
||||
|
||||
fun onImportFinished() {
|
||||
if (!CodeMigrationToggleAction.isEnabled(project) || old == null) {
|
||||
notifyFinish(null, false)
|
||||
return
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
var migrationInfo: MigrationInfo? = null
|
||||
var hasApplicableTools = false
|
||||
|
||||
try {
|
||||
val new = project.runReadActionInSmartMode {
|
||||
MigrationState.build(project)
|
||||
}
|
||||
|
||||
val localOld = old.also {
|
||||
old = null
|
||||
} ?: return@executeOnPooledThread
|
||||
|
||||
migrationInfo = prepareMigrationInfo(localOld, new) ?: return@executeOnPooledThread
|
||||
|
||||
if (applicableMigrationTools(migrationInfo).isEmpty()) {
|
||||
hasApplicableTools = false
|
||||
return@executeOnPooledThread
|
||||
} else {
|
||||
hasApplicableTools = true
|
||||
}
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
return@executeOnPooledThread
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
showMigrationNotification(project, migrationInfo)
|
||||
}
|
||||
} finally {
|
||||
notifyFinish(migrationInfo, hasApplicableTools)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstanceIfNotDisposed(project: Project): KotlinMigrationProjectComponent? {
|
||||
return runReadAction {
|
||||
if (!project.isDisposed) {
|
||||
project.getComponent(KotlinMigrationProjectComponent::class.java)
|
||||
?: error("Can't find ${KotlinMigrationProjectComponent::class.qualifiedName} component")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareMigrationInfo(old: MigrationState?, new: MigrationState?): MigrationInfo? {
|
||||
if (old == null || new == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val oldLibraryVersion = old.stdlibInfo?.version
|
||||
val newLibraryVersion = new.stdlibInfo?.version
|
||||
|
||||
if (oldLibraryVersion == null || newLibraryVersion == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (VersionComparatorUtil.COMPARATOR.compare(newLibraryVersion, oldLibraryVersion) > 0 ||
|
||||
old.apiVersion < new.apiVersion || old.languageVersion < new.languageVersion
|
||||
) {
|
||||
return MigrationInfo(
|
||||
oldLibraryVersion, newLibraryVersion,
|
||||
old.apiVersion, new.apiVersion,
|
||||
old.languageVersion, new.languageVersion
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun hasChangesInProjectFiles(project: Project): Boolean {
|
||||
if (ProjectLevelVcsManagerEx.getInstance(project).allVcsRoots.isEmpty()) {
|
||||
return true
|
||||
}
|
||||
|
||||
val checkedFiles = HashSet<File>()
|
||||
|
||||
project.basePath?.let { projectBasePath ->
|
||||
checkedFiles.add(File(projectBasePath))
|
||||
}
|
||||
|
||||
val changedFiles = ChangeListManager.getInstance(project).affectedPaths
|
||||
for (changedFile in changedFiles) {
|
||||
when (changedFile.extension) {
|
||||
"gradle" -> return true
|
||||
"properties" -> return true
|
||||
"kts" -> return true
|
||||
"iml" -> return true
|
||||
"xml" -> {
|
||||
if (changedFile.name == "pom.xml") return true
|
||||
val parentDir = changedFile.parentFile
|
||||
if (parentDir.isDirectory && parentDir.name == Project.DIRECTORY_STORE_FOLDER) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
"kt", "java", "groovy" -> {
|
||||
val dirs: Sequence<File> = generateSequence(changedFile) { it.parentFile }
|
||||
.drop(1) // Drop original file
|
||||
.takeWhile { it.isDirectory }
|
||||
|
||||
val isInBuildSrc = dirs
|
||||
.takeWhile { checkedFiles.add(it) }
|
||||
.any { it.name == BUILD_SRC_FOLDER_NAME }
|
||||
|
||||
if (isInBuildSrc) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MigrationState(
|
||||
var stdlibInfo: LibInfo?,
|
||||
var apiVersion: ApiVersion,
|
||||
var languageVersion: LanguageVersion
|
||||
) {
|
||||
companion object {
|
||||
fun build(project: Project): MigrationState {
|
||||
val libraries = maxKotlinLibVersion(project)
|
||||
val languageVersionSettings = collectMaxCompilerSettings(project)
|
||||
return MigrationState(libraries, languageVersionSettings.apiVersion, languageVersionSettings.languageVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MigrationInfo(
|
||||
val oldStdlibVersion: String,
|
||||
val newStdlibVersion: String,
|
||||
val oldApiVersion: ApiVersion,
|
||||
val newApiVersion: ApiVersion,
|
||||
val oldLanguageVersion: LanguageVersion,
|
||||
val newLanguageVersion: LanguageVersion
|
||||
) {
|
||||
companion object {
|
||||
fun create(
|
||||
oldStdlibVersion: String,
|
||||
oldApiVersion: ApiVersion,
|
||||
oldLanguageVersion: LanguageVersion,
|
||||
newStdlibVersion: String = oldStdlibVersion,
|
||||
newApiVersion: ApiVersion = oldApiVersion,
|
||||
newLanguageVersion: LanguageVersion = oldLanguageVersion
|
||||
): MigrationInfo {
|
||||
return MigrationInfo(
|
||||
oldStdlibVersion, newStdlibVersion,
|
||||
oldApiVersion, newApiVersion,
|
||||
oldLanguageVersion, newLanguageVersion
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MigrationInfo.isLanguageVersionUpdate(old: LanguageVersion, new: LanguageVersion): Boolean {
|
||||
return oldLanguageVersion <= old && newLanguageVersion >= new
|
||||
}
|
||||
|
||||
|
||||
private const val BUILD_SRC_FOLDER_NAME = "buildSrc"
|
||||
private const val KOTLIN_GROUP_ID = "org.jetbrains.kotlin"
|
||||
|
||||
private fun maxKotlinLibVersion(project: Project): LibInfo? {
|
||||
return runReadAction {
|
||||
var maxStdlibInfo: LibInfo? = null
|
||||
|
||||
val allLibProcessor = CommonProcessors.CollectUniquesProcessor<Library>()
|
||||
ProjectRootManager.getInstance(project).orderEntries().forEachLibrary(allLibProcessor)
|
||||
|
||||
for (library in allLibProcessor.results) {
|
||||
if (!ExternalSystemApiUtil.isExternalSystemLibrary(library, GRADLE_SYSTEM_ID) &&
|
||||
!ExternalSystemApiUtil.isExternalSystemLibrary(library, MAVEN_SYSTEM_ID)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (library.name?.contains(" $KOTLIN_GROUP_ID:kotlin-stdlib") != true) {
|
||||
continue
|
||||
}
|
||||
|
||||
val libraryInfo = parseExternalLibraryName(library) ?: continue
|
||||
|
||||
if (maxStdlibInfo == null || VersionComparatorUtil.COMPARATOR.compare(libraryInfo.version, maxStdlibInfo.version) > 0) {
|
||||
maxStdlibInfo = LibInfo(KOTLIN_GROUP_ID, libraryInfo.artifactId, libraryInfo.version)
|
||||
}
|
||||
}
|
||||
|
||||
maxStdlibInfo
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectMaxCompilerSettings(project: Project): LanguageVersionSettings {
|
||||
return runReadAction {
|
||||
var maxApiVersion: ApiVersion? = null
|
||||
var maxLanguageVersion: LanguageVersion? = null
|
||||
|
||||
for (module in ModuleManager.getInstance(project).modules) {
|
||||
if (!module.isKotlinModule()) {
|
||||
// Otherwise project compiler settings will give unreliable maximum for compiler settings
|
||||
continue
|
||||
}
|
||||
|
||||
val languageVersionSettings = module.languageVersionSettings
|
||||
|
||||
if (maxApiVersion == null || languageVersionSettings.apiVersion > maxApiVersion) {
|
||||
maxApiVersion = languageVersionSettings.apiVersion
|
||||
}
|
||||
|
||||
if (maxLanguageVersion == null || languageVersionSettings.languageVersion > maxLanguageVersion) {
|
||||
maxLanguageVersion = languageVersionSettings.languageVersion
|
||||
}
|
||||
}
|
||||
|
||||
LanguageVersionSettingsImpl(maxLanguageVersion ?: LanguageVersion.LATEST_STABLE, maxApiVersion ?: ApiVersion.LATEST_STABLE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Module.isKotlinModule(): Boolean {
|
||||
if (isDisposed) return false
|
||||
|
||||
if (KotlinFacet.get(this) != null) {
|
||||
return true
|
||||
}
|
||||
|
||||
// This code works only for Maven and Gradle import, and it's expected that Kotlin facets are configured for
|
||||
// all modules with external system.
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.configuration
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.roots.libraries.Library
|
||||
import com.intellij.openapi.vcs.changes.ChangeListManager
|
||||
import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx
|
||||
import com.intellij.util.CommonProcessors
|
||||
import com.intellij.util.text.VersionComparatorUtil
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.config.ApiVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
|
||||
import org.jetbrains.kotlin.idea.configuration.ui.showMigrationNotification
|
||||
import org.jetbrains.kotlin.idea.facet.KotlinFacet
|
||||
import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID
|
||||
import org.jetbrains.kotlin.idea.framework.MAVEN_SYSTEM_ID
|
||||
import org.jetbrains.kotlin.idea.migration.CodeMigrationToggleAction
|
||||
import org.jetbrains.kotlin.idea.migration.applicableMigrationTools
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.util.runReadActionInSmartMode
|
||||
import org.jetbrains.kotlin.idea.versions.LibInfo
|
||||
import java.io.File
|
||||
|
||||
typealias MigrationTestState = KotlinMigrationProjectService.MigrationTestState
|
||||
|
||||
class KotlinMigrationProjectService(val project: Project) {
|
||||
@Volatile
|
||||
private var old: MigrationState? = null
|
||||
|
||||
@Volatile
|
||||
private var importFinishListener: ((MigrationTestState?) -> Unit)? = null
|
||||
|
||||
class MigrationTestState(val migrationInfo: MigrationInfo?, val hasApplicableTools: Boolean)
|
||||
|
||||
@TestOnly
|
||||
fun setImportFinishListener(newListener: ((MigrationTestState?) -> Unit)?) {
|
||||
synchronized(this) {
|
||||
if (newListener != null && importFinishListener != null) {
|
||||
importFinishListener!!.invoke(null)
|
||||
}
|
||||
|
||||
importFinishListener = newListener
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyFinish(migrationInfo: MigrationInfo?, hasApplicableTools: Boolean) {
|
||||
importFinishListener?.invoke(MigrationTestState(migrationInfo, hasApplicableTools))
|
||||
}
|
||||
|
||||
fun onImportAboutToStart() {
|
||||
if (!CodeMigrationToggleAction.isEnabled(project) || !hasChangesInProjectFiles(project)) {
|
||||
old = null
|
||||
return
|
||||
}
|
||||
|
||||
old = MigrationState.build(project)
|
||||
}
|
||||
|
||||
fun onImportFinished() {
|
||||
if (!CodeMigrationToggleAction.isEnabled(project) || old == null) {
|
||||
notifyFinish(null, false)
|
||||
return
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
var migrationInfo: MigrationInfo? = null
|
||||
var hasApplicableTools = false
|
||||
|
||||
try {
|
||||
val new = project.runReadActionInSmartMode {
|
||||
MigrationState.build(project)
|
||||
}
|
||||
|
||||
val localOld = old.also {
|
||||
old = null
|
||||
} ?: return@executeOnPooledThread
|
||||
|
||||
migrationInfo = prepareMigrationInfo(localOld, new) ?: return@executeOnPooledThread
|
||||
|
||||
if (applicableMigrationTools(migrationInfo).isEmpty()) {
|
||||
hasApplicableTools = false
|
||||
return@executeOnPooledThread
|
||||
} else {
|
||||
hasApplicableTools = true
|
||||
}
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
return@executeOnPooledThread
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
showMigrationNotification(project, migrationInfo)
|
||||
}
|
||||
} finally {
|
||||
notifyFinish(migrationInfo, hasApplicableTools)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstanceIfNotDisposed(project: Project): KotlinMigrationProjectService? {
|
||||
return runReadAction {
|
||||
if (!project.isDisposed) {
|
||||
project.getServiceSafe(KotlinMigrationProjectService::class.java)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareMigrationInfo(old: MigrationState?, new: MigrationState?): MigrationInfo? {
|
||||
if (old == null || new == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val oldLibraryVersion = old.stdlibInfo?.version
|
||||
val newLibraryVersion = new.stdlibInfo?.version
|
||||
|
||||
if (oldLibraryVersion == null || newLibraryVersion == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (VersionComparatorUtil.COMPARATOR.compare(newLibraryVersion, oldLibraryVersion) > 0 ||
|
||||
old.apiVersion < new.apiVersion || old.languageVersion < new.languageVersion
|
||||
) {
|
||||
return MigrationInfo(
|
||||
oldLibraryVersion, newLibraryVersion,
|
||||
old.apiVersion, new.apiVersion,
|
||||
old.languageVersion, new.languageVersion
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun hasChangesInProjectFiles(project: Project): Boolean {
|
||||
if (ProjectLevelVcsManagerEx.getInstance(project).allVcsRoots.isEmpty()) {
|
||||
return true
|
||||
}
|
||||
|
||||
val checkedFiles = HashSet<File>()
|
||||
|
||||
project.basePath?.let { projectBasePath ->
|
||||
checkedFiles.add(File(projectBasePath))
|
||||
}
|
||||
|
||||
val changedFiles = ChangeListManager.getInstance(project).affectedPaths
|
||||
for (changedFile in changedFiles) {
|
||||
when (changedFile.extension) {
|
||||
"gradle" -> return true
|
||||
"properties" -> return true
|
||||
"kts" -> return true
|
||||
"iml" -> return true
|
||||
"xml" -> {
|
||||
if (changedFile.name == "pom.xml") return true
|
||||
val parentDir = changedFile.parentFile
|
||||
if (parentDir.isDirectory && parentDir.name == Project.DIRECTORY_STORE_FOLDER) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
"kt", "java", "groovy" -> {
|
||||
val dirs: Sequence<File> = generateSequence(changedFile) { it.parentFile }
|
||||
.drop(1) // Drop original file
|
||||
.takeWhile { it.isDirectory }
|
||||
|
||||
val isInBuildSrc = dirs
|
||||
.takeWhile { checkedFiles.add(it) }
|
||||
.any { it.name == BUILD_SRC_FOLDER_NAME }
|
||||
|
||||
if (isInBuildSrc) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MigrationState(
|
||||
var stdlibInfo: LibInfo?,
|
||||
var apiVersion: ApiVersion,
|
||||
var languageVersion: LanguageVersion
|
||||
) {
|
||||
companion object {
|
||||
fun build(project: Project): MigrationState {
|
||||
val libraries = maxKotlinLibVersion(project)
|
||||
val languageVersionSettings = collectMaxCompilerSettings(project)
|
||||
return MigrationState(libraries, languageVersionSettings.apiVersion, languageVersionSettings.languageVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MigrationInfo(
|
||||
val oldStdlibVersion: String,
|
||||
val newStdlibVersion: String,
|
||||
val oldApiVersion: ApiVersion,
|
||||
val newApiVersion: ApiVersion,
|
||||
val oldLanguageVersion: LanguageVersion,
|
||||
val newLanguageVersion: LanguageVersion
|
||||
) {
|
||||
companion object {
|
||||
fun create(
|
||||
oldStdlibVersion: String,
|
||||
oldApiVersion: ApiVersion,
|
||||
oldLanguageVersion: LanguageVersion,
|
||||
newStdlibVersion: String = oldStdlibVersion,
|
||||
newApiVersion: ApiVersion = oldApiVersion,
|
||||
newLanguageVersion: LanguageVersion = oldLanguageVersion
|
||||
): MigrationInfo {
|
||||
return MigrationInfo(
|
||||
oldStdlibVersion, newStdlibVersion,
|
||||
oldApiVersion, newApiVersion,
|
||||
oldLanguageVersion, newLanguageVersion
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MigrationInfo.isLanguageVersionUpdate(old: LanguageVersion, new: LanguageVersion): Boolean {
|
||||
return oldLanguageVersion <= old && newLanguageVersion >= new
|
||||
}
|
||||
|
||||
|
||||
private const val BUILD_SRC_FOLDER_NAME = "buildSrc"
|
||||
private const val KOTLIN_GROUP_ID = "org.jetbrains.kotlin"
|
||||
|
||||
private fun maxKotlinLibVersion(project: Project): LibInfo? {
|
||||
return runReadAction {
|
||||
var maxStdlibInfo: LibInfo? = null
|
||||
|
||||
val allLibProcessor = CommonProcessors.CollectUniquesProcessor<Library>()
|
||||
ProjectRootManager.getInstance(project).orderEntries().forEachLibrary(allLibProcessor)
|
||||
|
||||
for (library in allLibProcessor.results) {
|
||||
if (!ExternalSystemApiUtil.isExternalSystemLibrary(library, GRADLE_SYSTEM_ID) &&
|
||||
!ExternalSystemApiUtil.isExternalSystemLibrary(library, MAVEN_SYSTEM_ID)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (library.name?.contains(" $KOTLIN_GROUP_ID:kotlin-stdlib") != true) {
|
||||
continue
|
||||
}
|
||||
|
||||
val libraryInfo = parseExternalLibraryName(library) ?: continue
|
||||
|
||||
if (maxStdlibInfo == null || VersionComparatorUtil.COMPARATOR.compare(libraryInfo.version, maxStdlibInfo.version) > 0) {
|
||||
maxStdlibInfo = LibInfo(KOTLIN_GROUP_ID, libraryInfo.artifactId, libraryInfo.version)
|
||||
}
|
||||
}
|
||||
|
||||
maxStdlibInfo
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectMaxCompilerSettings(project: Project): LanguageVersionSettings {
|
||||
return runReadAction {
|
||||
var maxApiVersion: ApiVersion? = null
|
||||
var maxLanguageVersion: LanguageVersion? = null
|
||||
|
||||
for (module in ModuleManager.getInstance(project).modules) {
|
||||
if (!module.isKotlinModule()) {
|
||||
// Otherwise project compiler settings will give unreliable maximum for compiler settings
|
||||
continue
|
||||
}
|
||||
|
||||
val languageVersionSettings = module.languageVersionSettings
|
||||
|
||||
if (maxApiVersion == null || languageVersionSettings.apiVersion > maxApiVersion) {
|
||||
maxApiVersion = languageVersionSettings.apiVersion
|
||||
}
|
||||
|
||||
if (maxLanguageVersion == null || languageVersionSettings.languageVersion > maxLanguageVersion) {
|
||||
maxLanguageVersion = languageVersionSettings.languageVersion
|
||||
}
|
||||
}
|
||||
|
||||
LanguageVersionSettingsImpl(maxLanguageVersion ?: LanguageVersion.LATEST_STABLE, maxApiVersion ?: ApiVersion.LATEST_STABLE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Module.isKotlinModule(): Boolean {
|
||||
if (isDisposed) return false
|
||||
|
||||
if (KotlinFacet.get(this) != null) {
|
||||
return true
|
||||
}
|
||||
|
||||
// This code works only for Maven and Gradle import, and it's expected that Kotlin facets are configured for
|
||||
// all modules with external system.
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.configuration
|
||||
|
||||
// BUNCH: 192
|
||||
typealias KotlinMigrationProjectService = KotlinMigrationProjectComponent
|
||||
typealias MigrationTestState = KotlinMigrationProjectComponent.MigrationTestState
|
||||
@@ -17,14 +17,14 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import org.jetbrains.kotlin.idea.core.util.end
|
||||
import org.jetbrains.kotlin.idea.core.util.range
|
||||
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class KotlinCodeHintsModel(val project: Project) : EditorFactoryListener {
|
||||
companion object {
|
||||
fun getInstance(project: Project): KotlinCodeHintsModel =
|
||||
project.getComponent(KotlinCodeHintsModel::class.java) ?: error("Component `KotlinCodeHintsModel` is expected to be registered")
|
||||
fun getInstance(project: Project): KotlinCodeHintsModel = project.getServiceSafe(KotlinCodeHintsModel::class.java)
|
||||
}
|
||||
|
||||
private class DocumentExtensionInfoModel(val document: Document) {
|
||||
|
||||
+3
-11
@@ -9,7 +9,6 @@ import com.intellij.mock.MockProject
|
||||
import com.intellij.pom.PomModel
|
||||
import com.intellij.pom.core.impl.PomModelImpl
|
||||
import com.intellij.pom.tree.TreeAspect
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import org.jetbrains.kotlin.container.ComponentProvider
|
||||
import org.jetbrains.kotlin.container.get
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
@@ -33,19 +32,12 @@ abstract class AbstractAdditionalResolveDescriptorRendererTest : AbstractDescrip
|
||||
val pomModelImpl = PomModelImpl(project)
|
||||
val treeAspect = TreeAspect(pomModelImpl)
|
||||
|
||||
(project as MockProject).registerService(PomModel::class.java, pomModelImpl)
|
||||
(project.picoContainer as MutablePicoContainer).registerComponentInstance(
|
||||
KotlinCodeBlockModificationListener(
|
||||
PsiModificationTracker.SERVICE.getInstance(project),
|
||||
project,
|
||||
treeAspect
|
||||
)
|
||||
)
|
||||
val mockProject = project as MockProject
|
||||
createAndRegisterKotlinCodeBlockModificationListener(mockProject, pomModelImpl, treeAspect)
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
(project.picoContainer as MutablePicoContainer).unregisterComponent(KotlinCodeBlockModificationListener::class.java)
|
||||
(project.picoContainer as MutablePicoContainer).unregisterComponent(PomModel::class.java)
|
||||
unregisterKotlinCodeBlockModificationListener(project as MockProject)
|
||||
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.resolve
|
||||
|
||||
import com.intellij.mock.MockProject
|
||||
import com.intellij.pom.PomModel
|
||||
import com.intellij.pom.tree.TreeAspect
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
||||
|
||||
internal fun createAndRegisterKotlinCodeBlockModificationListener(project: MockProject, pomModel: PomModel, treeAspect: TreeAspect) {
|
||||
project.registerService(PomModel::class.java, pomModel)
|
||||
project.picoContainer.registerComponentInstance(
|
||||
KotlinCodeBlockModificationListener(
|
||||
PsiModificationTracker.SERVICE.getInstance(project),
|
||||
project,
|
||||
treeAspect
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
internal fun unregisterKotlinCodeBlockModificationListener(project: MockProject) {
|
||||
val picoContainer = project.picoContainer
|
||||
picoContainer.unregisterComponent(KotlinCodeBlockModificationListener::class.java)
|
||||
picoContainer.unregisterComponent(PomModel::class.java)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.resolve
|
||||
|
||||
import com.intellij.mock.MockProject
|
||||
import com.intellij.pom.PomModel
|
||||
import com.intellij.pom.tree.TreeAspect
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
||||
|
||||
internal fun createAndRegisterKotlinCodeBlockModificationListener(project: MockProject, pomModel: PomModel, treeAspect: TreeAspect) {
|
||||
project.registerService(PomModel::class.java, pomModel)
|
||||
project.registerService(TreeAspect::class.java, treeAspect)
|
||||
project.registerService(KotlinCodeBlockModificationListener::class.java, KotlinCodeBlockModificationListener(project))
|
||||
}
|
||||
|
||||
internal fun unregisterKotlinCodeBlockModificationListener(project: MockProject) {
|
||||
val picoContainer = project.picoContainer
|
||||
picoContainer.unregisterComponent(KotlinCodeBlockModificationListener::class.java)
|
||||
picoContainer.unregisterComponent(PomModel::class.java)
|
||||
}
|
||||
Reference in New Issue
Block a user