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.project.rootManager
|
||||||
import com.intellij.openapi.roots.ModuleRootEvent
|
import com.intellij.openapi.roots.ModuleRootEvent
|
||||||
import com.intellij.openapi.roots.ModuleRootListener
|
import com.intellij.openapi.roots.ModuleRootListener
|
||||||
|
import com.intellij.openapi.startup.StartupActivity
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.openapi.vfs.VfsUtilCore
|
import com.intellij.openapi.vfs.VfsUtilCore
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
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.ConcurrentHashMap
|
||||||
import java.util.concurrent.ConcurrentMap
|
import java.util.concurrent.ConcurrentMap
|
||||||
|
|
||||||
class KotlinPackageContentModificationListener(private val project: Project) : Disposable {
|
class KotlinPackageContentModificationListener : StartupActivity {
|
||||||
val connection = project.messageBus.connect()
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val LOG = Logger.getInstance(this::class.java)
|
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 {
|
connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
|
||||||
override fun before(events: MutableList<out VFileEvent>) = onEvents(events, false)
|
override fun before(events: MutableList<out VFileEvent>) = onEvents(events, false)
|
||||||
override fun after(events: List<VFileEvent>) = onEvents(events, true)
|
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 {
|
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.ide.highlighter.ArchiveFileType
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.components.ServiceManager
|
|
||||||
import com.intellij.openapi.fileTypes.FileTypeEvent
|
import com.intellij.openapi.fileTypes.FileTypeEvent
|
||||||
import com.intellij.openapi.fileTypes.FileTypeListener
|
import com.intellij.openapi.fileTypes.FileTypeListener
|
||||||
import com.intellij.openapi.fileTypes.FileTypeManager
|
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.VFileCreateEvent
|
||||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
||||||
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent
|
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||||
|
|
||||||
class LibraryModificationTracker(project: Project) : SimpleModificationTracker() {
|
class LibraryModificationTracker(project: Project) : SimpleModificationTracker() {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getInstance(project: Project) = ServiceManager.getService(project, LibraryModificationTracker::class.java)!!
|
fun getInstance(project: Project) = project.getServiceSafe(LibraryModificationTracker::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val connection = project.messageBus.connect()
|
val connection = project.messageBus.connect(project)
|
||||||
connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
|
connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
|
||||||
override fun after(events: List<VFileEvent>) {
|
override fun after(events: List<VFileEvent>) {
|
||||||
events.filter(::isRelevantEvent).let { createEvents ->
|
events.filter(::isRelevantEvent).let { createEvents ->
|
||||||
|
|||||||
+2
-2
@@ -72,7 +72,7 @@ class KotlinCodeBlockModificationListener(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
val model = PomManager.getModel(project)
|
val model = PomManager.getModel(project)
|
||||||
val messageBusConnection = project.messageBus.connect()
|
val messageBusConnection = project.messageBus.connect(project)
|
||||||
|
|
||||||
if (isLanguageTrackerEnabled) {
|
if (isLanguageTrackerEnabled) {
|
||||||
(PsiManager.getInstance(project) as PsiManagerImpl).addTreeChangePreprocessor(this)
|
(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
|
// 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
|
// 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()
|
val modificationScope = getInsideCodeBlockModificationScope(element.psi) ?: return emptyList()
|
||||||
modificationScope.blockDeclaration
|
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 newModCount = kotlinOfOfCodeBlockTracker.modificationCount
|
||||||
val affectedModule = lastAffectedModule
|
val affectedModule = lastAffectedModule
|
||||||
if (affectedModule != null && newModCount == lastAffectedModuleModCount + customIncrement) {
|
if (affectedModule != null && newModCount == lastAffectedModuleModCount + customIncrement) {
|
||||||
|
|||||||
@@ -51,3 +51,6 @@ inline fun invokeLater(crossinline action: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.application.ApplicationManager
|
||||||
import com.intellij.openapi.command.CommandProcessor
|
import com.intellij.openapi.command.CommandProcessor
|
||||||
|
import com.intellij.openapi.components.ServiceManager
|
||||||
import com.intellij.openapi.progress.ProgressIndicator
|
import com.intellij.openapi.progress.ProgressIndicator
|
||||||
import com.intellij.openapi.progress.ProgressIndicatorProvider
|
import com.intellij.openapi.progress.ProgressIndicatorProvider
|
||||||
import com.intellij.openapi.progress.ProgressManager
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
@@ -53,3 +54,6 @@ inline fun invokeLater(crossinline action: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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}")
|
||||||
|
|||||||
@@ -51,3 +51,6 @@ inline fun invokeLater(crossinline action: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.application.ApplicationManager
|
||||||
import com.intellij.openapi.command.CommandProcessor
|
import com.intellij.openapi.command.CommandProcessor
|
||||||
|
import com.intellij.openapi.components.ServiceManager
|
||||||
import com.intellij.openapi.progress.ProgressIndicator
|
import com.intellij.openapi.progress.ProgressIndicator
|
||||||
import com.intellij.openapi.progress.ProgressIndicatorProvider
|
import com.intellij.openapi.progress.ProgressIndicatorProvider
|
||||||
import com.intellij.openapi.progress.ProgressManager
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
@@ -53,3 +54,9 @@ inline fun invokeLater(crossinline action: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if (parameters.isAutoPopup) {
|
||||||
collector.addLookupElementPostProcessor { lookupElement ->
|
collector.addLookupElementPostProcessor { lookupElement ->
|
||||||
lookupElement.putUserData(LookupCancelWatcher.AUTO_POPUP_AT, position.startOffset)
|
lookupElement.putUserData(LookupCancelService.AUTO_POPUP_AT, position.startOffset)
|
||||||
lookupElement
|
lookupElement
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,7 +533,7 @@ class BasicCompletionSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun wasAutopopupRecentlyCancelled(parameters: CompletionParameters) =
|
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 {
|
private val KEYWORDS_ONLY = object : CompletionKind {
|
||||||
override val descriptorKindFilter: DescriptorKindFilter?
|
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.analysis.analyzeInContext
|
||||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
|
||||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
|
||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
import org.jetbrains.kotlin.psi.KtBlockExpression
|
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
@@ -46,7 +47,7 @@ class CompletionBindingContextProvider(project: Project) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getInstance(project: Project): CompletionBindingContextProvider =
|
fun getInstance(project: Project): CompletionBindingContextProvider =
|
||||||
project.getComponent(CompletionBindingContextProvider::class.java)
|
project.getServiceSafe(CompletionBindingContextProvider::class.java)
|
||||||
|
|
||||||
var ENABLED = true
|
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.LookupEvent
|
||||||
import com.intellij.codeInsight.lookup.LookupListener
|
import com.intellij.codeInsight.lookup.LookupListener
|
||||||
import com.intellij.codeInsight.lookup.LookupManager
|
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.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.EditorFactoryEvent
|
||||||
import com.intellij.openapi.editor.event.EditorFactoryListener
|
import com.intellij.openapi.editor.event.EditorFactoryListener
|
||||||
import com.intellij.openapi.project.Project
|
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
|
||||||
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector.completionStatsData
|
import org.jetbrains.kotlin.idea.statistics.CompletionFUSCollector.completionStatsData
|
||||||
import org.jetbrains.kotlin.idea.statistics.FinishReasonStats
|
import org.jetbrains.kotlin.idea.statistics.FinishReasonStats
|
||||||
|
|
||||||
class LookupCancelWatcher(val project: Project) : ProjectComponent {
|
class LookupCancelWatcher : StartupActivity {
|
||||||
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
|
override fun runActivity(project: Project) {
|
||||||
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 ->
|
CompletionPhaseListener.TOPIC.subscribe(project, CompletionPhaseListener { isCompletionRunning ->
|
||||||
if (isCompletionRunning) {
|
if (isCompletionRunning) {
|
||||||
if (completionStatsData != null) {
|
if (completionStatsData != null) {
|
||||||
@@ -117,9 +52,7 @@ class LookupCancelWatcher(val project: Project) : ProjectComponent {
|
|||||||
EditorFactory.getInstance().addEditorFactoryListener(
|
EditorFactory.getInstance().addEditorFactoryListener(
|
||||||
object : EditorFactoryListener {
|
object : EditorFactoryListener {
|
||||||
override fun editorReleased(event: EditorFactoryEvent) {
|
override fun editorReleased(event: EditorFactoryEvent) {
|
||||||
if (lastReminiscence?.editor == event.editor) {
|
LookupCancelService.getServiceIfCreated(project)?.disposeLastReminiscence(event.editor)
|
||||||
lastReminiscence!!.dispose()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
project
|
project
|
||||||
@@ -128,7 +61,7 @@ class LookupCancelWatcher(val project: Project) : ProjectComponent {
|
|||||||
LookupManager.getInstance(project).addPropertyChangeListener { event ->
|
LookupManager.getInstance(project).addPropertyChangeListener { event ->
|
||||||
if (event.propertyName == LookupManager.PROP_ACTIVE_LOOKUP) {
|
if (event.propertyName == LookupManager.PROP_ACTIVE_LOOKUP) {
|
||||||
val lookup = event.newValue as Lookup?
|
val lookup = event.newValue as Lookup?
|
||||||
lookup?.addLookupListener(lookupCancelListener)
|
lookup?.addLookupListener(LookupCancelService.getInstance(project).lookupCancelListener)
|
||||||
lookup?.addLookupListener(object : LookupListener {
|
lookup?.addLookupListener(object : LookupListener {
|
||||||
override fun lookupShown(event: LookupEvent) {
|
override fun lookupShown(event: LookupEvent) {
|
||||||
completionStatsData = completionStatsData?.copy(shownTime = System.currentTimeMillis())
|
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 {
|
init {
|
||||||
val connection = project.messageBus.connect()
|
val connection = project.messageBus.connect(project)
|
||||||
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
|
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
|
||||||
override fun rootsChanged(event: ModuleRootEvent) {
|
override fun rootsChanged(event: ModuleRootEvent) {
|
||||||
clearClassRootsCaches()
|
clearClassRootsCaches()
|
||||||
|
|||||||
+3
-3
@@ -13,8 +13,8 @@ import com.intellij.testFramework.runInEdtAndWait
|
|||||||
import com.intellij.util.concurrency.FutureResult
|
import com.intellij.util.concurrency.FutureResult
|
||||||
import org.jetbrains.kotlin.config.ApiVersion
|
import org.jetbrains.kotlin.config.ApiVersion
|
||||||
import org.jetbrains.kotlin.config.LanguageVersion
|
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.KotlinMigrationProjectComponent.MigrationTestState
|
import org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectService.MigrationTestState
|
||||||
import org.jetbrains.kotlin.idea.configuration.MigrationInfo
|
import org.jetbrains.kotlin.idea.configuration.MigrationInfo
|
||||||
import org.jetbrains.plugins.gradle.tooling.annotation.TargetVersions
|
import org.jetbrains.plugins.gradle.tooling.annotation.TargetVersions
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
@@ -98,7 +98,7 @@ class GradleMigrateTest : GradleImportingTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val importResult = FutureResult<MigrationTestState?>()
|
val importResult = FutureResult<MigrationTestState?>()
|
||||||
val migrationProjectComponent = KotlinMigrationProjectComponent.getInstanceIfNotDisposed(myProject)
|
val migrationProjectComponent = KotlinMigrationProjectService.getInstanceIfNotDisposed(myProject)
|
||||||
?: error("Disposed project")
|
?: error("Disposed project")
|
||||||
|
|
||||||
migrationProjectComponent.setImportFinishListener { migrationState ->
|
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() {
|
class KotlinExternalSystemSyncListener : ExternalSystemTaskNotificationListenerAdapter() {
|
||||||
override fun onStart(id: ExternalSystemTaskId, workingDir: String) {
|
override fun onStart(id: ExternalSystemTaskId, workingDir: String) {
|
||||||
val project = id.findResolvedProject() ?: return
|
val project = id.findResolvedProject() ?: return
|
||||||
KotlinMigrationProjectComponent.getInstanceIfNotDisposed(project)?.onImportAboutToStart()
|
KotlinMigrationProjectService.getInstanceIfNotDisposed(project)?.onImportAboutToStart()
|
||||||
KotlinConfigurationCheckerService.getInstanceIfNotDisposed(project)?.syncStarted()
|
KotlinConfigurationCheckerService.getInstanceIfNotDisposed(project)?.syncStarted()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ class KotlinConfigurationCheckerComponent(val project: Project) : ProjectCompone
|
|||||||
NotificationsConfiguration.getNotificationsConfiguration()
|
NotificationsConfiguration.getNotificationsConfiguration()
|
||||||
.register(CONFIGURE_NOTIFICATION_GROUP_ID, NotificationDisplayType.STICKY_BALLOON, true)
|
.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 {
|
connection.subscribe(ProjectDataImportListener.TOPIC, ProjectDataImportListener {
|
||||||
notifyOutdatedBundledCompilerIfNecessary(project)
|
notifyOutdatedBundledCompilerIfNecessary(project)
|
||||||
})
|
})
|
||||||
|
|||||||
+20
-6
@@ -18,6 +18,8 @@ package org.jetbrains.kotlin.idea.configuration.ui
|
|||||||
|
|
||||||
import com.intellij.notification.NotificationDisplayType
|
import com.intellij.notification.NotificationDisplayType
|
||||||
import com.intellij.notification.NotificationsConfiguration
|
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.ReadAction.nonBlocking
|
||||||
import com.intellij.openapi.application.runReadAction
|
import com.intellij.openapi.application.runReadAction
|
||||||
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener
|
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.notifyOutdatedBundledCompilerIfNecessary
|
||||||
import org.jetbrains.kotlin.idea.configuration.ui.notifications.notifyKotlinStyleUpdateIfNeeded
|
import org.jetbrains.kotlin.idea.configuration.ui.notifications.notifyKotlinStyleUpdateIfNeeded
|
||||||
import org.jetbrains.kotlin.idea.project.getAndCacheLanguageLevelByDependencies
|
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
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
class KotlinConfigurationCheckerStartupActivity : StartupActivity {
|
class KotlinConfigurationCheckerStartupActivity : StartupActivity {
|
||||||
@@ -53,15 +58,24 @@ class KotlinConfigurationCheckerService(val project: Project) {
|
|||||||
private val syncDepth = AtomicInteger()
|
private val syncDepth = AtomicInteger()
|
||||||
|
|
||||||
fun performProjectPostOpenActions() {
|
fun performProjectPostOpenActions() {
|
||||||
nonBlocking {
|
nonBlocking(Callable {
|
||||||
val modulesWithKotlinFiles = getModulesWithKotlinFiles(project)
|
return@Callable getModulesWithKotlinFiles(project)
|
||||||
for (module in modulesWithKotlinFiles) {
|
})
|
||||||
module.getAndCacheLanguageLevelByDependencies()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.inSmartMode(project)
|
.inSmartMode(project)
|
||||||
.expireWith(project)
|
.expireWith(project)
|
||||||
.coalesceBy(this)
|
.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())
|
.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.MavenImportListener
|
||||||
import org.jetbrains.idea.maven.project.MavenProject
|
import org.jetbrains.idea.maven.project.MavenProject
|
||||||
import org.jetbrains.idea.maven.project.MavenProjectsManager
|
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
|
import org.jetbrains.kotlin.idea.configuration.notifyOutdatedBundledCompilerIfNecessary
|
||||||
|
|
||||||
class MavenImportListener(val project: Project) : MavenProjectsManager.Listener {
|
class MavenImportListener(val project: Project) : MavenProjectsManager.Listener {
|
||||||
@@ -19,7 +19,7 @@ class MavenImportListener(val project: Project) : MavenProjectsManager.Listener
|
|||||||
MavenImportListener.TOPIC,
|
MavenImportListener.TOPIC,
|
||||||
MavenImportListener { _: Collection<MavenProject>, _: List<Module> ->
|
MavenImportListener { _: Collection<MavenProject>, _: List<Module> ->
|
||||||
notifyOutdatedBundledCompilerIfNecessary(project)
|
notifyOutdatedBundledCompilerIfNecessary(project)
|
||||||
KotlinMigrationProjectComponent.getInstanceIfNotDisposed(project)?.onImportFinished()
|
KotlinMigrationProjectService.getInstanceIfNotDisposed(project)?.onImportFinished()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,6 +27,6 @@ class MavenImportListener(val project: Project) : MavenProjectsManager.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun projectsScheduled() {
|
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 com.intellij.util.concurrency.FutureResult
|
||||||
import org.jetbrains.kotlin.config.ApiVersion
|
import org.jetbrains.kotlin.config.ApiVersion
|
||||||
import org.jetbrains.kotlin.config.LanguageVersion
|
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.MigrationInfo
|
||||||
|
import org.jetbrains.kotlin.idea.configuration.MigrationTestState
|
||||||
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
|
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -106,8 +107,8 @@ class MavenMigrateTest : MavenImportingTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val importResult = FutureResult<KotlinMigrationProjectComponent.MigrationTestState?>()
|
val importResult = FutureResult<MigrationTestState?>()
|
||||||
val migrationProjectComponent = KotlinMigrationProjectComponent.getInstanceIfNotDisposed(myProject)
|
val migrationProjectComponent = KotlinMigrationProjectService.getInstanceIfNotDisposed(myProject)
|
||||||
?: error("Disposed project")
|
?: error("Disposed project")
|
||||||
|
|
||||||
migrationProjectComponent.setImportFinishListener { migrationState ->
|
migrationProjectComponent.setImportFinishListener { migrationState ->
|
||||||
|
|||||||
@@ -1,26 +1,4 @@
|
|||||||
<idea-plugin>
|
<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>
|
<application-components>
|
||||||
<component>
|
<component>
|
||||||
<implementation-class>org.jetbrains.kotlin.idea.versions.KotlinUpdatePluginComponent</implementation-class>
|
<implementation-class>org.jetbrains.kotlin.idea.versions.KotlinUpdatePluginComponent</implementation-class>
|
||||||
@@ -216,14 +194,6 @@
|
|||||||
</actions>
|
</actions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
<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"
|
<applicationService serviceInterface="org.jetbrains.kotlin.idea.KotlinIconProviderService"
|
||||||
serviceImplementation="org.jetbrains.kotlin.idea.KotlinIdeFileIconProviderService"/>
|
serviceImplementation="org.jetbrains.kotlin.idea.KotlinIdeFileIconProviderService"/>
|
||||||
|
|
||||||
@@ -319,6 +289,8 @@
|
|||||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettings"
|
<projectService serviceInterface="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettings"
|
||||||
serviceImplementation="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 serviceImplementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerWorkspaceSettings"/>
|
||||||
|
|
||||||
<projectService serviceInterface="org.jetbrains.kotlin.asJava.classes.FacadeCache"
|
<projectService serviceInterface="org.jetbrains.kotlin.asJava.classes.FacadeCache"
|
||||||
@@ -336,6 +308,9 @@
|
|||||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker"
|
<projectService serviceInterface="org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker"
|
||||||
serviceImplementation="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"
|
<projectService serviceInterface="org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade"
|
||||||
serviceImplementation="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/*)"/>
|
<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>
|
<extensionPoints>
|
||||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/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>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
<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.gradle.target" version="2"/>
|
||||||
<statistics.counterUsagesCollector groupId="kotlin.maven.target" version="3"/>
|
<statistics.counterUsagesCollector groupId="kotlin.maven.target" version="3"/>
|
||||||
<statistics.counterUsagesCollector groupId="kotlin.jps.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/*)"/>
|
<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>
|
<extensionPoints>
|
||||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/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/*)"/>
|
<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>
|
<extensionPoints>
|
||||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/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>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
<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.gradle.target" version="2"/>
|
||||||
<statistics.counterUsagesCollector groupId="kotlin.maven.target" version="3"/>
|
<statistics.counterUsagesCollector groupId="kotlin.maven.target" version="3"/>
|
||||||
<statistics.counterUsagesCollector groupId="kotlin.jps.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/*)"/>
|
<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>
|
<extensionPoints>
|
||||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/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/*)"/>
|
<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>
|
<extensionPoints>
|
||||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/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/*)"/>
|
<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>
|
<extensionPoints>
|
||||||
<xi:include href="extensions/compiler.xml" xpointer="xpointer(/idea-plugin/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/*)"/>
|
<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">
|
<extensions defaultExtensionNs="com.intellij.jvm">
|
||||||
<declarationSearcher language="kotlin" implementationClass="org.jetbrains.kotlin.idea.jvm.KotlinDeclarationSearcher"/>
|
<declarationSearcher language="kotlin" implementationClass="org.jetbrains.kotlin.idea.jvm.KotlinDeclarationSearcher"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
<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"/>
|
<fileTypeUsageSchemaDescriptor schema="Gradle Script" implementationClass="org.jetbrains.kotlin.idea.core.script.KotlinGradleScriptFileTypeSchemaDetector"/>
|
||||||
<completion.ml.model implementation="org.jetbrains.kotlin.idea.completion.ml.KotlinMLRankingProvider"/>
|
<completion.ml.model implementation="org.jetbrains.kotlin.idea.completion.ml.KotlinMLRankingProvider"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
|
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
||||||
|
<pluginUpdateVerifier implementation="org.jetbrains.kotlin.idea.update.GooglePluginUpdateVerifier"/>
|
||||||
|
</extensions>
|
||||||
</idea-plugin>
|
</idea-plugin>
|
||||||
|
|||||||
+5
-298
@@ -5,310 +5,17 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.configuration
|
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.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.project.Project
|
||||||
import com.intellij.openapi.roots.ProjectRootManager
|
import com.intellij.openapi.startup.StartupActivity
|
||||||
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) {
|
class KotlinMigrationProjectComponent : StartupActivity {
|
||||||
@Volatile
|
|
||||||
private var old: MigrationState? = null
|
|
||||||
|
|
||||||
@Volatile
|
override fun runActivity(project: Project) {
|
||||||
private var importFinishListener: ((MigrationTestState?) -> Unit)? = null
|
val connection = project.messageBus.connect(project)
|
||||||
|
|
||||||
init {
|
|
||||||
val connection = project.messageBus.connect()
|
|
||||||
connection.subscribe(ProjectDataImportListener.TOPIC, ProjectDataImportListener {
|
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 com.intellij.util.containers.ContainerUtil
|
||||||
import org.jetbrains.kotlin.idea.core.util.end
|
import org.jetbrains.kotlin.idea.core.util.end
|
||||||
import org.jetbrains.kotlin.idea.core.util.range
|
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.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class KotlinCodeHintsModel(val project: Project) : EditorFactoryListener {
|
class KotlinCodeHintsModel(val project: Project) : EditorFactoryListener {
|
||||||
companion object {
|
companion object {
|
||||||
fun getInstance(project: Project): KotlinCodeHintsModel =
|
fun getInstance(project: Project): KotlinCodeHintsModel = project.getServiceSafe(KotlinCodeHintsModel::class.java)
|
||||||
project.getComponent(KotlinCodeHintsModel::class.java) ?: error("Component `KotlinCodeHintsModel` is expected to be registered")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DocumentExtensionInfoModel(val document: Document) {
|
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.PomModel
|
||||||
import com.intellij.pom.core.impl.PomModelImpl
|
import com.intellij.pom.core.impl.PomModelImpl
|
||||||
import com.intellij.pom.tree.TreeAspect
|
import com.intellij.pom.tree.TreeAspect
|
||||||
import com.intellij.psi.util.PsiModificationTracker
|
|
||||||
import org.jetbrains.kotlin.container.ComponentProvider
|
import org.jetbrains.kotlin.container.ComponentProvider
|
||||||
import org.jetbrains.kotlin.container.get
|
import org.jetbrains.kotlin.container.get
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
@@ -33,19 +32,12 @@ abstract class AbstractAdditionalResolveDescriptorRendererTest : AbstractDescrip
|
|||||||
val pomModelImpl = PomModelImpl(project)
|
val pomModelImpl = PomModelImpl(project)
|
||||||
val treeAspect = TreeAspect(pomModelImpl)
|
val treeAspect = TreeAspect(pomModelImpl)
|
||||||
|
|
||||||
(project as MockProject).registerService(PomModel::class.java, pomModelImpl)
|
val mockProject = project as MockProject
|
||||||
(project.picoContainer as MutablePicoContainer).registerComponentInstance(
|
createAndRegisterKotlinCodeBlockModificationListener(mockProject, pomModelImpl, treeAspect)
|
||||||
KotlinCodeBlockModificationListener(
|
|
||||||
PsiModificationTracker.SERVICE.getInstance(project),
|
|
||||||
project,
|
|
||||||
treeAspect
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tearDown() {
|
override fun tearDown() {
|
||||||
(project.picoContainer as MutablePicoContainer).unregisterComponent(KotlinCodeBlockModificationListener::class.java)
|
unregisterKotlinCodeBlockModificationListener(project as MockProject)
|
||||||
(project.picoContainer as MutablePicoContainer).unregisterComponent(PomModel::class.java)
|
|
||||||
|
|
||||||
super.tearDown()
|
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