Add script files changes monitoring, logic for initialization during index rebuilding

This commit is contained in:
Ilya Chernikov
2016-06-16 10:17:05 +02:00
committed by Pavel V. Talanov
parent 8dac72f465
commit 5bea5bcc37
3 changed files with 74 additions and 5 deletions
@@ -69,6 +69,13 @@ class KotlinScriptExternalDependenciesUnion(val dependencies: Iterable<KotlinScr
override val sources: Iterable<File> get() = dependencies.flatMap { it.sources }
}
fun Iterable<File>.isSameClasspathAs(other: Iterable<File>): Boolean {
val c1 = map { it.canonicalPath }.toHashSet()
val c2 = other.map { it.canonicalPath }.toHashSet()
return c1.size == c2.size &&
c1.zip(c2).all { it.first == it.second }
}
data class ScriptParameter(val name: Name, val type: KotlinType)
fun <TF> getFileName(file: TF): String = when (file) {
@@ -49,6 +49,7 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
}
}
// optimized for initial caching, additional handling of possible duplicates to save a call to distinct
fun <TF> cacheExternalImports(files: Iterable<TF>): Unit = cacheLock.write {
val uncached = hashSetOf<String>()
files.forEach { file ->
@@ -71,6 +72,36 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
}
}
// optimized for update, no special duplicates handling
fun <TF: Any> updateExternalImportsCache(files: Iterable<TF>): Iterable<TF> = cacheLock.write {
files.mapNotNull { file ->
val path = getFilePath(file)
val scriptDef = scriptDefinitionProvider.findScriptDefinition(file)
if (scriptDef != null) {
val deps = scriptDef.getDependenciesFor(file, project)
val oldDeps = cache[path]
when {
deps != null && (oldDeps == null ||
!deps.classpath.isSameClasspathAs(oldDeps.classpath) || !deps.sources.isSameClasspathAs(oldDeps.sources)) -> {
// changed or new
cache.put(path, deps)
cacheOfNulls.remove(path)
file
}
deps != null -> {
// same as before
null
}
else -> {
if (cache.remove(path) != null || cacheOfNulls.remove(path)) file // cleared
else null // same as before
}
}
}
else null // not a script
}
}
fun invalidateCaches() {
cacheLock.write {
cache.keys.toList().apply {
@@ -17,24 +17,33 @@
package org.jetbrains.kotlin.idea.core.script
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
import com.intellij.openapi.util.EmptyRunnable
import com.intellij.openapi.vfs.StandardFileSystems
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.VFileEvent
import com.intellij.psi.search.FileTypeIndex
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.indexing.IndexableSetContributor
import com.intellij.util.io.URLUtil
import org.jetbrains.kotlin.idea.caches.resolve.FileLibraryScope
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.script.*
import org.jetbrains.kotlin.utils.PathUtil
import java.io.File
import java.io.FileNotFoundException
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
class KotlinScriptConfigurationManager(
private val project: Project,
private val dumbService: DumbService,
private val scriptDefinitionProvider: KotlinScriptDefinitionProvider,
private val scriptExternalImportsProvider: KotlinScriptExternalImportsProvider?
) {
@@ -47,11 +56,28 @@ class KotlinScriptConfigurationManager(
reloadScriptDefinitions()
// TODO: sort out read/write action business and if possible make it lazy (e.g. move to getAllScriptsClasspath)
runReadAction { cacheAllScriptsExtraImports() }
project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener.Adapter() {
override fun after(events: List<VFileEvent>) {
val isChanged = scriptExternalImportsProvider?.updateExternalImportsCache( events.mapNotNull { it.file })?.any() ?: false
if (isChanged) {
// TODO: consider more fine-grained update
cacheLock.write {
allScriptsClasspathCache = null
}
notifyRootsChanged()
}
}
})
}
private var allScriptsClasspathCache: List<VirtualFile>? = null
private val cacheLock = ReentrantReadWriteLock()
private fun notifyRootsChanged() {
runWriteAction { ProjectRootManagerEx.getInstanceEx(project)?.makeRootsChange(EmptyRunnable.getInstance(), false, true) }
}
fun getScriptClasspath(file: VirtualFile): List<VirtualFile> =
scriptExternalImportsProvider
?.getExternalImports(file)
@@ -61,12 +87,17 @@ class KotlinScriptConfigurationManager(
fun getAllScriptsClasspath(): List<VirtualFile> = cacheLock.read {
if (allScriptsClasspathCache == null) {
allScriptsClasspathCache =
(scriptExternalImportsProvider?.getKnownCombinedClasspath() ?: emptyList())
.distinct()
.mapNotNull { it.classpathEntryToVfs() }
dumbService.runWhenSmart {
cacheLock.write {
allScriptsClasspathCache =
(scriptExternalImportsProvider?.getKnownCombinedClasspath() ?: emptyList())
.distinct()
.mapNotNull { it.classpathEntryToVfs() }
}
notifyRootsChanged()
}
}
return allScriptsClasspathCache!!
return allScriptsClasspathCache ?: emptyList()
}
private fun File.classpathEntryToVfs(): VirtualFile =