Rewrite scripting related API to PsiFile instead of VirtualFile

There were several places where we converted virtualFile to PsiFile. This operation need a read access and may throw ProcessCanceledException,
so we want to minimize its usages in IDE
This commit is contained in:
Natalia Selezneva
2019-08-08 13:47:39 +03:00
parent 5b48dcca4f
commit dc46f73ecf
30 changed files with 278 additions and 211 deletions
@@ -23,6 +23,7 @@ import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import com.intellij.ui.EditorNotifications
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.UserDataProperty
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
import kotlin.script.experimental.api.ScriptDiagnostic
@@ -49,6 +50,10 @@ class IdeScriptReportSink(val project: Project) : ScriptReportSink {
return file.scriptDiagnostics ?: emptyList()
}
fun getReports(file: KtFile): List<ScriptDiagnostic> {
return file.originalFile.virtualFile?.scriptDiagnostics ?: emptyList()
}
private var VirtualFile.scriptDiagnostics: List<ScriptDiagnostic>? by UserDataProperty(Key.create("KOTLIN_SCRIPT_DIAGNOSTICS"))
}
}
@@ -18,10 +18,18 @@ package org.jetbrains.kotlin.idea.core.script
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.util.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.getScriptOriginalFile
import java.io.*
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.dependencies.ScriptDependencies
var KtFile.scriptDependencies: ScriptDependencies?
get() = this.getScriptOriginalFile()?.virtualFile?.scriptDependencies
set(value) {
this.getScriptOriginalFile()?.virtualFile?.scriptDependencies = value
}
var VirtualFile.scriptDependencies: ScriptDependencies? by cachedFileAttribute(
name = "kotlin-script-dependencies",
version = 3,
@@ -45,6 +53,12 @@ var VirtualFile.scriptDependencies: ScriptDependencies? by cachedFileAttribute(
}
)
var KtFile.scriptCompilationConfiguration: ScriptCompilationConfiguration?
get() = this.getScriptOriginalFile()?.virtualFile?.scriptCompilationConfiguration
set(value) {
this.getScriptOriginalFile()?.virtualFile?.scriptCompilationConfiguration = value
}
var VirtualFile.scriptCompilationConfiguration: ScriptCompilationConfiguration? by cachedFileAttribute(
name = "kotlin-script-compilation-configuration",
version = 1,
@@ -23,16 +23,17 @@ import com.intellij.openapi.projectRoots.JavaSdkType
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.util.io.URLUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
import org.jetbrains.kotlin.idea.core.script.dependencies.SyncScriptDependenciesLoader
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.io.File
import kotlin.script.experimental.api.valueOrNull
@@ -40,28 +41,35 @@ import kotlin.script.experimental.api.valueOrNull
// NOTE: this service exists exclusively because ScriptDependencyManager
// cannot be registered as implementing two services (state would be duplicated)
class IdeScriptDependenciesProvider(
private val scriptDependenciesManager: ScriptDependenciesManager
) : ScriptDependenciesProvider {
override fun getScriptConfigurationResult(file: VirtualFile): ScriptCompilationConfigurationResult? = scriptDependenciesManager.getRefinedCompilationConfiguration(file)
private val scriptDependenciesManager: ScriptDependenciesManager,
project: Project
) : ScriptDependenciesProvider(project) {
override fun getScriptConfigurationResult(file: KtFile): ScriptCompilationConfigurationResult? {
return scriptDependenciesManager.getRefinedCompilationConfiguration(file)
}
}
// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore
class ScriptDependenciesManager internal constructor(
private val cacheUpdater: ScriptsCompilationConfigurationUpdater,
private val cache: ScriptsCompilationConfigurationCache
private val cache: ScriptsCompilationConfigurationCache,
private val project: Project
) {
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> =
toVfsRoots(cacheUpdater.getCurrentCompilationConfiguration(file)?.valueOrNull()?.dependenciesClassPath.orEmpty())
fun getRefinedCompilationConfiguration(file: VirtualFile): ScriptCompilationConfigurationResult? =
cacheUpdater.getCurrentCompilationConfiguration(file)
fun getScriptSdk(file: VirtualFile, project: Project): Sdk? {
return getScriptSdk(getRefinedCompilationConfiguration(file)?.valueOrNull())
?: getScriptDefaultSdk(project)
@Deprecated("Use getScriptClasspath(KtFile) instead")
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> {
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return emptyList()
return getScriptClasspath(ktFile)
}
fun getScriptClasspath(file: KtFile): List<VirtualFile> =
toVfsRoots(cacheUpdater.getCurrentCompilationConfiguration(file)?.valueOrNull()?.dependenciesClassPath.orEmpty())
fun getRefinedCompilationConfiguration(file: KtFile): ScriptCompilationConfigurationResult? =
cacheUpdater.getCurrentCompilationConfiguration(file)
fun getScriptDependenciesClassFilesScope(file: VirtualFile) = cache.scriptDependenciesClassFilesScope(file)
fun getScriptSdk(file: VirtualFile) = cache.getScriptSdk(file)
fun getAllScriptsSdks() = cache.allSdks
@@ -76,17 +84,6 @@ class ScriptDependenciesManager internal constructor(
fun getInstance(project: Project): ScriptDependenciesManager =
ServiceManager.getService(project, ScriptDependenciesManager::class.java)
fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? {
// workaround for mismatched gradle wrapper and plugin version
val javaHome = try {
compilationConfiguration?.javaHome?.let { VfsUtil.findFileByIoFile(it, true) }
} catch (e: Throwable) {
null
} ?: return null
return getAllProjectSdks().find { it.homeDirectory == javaHome }
}
fun getScriptDefaultSdk(project: Project): Sdk? {
val projectSdk = ProjectRootManager.getInstance(project).projectSdk?.takeIf { it.canBeUsedForScript() }
if (projectSdk != null) return projectSdk
@@ -124,10 +121,13 @@ class ScriptDependenciesManager internal constructor(
internal val log = Logger.getInstance(ScriptDependenciesManager::class.java)
@TestOnly
fun updateScriptDependenciesSynchronously(virtualFile: VirtualFile, project: Project) {
fun updateScriptDependenciesSynchronously(file: PsiFile, project: Project) {
val loader = SyncScriptDependenciesLoader(project)
val scriptDefinition = virtualFile.findScriptDefinition(project) ?: return
loader.loadDependencies(virtualFile, scriptDefinition)
val scriptDefinition = file.findScriptDefinition() ?: return
assert(file is KtFile) {
"PsiFile should be a KtFile, otherwise script dependencies cannot be loaded"
}
loader.loadDependencies(file as KtFile, scriptDefinition)
loader.notifyRootsChanged()
}
}
@@ -28,9 +28,9 @@ fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) {
}
fun VirtualFile.addScriptDependenciesNotificationPanel(
compilationConfigurationResult: ScriptCompilationConfigurationResult?,
compilationConfigurationResult: ScriptCompilationConfigurationResult,
project: Project,
onClick: (ScriptCompilationConfigurationResult?) -> Unit
onClick: (ScriptCompilationConfigurationResult) -> Unit
) {
withSelectedEditor(project) { manager ->
val existingPanel = notificationPanel
@@ -61,8 +61,8 @@ private fun VirtualFile.withSelectedEditor(project: Project, f: FileEditor.(File
private var FileEditor.notificationPanel: NewScriptDependenciesNotificationPanel? by UserDataProperty<FileEditor, NewScriptDependenciesNotificationPanel>(Key.create("script.dependencies.panel"))
private class NewScriptDependenciesNotificationPanel(
onClick: (ScriptCompilationConfigurationResult?) -> Unit,
val compilationConfigurationResult: ScriptCompilationConfigurationResult?,
onClick: (ScriptCompilationConfigurationResult) -> Unit,
val compilationConfigurationResult: ScriptCompilationConfigurationResult,
project: Project
) : EditorNotificationPanel() {
@@ -15,15 +15,19 @@ import com.intellij.openapi.roots.ModuleRootEvent
import com.intellij.openapi.roots.ModuleRootListener
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElementFinder
import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.NonClasspathDirectoriesScope
import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.util.containers.SLRUMap
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.kotlin.idea.caches.project.getAllProjectSdks
import org.jetbrains.kotlin.idea.core.util.EDT
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.util.concurrent.locks.ReentrantReadWriteLock
@@ -51,45 +55,79 @@ class ScriptsCompilationConfigurationCache(private val project: Project) {
private val cacheLock = ReentrantReadWriteLock()
private val scriptDependenciesCache = SLRUCacheWithLock<ScriptCompilationConfigurationResult>()
private val scriptsModificationStampsCache = SLRUCacheWithLock<Long>()
private val scriptDependenciesCache = SLRUCacheWithLock<VirtualFile, ScriptCompilationConfigurationResult>()
private val scriptsModificationStampsCache = SLRUCacheWithLock<KtFile, Long>()
operator fun get(virtualFile: VirtualFile): ScriptCompilationConfigurationResult? = scriptDependenciesCache.get(virtualFile)
operator fun get(file: VirtualFile): ScriptCompilationConfigurationResult? = scriptDependenciesCache.get(file)
fun shouldRunDependenciesUpdate(file: VirtualFile): Boolean {
fun shouldRunDependenciesUpdate(file: KtFile): Boolean {
return scriptsModificationStampsCache.replace(file, file.modificationStamp) != file.modificationStamp
}
private val scriptsDependenciesClasspathScopeCache = SLRUCacheWithLock<GlobalSearchScope>()
private val scriptsDependenciesClasspathScopeCache: MutableMap<VirtualFile, GlobalSearchScope> =
ConcurrentFactoryMap.createWeakMap { file ->
val compilationConfiguration = getCachedConfigurationOrLoad(file)
?: return@createWeakMap null
fun scriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope {
return scriptsDependenciesClasspathScopeCache.getOrPut(file) {
val compilationConfiguration = scriptDependenciesCache.get(file)?.valueOrNull() ?: return@getOrPut GlobalSearchScope.EMPTY_SCOPE
val roots = compilationConfiguration.dependenciesClassPath
val sdk = ScriptDependenciesManager.getScriptSdk(compilationConfiguration)
val sdk = getScriptSdk(file)
@Suppress("FoldInitializerAndIfToElvis")
if (sdk == null) {
return@getOrPut NonClasspathDirectoriesScope.compose(ScriptDependenciesManager.toVfsRoots(roots))
return@createWeakMap NonClasspathDirectoriesScope.compose(ScriptDependenciesManager.toVfsRoots(roots))
}
return@getOrPut NonClasspathDirectoriesScope.compose(
return@createWeakMap NonClasspathDirectoriesScope.compose(
sdk.rootProvider.getFiles(OrderRootType.CLASSES).toList() +
ScriptDependenciesManager.toVfsRoots(roots)
)
}
fun scriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope {
return scriptsDependenciesClasspathScopeCache[file] ?: GlobalSearchScope.EMPTY_SCOPE
}
private val scriptsSdksCache: MutableMap<VirtualFile, Sdk?> =
ConcurrentFactoryMap.createWeakMap { file ->
val compilationConfiguration = getCachedConfigurationOrLoad(file)
return@createWeakMap getScriptSdk(compilationConfiguration) ?: ScriptDependenciesManager.getScriptDefaultSdk(project)
}
fun getScriptSdk(file: VirtualFile): Sdk? {
return scriptsSdksCache[file]
}
private fun getCachedConfigurationOrLoad(file: VirtualFile): ScriptCompilationConfigurationWrapper? {
val compilationConfiguration = scriptDependenciesCache.get(file)?.valueOrNull()
if (compilationConfiguration != null) {
return compilationConfiguration
}
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null
return ScriptDependenciesManager.getInstance(project).getRefinedCompilationConfiguration(ktFile)?.valueOrNull()
}
private fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? {
// workaround for mismatched gradle wrapper and plugin version
val javaHome = try {
compilationConfiguration?.javaHome?.let { VfsUtil.findFileByIoFile(it, true) }
} catch (e: Throwable) {
null
} ?: return null
return getAllProjectSdks().find { it.homeDirectory == javaHome }
}
val allSdks by ClearableLazyValue(cacheLock) {
scriptDependenciesCache.getAll()
.mapNotNull { ScriptDependenciesManager.getInstance(project).getScriptSdk(it.key, project) }
.mapNotNull { getScriptSdk(it.key) }
.distinct()
}
private val allNonIndexedSdks by ClearableLazyValue(cacheLock) {
scriptDependenciesCache.getAll()
.mapNotNull { ScriptDependenciesManager.getInstance(project).getScriptSdk(it.key, project) }
.mapNotNull { getScriptSdk(it.key) }
.filterNonModuleSdk()
.distinct()
}
@@ -164,7 +202,7 @@ class ScriptsCompilationConfigurationCache(private val project: Project) {
}
fun hasNotCachedRoots(compilationConfiguration: ScriptCompilationConfigurationWrapper): Boolean {
val scriptSdk = ScriptDependenciesManager.getScriptSdk(compilationConfiguration) ?: ScriptDependenciesManager.getScriptDefaultSdk(project)
val scriptSdk = getScriptSdk(compilationConfiguration) ?: ScriptDependenciesManager.getScriptDefaultSdk(project)
return (scriptSdk != null && !allSdks.contains(scriptSdk)) ||
!allDependenciesClassFiles.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesClassPath)) ||
!allDependenciesSources.containsAll(ScriptDependenciesManager.toVfsRoots(compilationConfiguration.dependenciesSources))
@@ -176,23 +214,23 @@ class ScriptsCompilationConfigurationCache(private val project: Project) {
scriptDependenciesCache.clear()
scriptsModificationStampsCache.clear()
onChange(keys)
updateHighlighting(keys)
}
fun save(virtualFile: VirtualFile, new: ScriptCompilationConfigurationResult): Boolean {
val old = scriptDependenciesCache.replace(virtualFile, new)
fun save(file: VirtualFile, new: ScriptCompilationConfigurationResult): Boolean {
val old = scriptDependenciesCache.replace(file, new)
val changed = new != old
if (changed) {
onChange(listOf(virtualFile))
onChange(listOf(file))
}
return changed
}
fun delete(virtualFile: VirtualFile): Boolean {
val changed = scriptDependenciesCache.remove(virtualFile)
fun delete(file: VirtualFile): Boolean {
val changed = scriptDependenciesCache.remove(file)
if (changed) {
onChange(listOf(virtualFile))
onChange(listOf(file))
}
return changed
}
@@ -226,20 +264,19 @@ private class ClearableLazyValue<in R, out T : Any>(
}
}
private class SLRUCacheWithLock<T> {
private class SLRUCacheWithLock<K, V> {
private val lock = ReentrantReadWriteLock()
val cache = SLRUMap<VirtualFile, T>(
val cache = SLRUMap<K, V>(
ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED,
ScriptsCompilationConfigurationCache.MAX_SCRIPTS_CACHED
)
fun get(value: VirtualFile): T? = lock.write {
fun get(value: K): V? = lock.write {
cache[value]
}
fun getOrPut(key: VirtualFile, defaultValue: () -> T): T = lock.write {
fun getOrPut(key: K, defaultValue: () -> V): V = lock.write {
val value = cache.get(key)
return if (value == null) {
val answer = defaultValue()
@@ -250,11 +287,11 @@ private class SLRUCacheWithLock<T> {
}
}
fun remove(file: VirtualFile) = lock.write {
fun remove(file: K) = lock.write {
cache.remove(file)
}
fun getAll(): Collection<Map.Entry<VirtualFile, T>> = lock.write {
fun getAll(): Collection<Map.Entry<K, V>> = lock.write {
cache.entrySet()
}
@@ -262,10 +299,9 @@ private class SLRUCacheWithLock<T> {
cache.clear()
}
fun replace(file: VirtualFile, value: T): T? = lock.write {
fun replace(file: K, value: V): V? = lock.write {
val old = get(file)
cache.put(file, value)
old
}
}
}
@@ -39,13 +39,9 @@ import org.jetbrains.kotlin.idea.core.script.dependencies.SyncScriptDependencies
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.isNonScript
import org.jetbrains.kotlin.scripting.resolve.LegacyResolverWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
class ScriptsCompilationConfigurationUpdater(
private val project: Project,
@@ -65,8 +61,10 @@ class ScriptsCompilationConfigurationUpdater(
listenForChangesInScripts()
}
fun getCurrentCompilationConfiguration(file: VirtualFile): ScriptCompilationConfigurationResult? {
cache[file]?.let { return it }
fun getCurrentCompilationConfiguration(file: KtFile): ScriptCompilationConfigurationResult? {
val virtualFile = file.originalFile.virtualFile
cache[virtualFile]?.let { return it }
// Try-catch block is a temporary workaround for ProcessCanceledException
// that may happen during VirtualFile to PsiFile transformation
@@ -76,10 +74,10 @@ class ScriptsCompilationConfigurationUpdater(
makeRootsChangeIfNeeded()
}
return cache[file]
return cache[virtualFile]
}
fun updateDependenciesIfNeeded(files: List<VirtualFile>): Boolean {
fun updateDependenciesIfNeeded(files: List<KtFile>): Boolean {
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return false
var wasDependenciesUpdateStarted = false
@@ -97,11 +95,11 @@ class ScriptsCompilationConfigurationUpdater(
return wasDependenciesUpdateStarted
}
private fun updateDependencies(file: VirtualFile) {
private fun updateDependencies(file: KtFile) {
if (!ScriptDefinitionsManager.getInstance(project).isReady()) return
if (!cache.shouldRunDependenciesUpdate(file)) return
val scriptDefinition = file.findScriptDefinition(project) ?: return
val scriptDefinition = file.findScriptDefinition() ?: return
loaders.filter { it.isApplicable(file, scriptDefinition) }.forEach { it.loadDependencies(file, scriptDefinition) }
}
@@ -122,24 +120,18 @@ class ScriptsCompilationConfigurationUpdater(
}
private fun runScriptDependenciesUpdateIfNeeded(file: VirtualFile) {
if (!shouldStartUpdate(file)) return
val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return
updateDependencies(file)
updateDependencies(ktFile)
makeRootsChangeIfNeeded()
}
})
EditorFactory.getInstance().eventMulticaster.addDocumentListener(object : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
val document = event.document
val file = FileDocumentManager.getInstance().getFile(document)?.takeIf { it.isInLocalFileSystem } ?: return
if (!file.isValid) {
cache.delete(file)
return
}
if (!shouldStartUpdate(file)) return
val ktFile = getKtFileToStartConfigurationUpdate(file) ?: return
// only update dependencies for scripts that were touched recently
if (cache[file] == null) {
@@ -151,7 +143,7 @@ class ScriptsCompilationConfigurationUpdater(
scriptsQueue.addRequest(
{
FileDocumentManager.getInstance().saveDocument(document)
updateDependencies(file)
updateDependencies(ktFile)
makeRootsChangeIfNeeded()
},
scriptChangesListenerDelay,
@@ -161,31 +153,30 @@ class ScriptsCompilationConfigurationUpdater(
}, project.messageBus.connect())
}
private fun shouldStartUpdate(file: VirtualFile): Boolean {
private fun getKtFileToStartConfigurationUpdate(file: VirtualFile): KtFile? {
if (project.isDisposed || !file.isValid || file.isNonScript()) {
return false
return null
}
if (
ApplicationManager.getApplication().isUnitTestMode &&
ApplicationManager.getApplication().isScriptDependenciesUpdaterDisabled == true
) {
return false
return null
}
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return false
return ProjectRootsUtil.isInProjectSource(ktFile, includeScriptsOutsideSourceRoots = true)
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null
if (ProjectRootsUtil.isInProjectSource(ktFile, includeScriptsOutsideSourceRoots = true)) {
return ktFile
}
return null
}
private fun areDependenciesCached(file: VirtualFile): Boolean {
return cache[file] != null || file.scriptDependencies != null
return cache[file] != null || file.scriptDependencies != null || file.scriptCompilationConfiguration != null
}
fun isAsyncDependencyResolver(scriptDef: ScriptDefinition): Boolean =
scriptDef.asLegacyOrNull<KotlinScriptDefinition>()?.dependencyResolver?.let {
it is AsyncDependenciesResolver || it is LegacyResolverWrapper
} ?: false
companion object {
@JvmStatic
fun getInstance(project: Project): ScriptsCompilationConfigurationUpdater =
@@ -194,11 +185,6 @@ class ScriptsCompilationConfigurationUpdater(
fun areDependenciesCached(file: KtFile): Boolean {
return getInstance(file.project).areDependenciesCached(file.virtualFile)
}
fun isAsyncDependencyResolver(file: KtFile): Boolean {
val scriptDefinition = file.virtualFile.findScriptDefinition(file.project) ?: return false
return getInstance(file.project).isAsyncDependencyResolver(scriptDefinition)
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.
*/
@@ -10,13 +10,12 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task
import com.intellij.openapi.progress.util.BackgroundTaskUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import kotlinx.coroutines.Runnable
import org.jetbrains.kotlin.idea.core.script.ScriptsCompilationConfigurationUpdater
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.locks.ReentrantReadWriteLock
@@ -30,14 +29,14 @@ class AsyncScriptDependenciesLoader internal constructor(project: Project) : Scr
private var backgroundTasksQueue: LoaderBackgroundTask? = null
override fun isApplicable(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
): Boolean {
return ScriptsCompilationConfigurationUpdater.getInstance(project).isAsyncDependencyResolver(scriptDefinition)
return isAsyncDependencyResolver(scriptDefinition)
}
override fun loadDependencies(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
) {
lock.write {
@@ -74,20 +73,20 @@ class AsyncScriptDependenciesLoader internal constructor(project: Project) : Scr
return false
}
private fun runDependenciesUpdate(file: VirtualFile) {
val scriptDef = file.findScriptDefinition(project) ?: return
private fun runDependenciesUpdate(file: KtFile) {
val scriptDef = file.findScriptDefinition() ?: return
debug(file) { "start async dependencies loading" }
val result = refineScriptCompilationConfiguration(VirtualFileScriptSource(file), scriptDef, project)
val result = refineScriptCompilationConfiguration(KtFileScriptSource(file), scriptDef, project)
debug(file) { "finish async dependencies loading" }
processRefinedConfiguration(result, file)
processRefinedConfiguration(result, file.originalFile.virtualFile)
}
private inner class LoaderBackgroundTask {
private val sequenceOfFiles: ConcurrentLinkedQueue<VirtualFile> = ConcurrentLinkedQueue()
private val sequenceOfFiles: ConcurrentLinkedQueue<KtFile> = ConcurrentLinkedQueue()
private var forceStop : Boolean = false
private var startedSilently : Boolean = false
@@ -124,7 +123,7 @@ class AsyncScriptDependenciesLoader internal constructor(project: Project) : Scr
}.queue()
}
fun addTask(file: VirtualFile) {
fun addTask(file: KtFile) {
if (sequenceOfFiles.contains(file)) return
debug(file) { "added to update queue" }
@@ -9,39 +9,41 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.scriptCompilationConfiguration
import org.jetbrains.kotlin.idea.core.script.scriptDependencies
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import kotlin.script.experimental.api.asSuccess
// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore
class FromFileAttributeScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) {
override fun isApplicable(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
): Boolean {
return file.scriptDependencies != null || file.scriptCompilationConfiguration != null
}
override fun loadDependencies(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
) {
file.scriptCompilationConfiguration?.let {
ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(VirtualFileScriptSource(file), it).apply {
debug(file) { "refined configuration from fileAttributes = $it" }
val virtualFile = file.originalFile.virtualFile
virtualFile.scriptCompilationConfiguration?.let {
ScriptCompilationConfigurationWrapper.FromCompilationConfiguration(KtFileScriptSource(file), it).apply {
debug(virtualFile) { "refined configuration from fileAttributes = $it" }
}
} ?: file.scriptDependencies?.let {
ScriptCompilationConfigurationWrapper.FromLegacy(VirtualFileScriptSource(file), it, scriptDefinition).apply {
debug(file) { "dependencies from fileAttributes = $it" }
} ?: virtualFile.scriptDependencies?.let {
ScriptCompilationConfigurationWrapper.FromLegacy(KtFileScriptSource(file), it, scriptDefinition).apply {
debug(virtualFile) { "dependencies from fileAttributes = $it" }
}
}?.let {
if (areDependenciesValid(file, it)) {
saveToCache(file, it.asSuccess(), skipSaveToAttributes = true)
if (areDependenciesValid(virtualFile, it)) {
saveToCache(virtualFile, it.asSuccess(), skipSaveToAttributes = true)
} else {
file.scriptCompilationConfiguration = null
file.scriptDependencies = null
virtualFile.scriptCompilationConfiguration = null
virtualFile.scriptDependencies = null
}
}
}
@@ -18,10 +18,13 @@ package org.jetbrains.kotlin.idea.core.script.dependencies
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import com.intellij.psi.ResolveScopeProvider
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager
import org.jetbrains.kotlin.idea.core.script.StandardIdeScriptDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
@@ -33,7 +36,10 @@ class KotlinScriptResolveScopeProvider : ResolveScopeProvider() {
}
override fun getResolveScope(file: VirtualFile, project: Project): GlobalSearchScope? {
val scriptDefinition = file.findScriptDefinition(project)
if (file.fileType != KotlinFileType.INSTANCE) return null
val ktFile = PsiManager.getInstance(project).findFile(file) as? KtFile ?: return null
val scriptDefinition = ktFile.findScriptDefinition()
return when {
scriptDefinition == null -> null
// This is a workaround for completion in scripts and REPL to provide module dependencies
@@ -6,27 +6,32 @@
package org.jetbrains.kotlin.idea.core.script.dependencies
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesManager
import org.jetbrains.kotlin.idea.highlighter.OutsidersPsiFileSupportUtils
import org.jetbrains.kotlin.idea.highlighter.OutsidersPsiFileSupportWrapper
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
class OutsiderFileDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) {
override fun isApplicable(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
): Boolean {
return OutsidersPsiFileSupportWrapper.isOutsiderFile(file)
val virtualFile = file.virtualFile ?: return false
return OutsidersPsiFileSupportWrapper.isOutsiderFile(virtualFile)
}
override fun loadDependencies(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
) {
val fileOrigin = OutsidersPsiFileSupportUtils.getOutsiderFileOrigin(project, file) ?: return
val compilationConfiguration = ScriptDependenciesManager.getInstance(project).getRefinedCompilationConfiguration(fileOrigin) ?: return
saveToCache(file, compilationConfiguration)
val virtualFile = file.virtualFile ?: return
val fileOrigin = OutsidersPsiFileSupportUtils.getOutsiderFileOrigin(project, virtualFile) ?: return
val psiFileOrigin = PsiManager.getInstance(project).findFile(fileOrigin) as? KtFile ?: return
val compilationConfiguration =
ScriptDependenciesManager.getInstance(project).getRefinedCompilationConfiguration(psiFileOrigin) ?: return
saveToCache(virtualFile, compilationConfiguration)
}
override fun shouldShowNotification(): Boolean = false
@@ -15,18 +15,22 @@ import com.intellij.openapi.util.EmptyRunnable
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.*
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.LegacyResolverWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationResult
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import org.jetbrains.kotlin.scripting.resolve.ScriptReportSink
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.valueOrNull
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
// TODO: rename and provide alias for compatibility - this is not only about dependencies anymore
abstract class ScriptDependenciesLoader(protected val project: Project) {
abstract fun isApplicable(file: VirtualFile, scriptDefinition: ScriptDefinition): Boolean
abstract fun loadDependencies(file: VirtualFile, scriptDefinition: ScriptDefinition)
abstract fun isApplicable(file: KtFile, scriptDefinition: ScriptDefinition): Boolean
abstract fun loadDependencies(file: KtFile, scriptDefinition: ScriptDefinition)
protected abstract fun shouldShowNotification(): Boolean
@@ -138,13 +142,23 @@ abstract class ScriptDependenciesLoader(protected val project: Project) {
return true
}
companion object {
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.script")
protected fun isAsyncDependencyResolver(scriptDef: ScriptDefinition): Boolean =
scriptDef.asLegacyOrNull<KotlinScriptDefinition>()?.dependencyResolver?.let {
it is AsyncDependenciesResolver || it is LegacyResolverWrapper
} ?: false
internal fun debug(file: VirtualFile? = null, message: () -> String) {
if (LOG.isDebugEnabled) {
LOG.debug("[KOTLIN SCRIPT] " + (file?.let { "file = ${file.path}, " } ?: "") + message())
}
}
}
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.script")
internal fun debug(file: VirtualFile? = null, message: () -> String) {
if (LOG.isDebugEnabled) {
LOG.debug("[KOTLIN SCRIPT] ${file?.path} " + message())
}
}
internal fun debug(file: KtFile, message: () -> String) {
if (LOG.isDebugEnabled) {
LOG.debug("[KOTLIN SCRIPT] ${file.originalFile.virtualFile?.path} " + message())
}
}
@@ -6,28 +6,27 @@
package org.jetbrains.kotlin.idea.core.script.dependencies
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.ScriptsCompilationConfigurationUpdater
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.VirtualFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.KtFileScriptSource
import org.jetbrains.kotlin.scripting.resolve.refineScriptCompilationConfiguration
class SyncScriptDependenciesLoader(project: Project) : ScriptDependenciesLoader(project) {
override fun isApplicable(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
): Boolean {
return !ScriptsCompilationConfigurationUpdater.getInstance(project).isAsyncDependencyResolver(scriptDefinition)
return !isAsyncDependencyResolver(scriptDefinition)
}
override fun loadDependencies(
file: VirtualFile,
file: KtFile,
scriptDefinition: ScriptDefinition
) {
debug(file) { "start sync dependencies loading" }
val result = refineScriptCompilationConfiguration(VirtualFileScriptSource(file), scriptDefinition, project)
val result = refineScriptCompilationConfiguration(KtFileScriptSource(file), scriptDefinition, project)
debug(file) { "finish sync dependencies loading" }
processRefinedConfiguration(result, file)
processRefinedConfiguration(result, file.originalFile.virtualFile)
}
override fun shouldShowNotification(): Boolean = false