Scripting: use VFS events instead of document changes to track changes outside of IDE (KT-35557)

^KT-35557 Fixed
This commit is contained in:
Natalia Selezneva
2019-12-19 11:09:23 +03:00
parent 6bf0e4b1a9
commit a653feb2dc
5 changed files with 25 additions and 164 deletions
@@ -6,15 +6,14 @@
package org.jetbrains.kotlin.idea.scripting.gradle package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.components.* import com.intellij.openapi.components.*
import com.intellij.openapi.externalSystem.service.project.autoimport.ConfigurationFileCrcFactory
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile 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.util.PathUtil import com.intellij.util.PathUtil
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
import java.nio.file.Paths
@State( @State(
name = "KotlinBuildScriptsModificationInfo", name = "KotlinBuildScriptsModificationInfo",
@@ -23,21 +22,22 @@ import java.nio.file.Paths
class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent<GradleScriptInputsWatcher.Storage> { class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent<GradleScriptInputsWatcher.Storage> {
private var storage = Storage() private var storage = Storage()
init { fun startWatching() {
initStorage(project) project.messageBus.connect().subscribe(
} VirtualFileManager.VFS_CHANGES,
object : BulkFileListener {
override fun after(events: List<VFileEvent>) {
if (project.isDisposed) return
private fun initStorage(project: Project) { val files = getAffectedGradleProjectFiles(project)
val localSettings = GradleLocalSettings.getInstance(project) for (event in events) {
localSettings.externalConfigModificationStamps.forEach { (path, stamp) -> val file = event.file ?: return
val file = VfsUtil.findFile(Paths.get(path), true) if (isInAffectedGradleProjectFiles(files, file)) {
if (file != null && !file.isDirectory) { storage.fileChanged(file, file.timeStamp)
val calculateCrc = ConfigurationFileCrcFactory(file).create() }
if (calculateCrc != stamp) { }
storage.fileChanged(file, file.timeStamp)
} }
} })
}
} }
fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean { fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean {
@@ -1,111 +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.scripting.gradle
import com.intellij.openapi.components.*
import com.intellij.openapi.externalSystem.service.project.autoimport.ConfigurationFileCrcFactory
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.PathUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
import java.nio.file.Paths
@State(
name = "KotlinBuildScriptsModificationInfo",
storages = [Storage(StoragePathMacros.CACHE_FILE)]
)
class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent<GradleScriptInputsWatcher.Storage> {
private var storage = Storage()
init {
initStorage(project)
}
private fun initStorage(project: Project) {
val localSettings = GradleLocalSettings.getInstance(project)
localSettings.externalConfigModificationStamps.forEach { (path, stamp) ->
val file = VfsUtil.findFile(Paths.get(path), true)
if (file != null && !file.isDirectory) {
val calculateCrc = ConfigurationFileCrcFactory(project, file).create()
if (calculateCrc != stamp) {
storage.fileChanged(file, file.timeStamp)
}
}
}
}
fun lastModifiedFileTimeStamp(file: VirtualFile): Long = storage.lastModifiedTimeStampExcept(file)
fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean {
return lastModifiedFileTimeStamp(file) < timeStamp
}
class Storage(
private var lastModifiedTS: Long = 0,
private var lastModifiedFiles: MutableSet<String> = hashSetOf(),
private var previousModifiedTS: Long = 0,
private var previousModifiedFiles: MutableSet<String> = hashSetOf()
) {
fun lastModifiedTimeStampExcept(file: VirtualFile): Long {
val fileId = getFileId(file)
synchronized(this) {
if (lastModifiedFiles.contains(fileId) && lastModifiedFiles.size == 1) {
return previousModifiedTS
}
return lastModifiedTS
}
}
fun fileChanged(file: VirtualFile, ts: Long) {
val fileId = getFileId(file)
synchronized(this) {
when {
ts > lastModifiedTS -> {
previousModifiedFiles = lastModifiedFiles
previousModifiedTS = lastModifiedTS
lastModifiedFiles = hashSetOf(fileId)
lastModifiedTS = ts
}
ts == lastModifiedTS -> {
lastModifiedFiles.add(fileId)
}
ts == previousModifiedTS -> {
previousModifiedFiles.add(fileId)
}
else -> {}
}
}
}
private fun getFileId(file: VirtualFile): String {
val canonized = PathUtil.getCanonicalPath(file.path)
return FileUtil.toSystemIndependentName(canonized)
}
}
override fun getState(): Storage {
return storage
}
override fun loadState(state: Storage) {
this.storage = state
}
@TestOnly
fun clearAndRefillState() {
loadState(project.service<GradleScriptInputsWatcher>().state)
}
@TestOnly
fun fileChanged(file: VirtualFile, ts: Long) {
storage.fileChanged(file, ts)
}
}
@@ -6,22 +6,18 @@
package org.jetbrains.kotlin.idea.scripting.gradle package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.io.systemIndependentPath
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCacheScope
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
import org.jetbrains.plugins.gradle.GradleManager
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
import org.jetbrains.plugins.gradle.util.GradleConstants
open class GradleScriptListener(project: Project) : ScriptChangeListener(project) { open class GradleScriptListener(project: Project) : ScriptChangeListener(project) {
init {
// start GradleScriptInputsWatcher to track changes in gradle-configuration related files
project.service<GradleScriptInputsWatcher>().startWatching()
}
override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) { override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) {
if (!isGradleKotlinScript(vFile)) return
if (useScriptConfigurationFromImportOnly()) { if (useScriptConfigurationFromImportOnly()) {
// do nothing // do nothing
} else { } else {
@@ -36,10 +32,11 @@ open class GradleScriptListener(project: Project) : ScriptChangeListener(project
// *.gradle.kts file was changed // *.gradle.kts file was changed
updater.ensureUpToDatedConfigurationSuggested(file) updater.ensureUpToDatedConfigurationSuggested(file)
} }
project.service<GradleScriptInputsWatcher>().fileChanged(vFile, vFile.timeStamp)
} }
override fun isApplicable(vFile: VirtualFile): Boolean { override fun isApplicable(vFile: VirtualFile): Boolean {
if (!isGradleKotlinScript(vFile)) return false
return isInAffectedGradleProjectFiles(project, vFile) return isInAffectedGradleProjectFiles(project, vFile)
} }
} }
@@ -5,14 +5,10 @@
package org.jetbrains.kotlin.idea.scripting.gradle package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
import org.jetbrains.kotlin.idea.core.script.isScriptChangesNotifierDisabled
import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationLoadingTest import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationLoadingTest
import org.jetbrains.kotlin.idea.script.addExtensionPointInTest
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.plugins.gradle.util.GradleConstants import org.jetbrains.plugins.gradle.util.GradleConstants
import java.io.File import java.io.File
@@ -28,17 +24,11 @@ class GradleScriptInputsWatcherTest : AbstractScriptConfigurationLoadingTest() {
override fun setUp() { override fun setUp() {
super.setUp() super.setUp()
ApplicationManager.getApplication().isScriptChangesNotifierDisabled = false // should be initialized explicitly because we do not have a real Gradle Project in this test
project.service<GradleScriptInputsWatcher>().startWatching()
} }
override fun setUpTestProject() { override fun setUpTestProject() {
addExtensionPointInTest(
ScriptChangeListener.LISTENER,
project,
TestGradleScriptListener(project),
testRootDisposable
)
val rootDir = "idea/testData/script/definition/loading/gradle/" val rootDir = "idea/testData/script/definition/loading/gradle/"
val settings: KtFile = addFileToProject(rootDir + GradleConstants.KOTLIN_DSL_SETTINGS_FILE_NAME) val settings: KtFile = addFileToProject(rootDir + GradleConstants.KOTLIN_DSL_SETTINGS_FILE_NAME)
@@ -1,15 +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.scripting.gradle
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
internal class TestGradleScriptListener(project: Project) : GradleScriptListener(project) {
override fun isApplicable(vFile: VirtualFile): Boolean {
return true
}
}