IDEA-CR-57413: [auto-import] added settings to disable auto reload of external changes

(cherry picked from commit c68f8ee33e515b6e7adf46708fbd8a6a65ea1ff6)

GitOrigin-RevId: b3ce1d7b776b2af12a00f588c9375afb56ca68fe
This commit is contained in:
Sergei Vorobyov
2020-01-14 20:59:20 +03:00
committed by intellij-monorepo-bot
parent d8a295131e
commit 502032aa69
9 changed files with 152 additions and 23 deletions

View File

@@ -62,8 +62,9 @@ action.unignore.external.projects.description=Unignore selected {0} {1,choice,1#
action.open.config.text=Open {0} Config
action.open.config.description=Opens the {0} project file in the editor
action.refresh.project.auto.text=Auto-Import
action.refresh.project.auto.description=Enable/disable automatic project importing on changes in build script files
action.refresh.project.auto.text=Auto-reload external changes
action.refresh.project.auto.description.enable=Enable automatic import on VCS updates and external changes
action.refresh.project.auto.description.disable=Disable automatic import on VCS updates and external changes
action.open.settings.text={0} Settings
action.open.settings.description=Edit {0} settings for the current project

View File

@@ -8,6 +8,11 @@ import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental
interface ExternalSystemProjectTracker {
/**
* Enables/disables auto reload external changes for all projects
*/
var isAutoReloadExternalChanges: Boolean
/**
* Starts tracking of project settings that will be defined by [projectAware]
*/

View File

@@ -13,7 +13,7 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.externalSystem.autoimport.ExternalSystemRefreshStatus.SUCCESS
import com.intellij.openapi.externalSystem.autoimport.ProjectStatus.ModificationType
import com.intellij.openapi.externalSystem.autoimport.ProjectStatus.ModificationType.EXTERNAL
import com.intellij.openapi.externalSystem.autoimport.ProjectStatus.ModificationType.INTERNAL
import com.intellij.openapi.externalSystem.model.ProjectSystemId
import com.intellij.openapi.observable.operations.AnonymousParallelOperationTrace
import com.intellij.openapi.observable.operations.CompoundParallelOperationTrace
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
@@ -39,10 +39,13 @@ class AutoImportProjectTracker(private val project: Project) : ExternalSystemPro
private val projectStates = ConcurrentHashMap<State.Id, State.Project>()
private val projectDataMap = ConcurrentHashMap<ExternalSystemProjectId, ProjectData>()
private val isDisabled = AtomicBooleanProperty(ApplicationManager.getApplication().isUnitTestMode)
private val autoReloadExternalChangesProperty = AtomicBooleanProperty(true)
private val projectChangeOperation = AnonymousParallelOperationTrace(debugName = "Project change operation")
private val projectRefreshOperation = CompoundParallelOperationTrace<String>(debugName = "Project refresh operation")
private val dispatcher = MergingUpdateQueue("project tracker", AUTO_REPARSE_DELAY, false, null, project)
override var isAutoReloadExternalChanges by autoReloadExternalChangesProperty
private fun createProjectChangesListener() =
object : ProjectBatchFileChangeListener(project) {
override fun batchChangeStarted(activityName: String?) =
@@ -89,10 +92,11 @@ class AutoImportProjectTracker(private val project: Project) : ExternalSystemPro
LOG.debug("Schedule change processing")
dispatcher.queue(object : Update("notify") {
override fun run() {
when (getModificationType()) {
INTERNAL -> updateProjectNotification()
EXTERNAL -> refreshProject()
null -> updateProjectNotification()
if (getModificationType() == EXTERNAL && isAutoReloadExternalChanges) {
refreshProject()
}
else {
updateProjectNotification()
}
}
})
@@ -102,9 +106,11 @@ class AutoImportProjectTracker(private val project: Project) : ExternalSystemPro
LOG.debug("Incremental project refresh")
if (isDisabled.get() || Registry.`is`("external.system.auto.import.disabled")) return
if (!projectChangeOperation.isOperationCompleted()) return
var isSkippedProjectRefresh = true
for (projectData in projectDataMap.values) {
val projectId = projectData.projectAware.projectId.readableName
if (!projectData.isUpToDate()) {
isSkippedProjectRefresh = false
LOG.debug("$projectId: Project refresh")
projectData.projectAware.refreshProject()
}
@@ -112,6 +118,9 @@ class AutoImportProjectTracker(private val project: Project) : ExternalSystemPro
LOG.debug("$projectId: Skip project refresh")
}
}
if (isSkippedProjectRefresh) {
updateProjectNotification()
}
}
private fun updateProjectNotification() {
@@ -175,13 +184,14 @@ class AutoImportProjectTracker(private val project: Project) : ExternalSystemPro
}
override fun getState(): State {
val projectSettingsTrackerStates = projectDataMap.values
.map { it.projectAware.projectId.getState() to it.getState() }
val projectSettingsTrackerStates = projectDataMap.asSequence()
.map { (id, data) -> id.getState() to data.getState() }
.toMap()
return State(projectSettingsTrackerStates)
return State(isAutoReloadExternalChanges, projectSettingsTrackerStates)
}
override fun loadState(state: State) {
isAutoReloadExternalChanges = state.isAutoReloadExternalChanges
projectStates.putAll(state.projectSettingsTrackerStates)
projectDataMap.forEach { (id, data) -> loadState(id, data) }
}
@@ -190,8 +200,8 @@ class AutoImportProjectTracker(private val project: Project) : ExternalSystemPro
val projectState = projectStates.remove(projectId.getState())
val settingsTrackerState = projectState?.settingsTracker
if (settingsTrackerState == null || projectState.isDirty) {
projectData.status.markDirty(currentTime())
scheduleProjectRefresh()
projectData.status.markDirty(currentTime(), EXTERNAL)
scheduleChangeProcessing()
return
}
projectData.settingsTracker.loadState(settingsTrackerState)
@@ -225,12 +235,14 @@ class AutoImportProjectTracker(private val project: Project) : ExternalSystemPro
projectChangeOperation.beforeOperation { notificationAware.notificationExpire() }
projectChangeOperation.afterOperation { scheduleChangeProcessing() }
projectChangeOperation.afterOperation { LOG.debug("Project change finished") }
isDisabled.afterReset { scheduleProjectRefresh() }
autoReloadExternalChangesProperty.afterSet { scheduleProjectRefresh() }
}
private fun ProjectData.getState() = State.Project(status.isDirty(), settingsTracker.getState())
private fun ExternalSystemProjectId.getState() = State.Id(systemId.id, externalProjectPath)
private fun ProjectSystemId.getState() = id
private fun ExternalSystemProjectId.getState() = State.Id(systemId.getState(), externalProjectPath)
private data class ProjectData(
val status: ProjectStatus,
@@ -240,10 +252,21 @@ class AutoImportProjectTracker(private val project: Project) : ExternalSystemPro
) {
fun isUpToDate() = status.isUpToDate() && settingsTracker.isUpToDate()
fun getModificationType() = settingsTracker.getModificationType()
fun getModificationType(): ModificationType? {
val trackerModificationType = status.getModificationType()
val settingsTrackerModificationType = settingsTracker.getModificationType()
return when {
trackerModificationType == null -> settingsTrackerModificationType
settingsTrackerModificationType == null -> trackerModificationType
else -> settingsTrackerModificationType.merge(trackerModificationType)
}
}
}
data class State(var projectSettingsTrackerStates: Map<Id, Project> = emptyMap()) {
data class State(
var isAutoReloadExternalChanges: Boolean = true,
var projectSettingsTrackerStates: Map<Id, Project> = emptyMap()
) {
data class Id(var systemId: String? = null, var externalProjectPath: String? = null)
data class Project(
var isDirty: Boolean = false,

View File

@@ -107,10 +107,10 @@ class ProjectSettingsTracker(
submitSettingsFilesRefresh {
submitSettingsFilesCRCCalculation { newSettingsFilesCRC ->
when (hasChanges(newSettingsFilesCRC)) {
true -> status.markDirty(currentTime())
true -> status.markDirty(currentTime(), EXTERNAL)
else -> status.markReverted(currentTime())
}
projectTracker.scheduleProjectRefresh()
projectTracker.scheduleChangeProcessing()
}
}
}
@@ -118,7 +118,7 @@ class ProjectSettingsTracker(
fun getState() = State(status.isDirty(), settingsFilesCRC.get().toMap())
fun loadState(state: State) {
if (state.isDirty) status.markDirty(currentTime())
if (state.isDirty) status.markDirty(currentTime(), EXTERNAL)
settingsFilesCRC.set(state.settingsFiles.toMap())
}

View File

@@ -0,0 +1,35 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.externalSystem.autoimport
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.ToggleAction
import com.intellij.openapi.externalSystem.util.ExternalSystemBundle
import com.intellij.openapi.project.DumbAware
class ToggleProjectRefreshAction : ToggleAction(), DumbAware {
override fun update(e: AnActionEvent) {
super.update(e)
e.presentation.description = when (isSelected(e)) {
true -> ExternalSystemBundle.message("action.refresh.project.auto.description.disable")
else -> ExternalSystemBundle.message("action.refresh.project.auto.description.enable")
}
}
override fun isSelected(e: AnActionEvent): Boolean {
val project = e.project ?: return false
val projectTracker = ExternalSystemProjectTracker.getInstance(project)
return projectTracker.isAutoReloadExternalChanges
}
override fun setSelected(e: AnActionEvent, state: Boolean) {
val project = e.project ?: return
val projectTracker = ExternalSystemProjectTracker.getInstance(project)
projectTracker.isAutoReloadExternalChanges = state
}
init {
templatePresentation.icon = null
templatePresentation.text = ExternalSystemBundle.message("action.refresh.project.auto.text")
templatePresentation.description = ExternalSystemBundle.message("action.refresh.project.auto.description.disable")
}
}

View File

@@ -2,7 +2,6 @@
package com.intellij.openapi.externalSystem.service.project.autoimport
import com.intellij.openapi.Disposable
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.externalSystem.ExternalSystemAutoImportAware
import com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectAware
import com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectId
@@ -26,8 +25,6 @@ class ProjectAware(
private val autoImportAware: ExternalSystemAutoImportAware
) : ExternalSystemProjectAware {
private val LOG = Logger.getInstance("#com.intellij.openapi.externalSystem.autoimport")
private val systemId = projectId.systemId
private val projectPath = projectId.externalProjectPath

View File

@@ -498,4 +498,41 @@ class AutoImportTest : AutoImportTestCase() {
assertProjectAware(projectAware, refresh = 3, event = "modification during project refresh")
assertNotificationAware(projectId, event = "modification during project refresh")
}
fun `test disabling of auto-import`() {
var state = simpleTest("settings.groovy") { settingsFile ->
assertState(refresh = 1, enabled = true, notified = false, event = "register project without cache")
disableAutoReloadExternalChanges()
assertState(refresh = 1, enabled = false, notified = false, event = "disable project auto-import")
settingsFile.replaceContentInIoFile("println 'hello'")
assertState(refresh = 1, enabled = false, notified = true, event = "modification with disabled auto-import")
}
state = simpleTest("settings.groovy", state = state) { settingsFile ->
// Open modified project with disabled auto-import for external changes
assertState(refresh = 0, enabled = false, notified = true, event = "register modified project")
refreshProject()
assertState(refresh = 1, enabled = false, notified = false, event = "refresh project")
// Checkout git branch, that has additional linked project
withLinkedProject("module/settings.groovy") { moduleSettingsFile ->
assertState(refresh = 0, enabled = false, notified = true, event = "register project without cache with disabled auto-import")
moduleSettingsFile.replaceContentInIoFile("println 'hello'")
assertState(refresh = 0, enabled = false, notified = true, event = "modification with disabled auto-import")
}
assertState(refresh = 1, enabled = false, notified = false, event = "remove modified linked project")
enableAutoReloadExternalChanges()
assertState(refresh = 1, enabled = true, notified = false, event = "enable auto-import for project without modifications")
disableAutoReloadExternalChanges()
assertState(refresh = 1, enabled = false, notified = false, event = "disable project auto-import")
settingsFile.replaceStringInIoFile("hello", "hi")
assertState(refresh = 1, enabled = false, notified = true, event = "modification with disabled auto-import")
enableAutoReloadExternalChanges()
assertState(refresh = 2, enabled = true, notified = false, event = "enable auto-import for modified project")
}
simpleTest("settings.groovy", state = state) {
assertState(refresh = 0, enabled = true, notified = false, event = "register project with correct cache")
}
}
}

View File

@@ -171,6 +171,14 @@ abstract class AutoImportTestCase : ExternalSystemTestCase() {
private fun loadState(state: AutoImportProjectTracker.State) = projectTracker.loadState(state)
protected fun enableAutoReloadExternalChanges() {
projectTracker.isAutoReloadExternalChanges = true
}
protected fun disableAutoReloadExternalChanges() {
projectTracker.isAutoReloadExternalChanges = false
}
protected fun initialize() = projectTracker.initializeComponent()
protected fun getState() = projectTracker.state
@@ -194,11 +202,18 @@ abstract class AutoImportTestCase : ExternalSystemTestCase() {
assertEquals("$message on $event", expected, actual)
}
protected fun assertProjectTracker(isAutoReload: Boolean, event: String) {
val message = when (isAutoReload) {
true -> "Auto reload must be enabled"
false -> "Auto reload must be disabled"
}
assertEquals("$message on $event", isAutoReload, projectTracker.isAutoReloadExternalChanges)
}
protected fun assertNotificationAware(vararg projects: ExternalSystemProjectId, event: String) {
val message = when (projects.isEmpty()) {
true -> "Notification must be expired"
else -> "Notification must be notified for $projects"
else -> "Notification must be notified"
}
assertEquals("$message on $event", projects.toSet(), notificationAware.getProjectsWithNotification())
}
@@ -266,12 +281,24 @@ abstract class AutoImportTestCase : ExternalSystemTestCase() {
projectAware.refreshStatus = status
}
fun withLinkedProject(fileRelativePath: String, test: SimpleTestBench.(VirtualFile) -> Unit) {
val projectId = ExternalSystemProjectId(projectAware.projectId.systemId, "$projectPath/$name")
val projectAware = MockProjectAware(projectId)
register(projectAware)
val file = findOrCreateVirtualFile("$name/$fileRelativePath")
projectAware.settingsFiles.add(file.path)
SimpleTestBench(projectAware).test(file)
remove(projectId)
}
fun assertState(refresh: Int? = null,
subscribe: Int? = null,
unsubscribe: Int? = null,
enabled: Boolean = true,
notified: Boolean,
event: String) {
assertProjectAware(projectAware, refresh, subscribe, unsubscribe, event)
assertProjectTracker(enabled, event = event)
when (notified) {
true -> assertNotificationAware(projectAware.projectId, event = event)
else -> assertNotificationAware(event = event)

View File

@@ -20,6 +20,10 @@
icon="AllIcons.General.Settings">
</action>
<action id="ExternalSystem.ToggleProjectRefresh"
class="com.intellij.openapi.externalSystem.autoimport.ToggleProjectRefreshAction">
</action>
<action id="ExternalSystem.OpenTasksActivationManager"
class="com.intellij.openapi.externalSystem.action.OpenTasksActivationManagerAction">
</action>