Further reduce number of calls to getConfigurableModulesWithKotlinFiles

Also handle INRE and requeue check
This commit is contained in:
Dmitry Jemerov
2018-03-09 12:40:00 +01:00
parent a29d693dc5
commit b4fb0e0305
4 changed files with 79 additions and 37 deletions
@@ -191,10 +191,10 @@ class GradleConfiguratorTest : GradleImportingTestCase() {
runReadAction {
val configurator = findGradleModuleConfigurator()
val (modules, ableToRunConfigurators) = getConfigurationPossibilities(myProject)
val (modules, ableToRunConfigurators) = getConfigurationPossibilitiesForConfigureNotification(myProject)
assertTrue(ableToRunConfigurators.any { it is KotlinGradleModuleConfigurator })
assertTrue(ableToRunConfigurators.any { it is KotlinJsGradleModuleConfigurator })
val moduleNames = modules.map { it.name }
val moduleNames = modules.map { it.baseModule.name }
assertSameElements(moduleNames, "app")
val moduleNamesFromConfigurator = getCanBeConfiguredModules(myProject, configurator).map { it.name }
@@ -232,7 +232,7 @@ class GradleConfiguratorTest : GradleImportingTestCase() {
importProject()
runReadAction {
assertEmpty(getConfigurationPossibilities(myProject).first)
assertEmpty(getConfigurationPossibilitiesForConfigureNotification(myProject).first)
}
}
@@ -263,7 +263,7 @@ class GradleConfiguratorTest : GradleImportingTestCase() {
importProject()
runReadAction {
assertEmpty(getConfigurationPossibilities(myProject).first)
assertEmpty(getConfigurationPossibilitiesForConfigureNotification(myProject).first)
}
}
@@ -91,6 +91,10 @@ fun isModuleConfigured(moduleSourceRootGroup: ModuleSourceRootGroup): Boolean {
}
}
/**
* Returns a list of modules which contain sources in Kotlin.
* Note that this method is expensive and should not be called more often than strictly necessary.
*/
fun getModulesWithKotlinFiles(project: Project): Collection<Module> {
if (!runReadAction {
!project.isDisposed && FileTypeIndex.containsFileOfType (KotlinFileType.INSTANCE, GlobalSearchScope.projectScope(project))
@@ -106,6 +110,10 @@ fun getModulesWithKotlinFiles(project: Project): Collection<Module> {
}
}
/**
* Returns a list of modules which contain sources in Kotlin, grouped by base module.
* Note that this method is expensive and should not be called more often than strictly necessary.
*/
fun getConfigurableModulesWithKotlinFiles(project: Project): List<ModuleSourceRootGroup> {
val modules = getModulesWithKotlinFiles(project)
if (modules.isEmpty()) return emptyList()
@@ -121,17 +129,13 @@ fun showConfigureKotlinNotificationIfNeeded(module: Module) {
}
fun showConfigureKotlinNotificationIfNeeded(project: Project, excludeModules: List<Module> = emptyList()) {
val notificationString = DumbService.getInstance(project).runReadActionInSmartMode(Computable {
val modules = getConfigurableModulesWithKotlinFiles(project).exclude(excludeModules)
if (modules.none(::isNotConfiguredNotificationRequired))
null
else
ConfigureKotlinNotification.getNotificationString(project, excludeModules)
val notificationState = DumbService.getInstance(project).runReadActionInSmartMode(Computable {
ConfigureKotlinNotification.getNotificationState(project, excludeModules)
})
if (notificationString != null) {
if (notificationState != null) {
ApplicationManager.getApplication().invokeLater {
ConfigureKotlinNotificationManager.notify(project, ConfigureKotlinNotification(project, excludeModules, notificationString))
ConfigureKotlinNotificationManager.notify(project, ConfigureKotlinNotification(project, excludeModules, notificationState))
}
}
}
@@ -177,33 +181,43 @@ private fun KotlinProjectConfigurator.canConfigure(moduleSourceRootGroup: Module
getStatus(moduleSourceRootGroup) == ConfigureKotlinStatus.CAN_BE_CONFIGURED &&
(allConfigurators().toList() - this).none { it.getStatus(moduleSourceRootGroup) == ConfigureKotlinStatus.CONFIGURED }
/**
* Returns a list of modules which contain sources in Kotlin and for which it's possible to run the given configurator.
* Note that this method is expensive and should not be called more often than strictly necessary.
*/
fun getCanBeConfiguredModulesWithKotlinFiles(project: Project, configurator: KotlinProjectConfigurator): List<Module> {
val modules = getConfigurableModulesWithKotlinFiles(project)
return modules.filter { configurator.getStatus(it) == ConfigureKotlinStatus.CAN_BE_CONFIGURED }.map { it.baseModule }
}
fun getConfigurationPossibilities(
fun getConfigurationPossibilitiesForConfigureNotification(
project: Project,
excludeModules: Collection<Module> = emptyList()
): Pair<Collection<Module>, Collection<KotlinProjectConfigurator>> {
): Pair<Collection<ModuleSourceRootGroup>, Collection<KotlinProjectConfigurator>> {
val modulesWithKotlinFiles = getConfigurableModulesWithKotlinFiles(project).exclude(excludeModules)
val configurators = allConfigurators()
val runnableConfigurators = mutableSetOf<KotlinProjectConfigurator>()
val configurableModules = mutableListOf<Module>()
val configurableModules = mutableListOf<ModuleSourceRootGroup>()
// We need to return all modules for which at least one configurator is applicable, as well as all configurators which
// are applicable for at least one module. At the same time we want to call getStatus() only once for each module/configurator pair.
for (moduleSourceRootGroup in modulesWithKotlinFiles) {
var moduleCanBeConfigured = false
var moduleAlreadyConfigured = false
for (configurator in configurators) {
if (moduleCanBeConfigured && configurator in runnableConfigurators) continue
if (configurator.getStatus(moduleSourceRootGroup) == ConfigureKotlinStatus.CAN_BE_CONFIGURED) {
moduleCanBeConfigured = true
runnableConfigurators.add(configurator)
val status = configurator.getStatus(moduleSourceRootGroup)
when (status) {
ConfigureKotlinStatus.CAN_BE_CONFIGURED -> {
moduleCanBeConfigured = true
runnableConfigurators.add(configurator)
}
ConfigureKotlinStatus.CONFIGURED -> moduleAlreadyConfigured = true
}
}
if (moduleCanBeConfigured) configurableModules.add(moduleSourceRootGroup.baseModule)
if (moduleCanBeConfigured && !moduleAlreadyConfigured && !SuppressNotificationState.isKotlinNotConfiguredSuppressed(moduleSourceRootGroup))
configurableModules.add(moduleSourceRootGroup)
}
return configurableModules to runnableConfigurators
@@ -8,8 +8,11 @@ package org.jetbrains.kotlin.idea.configuration
import com.intellij.notification.Notification
import com.intellij.notification.NotificationsManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.configuration.ui.notifications.ConfigureKotlinNotification
import java.util.concurrent.atomic.AtomicBoolean
@@ -17,9 +20,9 @@ import kotlin.reflect.KClass
object ConfigureKotlinNotificationManager: KotlinSingleNotificationManager<ConfigureKotlinNotification> {
fun notify(project: Project, excludeModules: List<Module> = emptyList()) {
val notificationString = ConfigureKotlinNotification.getNotificationString(project, excludeModules)
if (notificationString != null) {
notify(project, ConfigureKotlinNotification(project, excludeModules, notificationString))
val notificationState = ConfigureKotlinNotification.getNotificationState(project, excludeModules)
if (notificationState != null) {
notify(project, ConfigureKotlinNotification(project, excludeModules, notificationState))
}
}
@@ -47,8 +50,7 @@ interface KotlinSingleNotificationManager<in T: Notification> {
for (oldNotification in notifications) {
if (oldNotification == notification) {
isNotificationExists = true
}
else {
} else {
oldNotification?.expire()
}
}
@@ -59,20 +61,37 @@ interface KotlinSingleNotificationManager<in T: Notification> {
private val checkInProgress = AtomicBoolean(false)
fun checkHideNonConfiguredNotifications(project: Project) {
if (checkInProgress.get() || ConfigureKotlinNotificationManager.getVisibleNotifications(project).isEmpty()) return
if (checkInProgress.get()) return
val notification = ConfigureKotlinNotificationManager.getVisibleNotifications(project).firstOrNull() ?: return
ApplicationManager.getApplication().executeOnPooledThread {
if (!checkInProgress.compareAndSet(false, true)) return@executeOnPooledThread
DumbService.getInstance(project).waitForSmartMode()
if (getConfigurableModulesWithKotlinFiles(project).none(::isNotConfiguredNotificationRequired)) {
val moduleSourceRootMap = ModuleSourceRootMap(project)
val hideNotification = try {
val moduleSourceRootGroups = notification.notificationState.notConfiguredModules
.mapNotNull { ModuleManager.getInstance(project).findModuleByName(it) }
.map { moduleSourceRootMap.getWholeModuleGroup(it) }
moduleSourceRootGroups.none(::isNotConfiguredNotificationRequired)
} catch (e: IndexNotReadyException) {
checkInProgress.set(false)
ApplicationManager.getApplication().invokeLater {
checkHideNonConfiguredNotifications(project)
}
return@executeOnPooledThread
}
if (hideNotification) {
ApplicationManager.getApplication().invokeLater {
ConfigureKotlinNotificationManager.expireOldNotifications(project)
checkInProgress.set(false)
}
}
else {
} else {
checkInProgress.set(false)
}
}
}
private val LOG = Logger.getInstance(ConfigureKotlinNotificationManager::class.java)
@@ -11,19 +11,25 @@ import com.intellij.notification.NotificationType
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.configuration.KotlinProjectConfigurator
import org.jetbrains.kotlin.idea.configuration.getConfigurationPossibilities
import org.jetbrains.kotlin.idea.configuration.getConfigurationPossibilitiesForConfigureNotification
import org.jetbrains.kotlin.idea.configuration.getConfiguratorByName
import org.jetbrains.kotlin.idea.configuration.ui.KotlinConfigurationCheckerComponent
import javax.swing.event.HyperlinkEvent
data class ConfigureKotlinNotificationState(
val notificationString: String,
val notConfiguredModules: Collection<String>
)
class ConfigureKotlinNotification(
project: Project,
excludeModules: List<Module>,
notificationString: String
val notificationState: ConfigureKotlinNotificationState
) : Notification(
KotlinConfigurationCheckerComponent.CONFIGURE_NOTIFICATION_GROUP_ID, "Configure Kotlin",
notificationString,
NotificationType.WARNING, NotificationListener { notification, event ->
notificationState.notificationString,
NotificationType.WARNING,
NotificationListener { notification, event ->
if (event.eventType == HyperlinkEvent.EventType.ACTIVATED) {
val configurator = getConfiguratorByName(event.description) ?: throw AssertionError("Missed action: " + event.description)
notification.expire()
@@ -46,18 +52,21 @@ class ConfigureKotlinNotification(
}
companion object {
fun getNotificationString(project: Project, excludeModules: Collection<Module>): String? {
val (configurableModules, ableToRunConfigurators) = getConfigurationPossibilities(project, excludeModules)
if (ableToRunConfigurators.isEmpty()) return null
fun getNotificationState(project: Project, excludeModules: Collection<Module>): ConfigureKotlinNotificationState? {
val (configurableModules, ableToRunConfigurators) = getConfigurationPossibilitiesForConfigureNotification(project, excludeModules)
if (ableToRunConfigurators.isEmpty() || configurableModules.isEmpty()) return null
val isOnlyOneModule = configurableModules.size == 1
val modulesString = if (isOnlyOneModule) "'${configurableModules.first().name}' module" else "modules"
val modulesString = if (isOnlyOneModule) "'${configurableModules.first().baseModule.name}' module" else "modules"
val links = ableToRunConfigurators.joinToString(separator = "<br/>") { configurator ->
getLink(configurator, isOnlyOneModule)
}
return "Configure $modulesString in '${project.name}' project<br/> $links"
return ConfigureKotlinNotificationState(
"Configure $modulesString in '${project.name}' project<br/> $links",
configurableModules.map { it.baseModule.name }
)
}
private fun getLink(configurator: KotlinProjectConfigurator, isOnlyOneModule: Boolean): String {