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
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.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.newvfs.BulkFileListener
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
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",
@@ -23,21 +22,22 @@ import java.nio.file.Paths
class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent<GradleScriptInputsWatcher.Storage> {
private var storage = Storage()
init {
initStorage(project)
}
fun startWatching() {
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 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(file).create()
if (calculateCrc != stamp) {
storage.fileChanged(file, file.timeStamp)
val files = getAffectedGradleProjectFiles(project)
for (event in events) {
val file = event.file ?: return
if (isInAffectedGradleProjectFiles(files, file)) {
storage.fileChanged(file, file.timeStamp)
}
}
}
}
}
})
}
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
import com.intellij.openapi.components.service
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.project.Project
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.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) {
init {
// start GradleScriptInputsWatcher to track changes in gradle-configuration related files
project.service<GradleScriptInputsWatcher>().startWatching()
}
override fun editorActivated(vFile: VirtualFile, updater: ScriptConfigurationUpdater) {
if (!isGradleKotlinScript(vFile)) return
if (useScriptConfigurationFromImportOnly()) {
// do nothing
} else {
@@ -36,10 +32,11 @@ open class GradleScriptListener(project: Project) : ScriptChangeListener(project
// *.gradle.kts file was changed
updater.ensureUpToDatedConfigurationSuggested(file)
}
project.service<GradleScriptInputsWatcher>().fileChanged(vFile, vFile.timeStamp)
}
override fun isApplicable(vFile: VirtualFile): Boolean {
if (!isGradleKotlinScript(vFile)) return false
return isInAffectedGradleProjectFiles(project, vFile)
}
}
@@ -5,14 +5,10 @@
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.psi.PsiFile
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.addExtensionPointInTest
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.plugins.gradle.util.GradleConstants
import java.io.File
@@ -28,17 +24,11 @@ class GradleScriptInputsWatcherTest : AbstractScriptConfigurationLoadingTest() {
override fun 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() {
addExtensionPointInTest(
ScriptChangeListener.LISTENER,
project,
TestGradleScriptListener(project),
testRootDisposable
)
val rootDir = "idea/testData/script/definition/loading/gradle/"
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
}
}