jdk-auto: use non-modal notifications for compile actions, IDEA-253119, IDEA-240999

GitOrigin-RevId: ec7b75fef53b2261b6638b4f9034f8424f502778
This commit is contained in:
Eugene Petrenko
2020-10-16 21:08:47 +02:00
committed by intellij-monorepo-bot
parent f170505ff8
commit 4635352640
8 changed files with 134 additions and 243 deletions

View File

@@ -677,10 +677,9 @@ public final class CompileDriver {
if (runUnknownSdkCheck) {
var result = CompilerDriverUnknownSdkTracker
.getInstance(myProject)
.fixSdkSettings(projectSdkNotSpecified, scopeModules);
.fixSdkSettings(projectSdkNotSpecified, scopeModules, formatModulesList(modulesWithoutJdkAssigned));
if (result.getShouldOpenProjectStructureDialog()) {
result.openProjectStructureDialogIfNeeded();
if (result == CompilerDriverUnknownSdkTracker.Outcome.STOP_COMPILE) {
return false;
}
@@ -785,13 +784,7 @@ public final class CompileDriver {
List<String> modules,
String editorNameToSelect) {
String nameToSelect = notSpecifiedValueInheritedFromProject ? null : ContainerUtil.getFirstItem(modules);
final int maxModulesToShow = 10;
List<String> actualNamesToInclude = new ArrayList<>(ContainerUtil.getFirstItems(modules, maxModulesToShow));
if (modules.size() > maxModulesToShow) {
actualNamesToInclude.add(JavaCompilerBundle.message("error.jdk.module.names.overflow.element.ellipsis"));
}
final String message = JavaCompilerBundle.message(resourceId, modules.size(), NlsMessages.formatNarrowAndList(actualNamesToInclude));
final String message = JavaCompilerBundle.message(resourceId, modules.size(), formatModulesList(modules));
if (ApplicationManager.getApplication().isUnitTestMode()) {
LOG.error(message);
@@ -804,6 +797,17 @@ public final class CompileDriver {
.showNotification();
}
@NotNull
private static String formatModulesList(@NotNull List<String> modules) {
final int maxModulesToShow = 10;
List<String> actualNamesToInclude = new ArrayList<>(ContainerUtil.getFirstItems(modules, maxModulesToShow));
if (modules.size() > maxModulesToShow) {
actualNamesToInclude.add(JavaCompilerBundle.message("error.jdk.module.names.overflow.element.ellipsis"));
}
return NlsMessages.formatNarrowAndList(actualNamesToInclude);
}
public static CompilerMessageCategory convertToCategory(CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind kind, CompilerMessageCategory defaultCategory) {
switch(kind) {
case ERROR: case INTERNAL_BUILDER_ERROR:

View File

@@ -44,8 +44,14 @@ class CompileDriverNotifications(
.setTitle(JavaCompilerBundle.message("notification.title.jps.cannot.start.compiler"))
.setImportant(true)
fun withOpenSettingsAction(moduleNameToSelect: String?, tabNameToSelect: String?): LightNotification = apply {
val handler = Runnable {
fun withExpiringAction(@NotificationContent title : String,
handler: () -> Unit) = apply {
baseNotification.addAction(NotificationAction.createSimpleExpiring(title, handler))
}
@JvmOverloads
fun withOpenSettingsAction(moduleNameToSelect: String? = null, tabNameToSelect: String? = null) =
withExpiringAction(JavaCompilerBundle.message("notification.action.jps.open.configuration.dialog")) {
val service = ProjectSettingsService.getInstance(project)
if (moduleNameToSelect != null) {
service.showModuleConfigurationDialog(moduleNameToSelect, tabNameToSelect)
@@ -55,13 +61,7 @@ class CompileDriverNotifications(
}
}
baseNotification.addAction(NotificationAction.createSimpleExpiring(
JavaCompilerBundle.message("notification.action.jps.open.configuration.dialog"),
handler
))
}
fun withContent(@NotificationContent content : String): LightNotification = apply {
fun withContent(@NotificationContent content: String): LightNotification = apply {
baseNotification.setContent(content)
}

View File

@@ -1,17 +1,20 @@
// 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.compiler.impl
import com.intellij.ide.nls.NlsMessages
import com.intellij.openapi.compiler.JavaCompilerBundle
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.ControlFlowException
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.*
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.impl.UnknownSdkCollector
import com.intellij.openapi.projectRoots.impl.UnknownSdkModalNotification
import com.intellij.openapi.projectRoots.impl.UnknownSdkTracker
import com.intellij.openapi.project.ProjectBundle
import com.intellij.openapi.projectRoots.impl.*
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.text.HtmlBuilder
import com.intellij.openapi.util.text.HtmlChunk
@Service //project
class CompilerDriverUnknownSdkTracker(
@@ -25,25 +28,112 @@ class CompilerDriverUnknownSdkTracker(
}
fun fixSdkSettings(updateProjectSdk: Boolean,
modules: List<Module>
): UnknownSdkModalNotification.Outcome {
if (!Registry.`is`("unknown.sdk.modal.jps")) return UnknownSdkModalNotification.getInstance(project).noSettingsDialogSuggested
modules: List<Module>,
@NlsSafe formattedModulesList: String
): Outcome {
if (!Registry.`is`("unknown.sdk.modal.jps")) return Outcome.CONTINUE_COMPILE
val collector = object: UnknownSdkCollector(project) {
override fun checkProjectSdk(project: Project): Boolean = updateProjectSdk
override fun collectModulesToCheckSdk(project: Project) = modules
return ProgressManager.getInstance()
.run(object : Task.WithResult<Outcome, Exception>(project, ProjectBundle.message("progress.title.resolving.sdks"), true) {
override fun compute(indicator: ProgressIndicator): Outcome = try {
computeImpl(indicator)
}
catch (t: Throwable) {
if (t is ControlFlowException) throw t
LOG.warn("Failed to test for Unknown SDKs. ${t.message}", t)
Outcome.CONTINUE_COMPILE
}
fun computeImpl(indicator: ProgressIndicator): Outcome {
val collector = object : UnknownSdkCollector(project) {
override fun checkProjectSdk(project: Project): Boolean = updateProjectSdk
override fun collectModulesToCheckSdk(project: Project) = modules
}
val allActions = UnknownSdkTracker.getInstance(project).collectUnknownSdks(collector, indicator)
if (allActions.isEmpty()) return Outcome.CONTINUE_COMPILE
val actions = UnknownSdkTracker
.getInstance(project)
.applyAutoFixesAndNotify(allActions, indicator)
if (actions.isEmpty()) return Outcome.CONTINUE_COMPILE
return processManualFixes(modules, actions)
}
})
}
enum class Outcome {
//we were able to apply a fix (probably not enough fixes)
STOP_COMPILE,
//we see no reasons to stop the standard logic
CONTINUE_COMPILE,
}
fun processManualFixes(modules: List<Module>,
actions: List<UnknownSdkFix>): Outcome {
val actionsWithFix = actions.mapNotNull { it.suggestedFixAction }
val actionsWithoutFix = actions.filter { it.suggestedFixAction == null }
if (actionsWithFix.isEmpty()) {
//nothing to do. We fallback to the default behaviour because there is nothing we can do better
return Outcome.CONTINUE_COMPILE
}
val moduleNames = modules.map { JavaCompilerBundle.message("dialog.message.error.jdk.not.specified.with.module.name.quoted", it.name) }.toSortedSet()
val errorMessage = JavaCompilerBundle.message("dialog.title.error.jdk.not.specified.with.fixSuggestion")
val errorText = JavaCompilerBundle.message("dialog.message.error.jdk.not.specified.with.fixSuggestion", moduleNames.size, NlsMessages.formatAndList(moduleNames))
val message = HtmlBuilder()
val handler = UnknownSdkModalNotification
message.append(JavaCompilerBundle.message(
"dialog.message.error.jdk.not.specified.with.fixSuggestion",
modules.size,
modules.size)
)
message.append(HtmlChunk.ul().children(actionsWithFix.sortedBy { it.actionDetailedText.toLowerCase() }.map { fix ->
var li = HtmlChunk.li().addText(fix.actionDetailedText)
fix.actionTooltipText?.let {
li = li.addText(it)
}
li
}))
if (actionsWithoutFix.isNotEmpty()) {
message.append(JavaCompilerBundle.message("dialog.message.error.jdk.not.specified.with.noFix"))
message.append(HtmlChunk.ul().children(actionsWithoutFix.sortedBy { it.notificationText.toLowerCase() }.map { fix ->
HtmlChunk.li().addText(fix.notificationText)
}))
}
CompileDriverNotifications
.getInstance(project)
.newModalHandler(errorMessage, errorText)
.createCannotStartNotification()
.withContent(message.toString())
.withExpiringAction(JavaCompilerBundle.message("dialog.message.action.apply.fix")) { applySuggestions(actionsWithFix) }
.withOpenSettingsAction(modules.firstOrNull()?.name, null)
.showNotification()
UnknownSdkTracker.getInstance(project).collectUnknownSdksBlocking(collector, handler)
return Outcome.STOP_COMPILE
}
return handler.outcome
private fun applySuggestions(suggestions: List<UnknownSdkFixAction>) {
if (suggestions.isEmpty()) return
val task = object : Task.Backgroundable(project, ProjectBundle.message("progress.title.resolving.sdks"), true) {
override fun run(indicator: ProgressIndicator) {
for (suggestion in suggestions) {
try {
indicator.withPushPop {
suggestion.applySuggestionBlocking(indicator)
}
}
catch (t: Throwable) {
if (t is ControlFlowException) break
LOG.warn("Failed to apply suggestion $suggestion. ${t.message}", t)
}
}
}
}
ProgressManager.getInstance().run(task)
}
}

View File

@@ -39,9 +39,9 @@ java.compiler.description=Java Compiler
rmi.compiler.description=RMI Compiler
error.jdk.not.specified=The SDK is not specified for {0,choice, 1#module|2#modules\n} {1}
error.jdk.module.names.overflow.element.ellipsis=...
dialog.title.error.jdk.not.specified.with.fixSuggestion=Cannot Start Compiler
dialog.message.error.jdk.not.specified.with.module.name.quoted="{0}"
dialog.message.error.jdk.not.specified.with.fixSuggestion=The SDK is not specified for {1} {0,choice, 1#module|2#modules}
dialog.message.error.jdk.not.specified.with.fixSuggestion=SDK is not specified or corrupted<br/>for {0} {1,choice, 1#module|2#modules}. Resolve the following SDKs automatically?
dialog.message.error.jdk.not.specified.with.noFix=Manual configuration is still required for:
dialog.message.action.apply.fix=Fix automatically
error.output.not.specified=The output path is not specified for {0,choice, 1#module|2#modules\n} {1}
compiler.javac.name=Javac
compiler.configurable.display.name=Compiler

View File

@@ -36,8 +36,7 @@ public interface UnknownSdkFixAction {
void applySuggestionAsync(@Nullable Project project);
/**
* Applies suggestions under a modal progress, e.g. as a part of
* the {@link UnknownSdkModalNotification}.
* Applies suggestions under a given progress
*/
void applySuggestionBlocking(@NotNull ProgressIndicator indicator);

View File

@@ -1,175 +0,0 @@
// 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.projectRoots.impl
import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.ControlFlowException
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.withPushPop
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectBundle
import com.intellij.openapi.projectRoots.impl.UnknownSdkTracker.ShowStatusCallback
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.ui.layout.*
import org.jetbrains.annotations.Nls
import javax.swing.Action
@Service
class UnknownSdkModalNotification(
private val project: Project
) {
companion object {
private val LOG = logger<UnknownSdkModalNotification>()
@JvmStatic
fun getInstance(project: Project) = project.service<UnknownSdkModalNotification>()
}
interface Outcome {
val shouldOpenProjectStructureDialog: Boolean
fun openProjectStructureDialogIfNeeded()
}
val noSettingsDialogSuggested = object: Outcome {
override val shouldOpenProjectStructureDialog: Boolean = false
override fun openProjectStructureDialogIfNeeded() {}
}
private val OPEN_DIALOG = object : Outcome {
override val shouldOpenProjectStructureDialog: Boolean = true
override fun openProjectStructureDialogIfNeeded() {
if (!shouldOpenProjectStructureDialog) return
val service = ProjectSettingsService.getInstance(project)
service.openProjectSettings()
}
}
interface UnknownSdkModalNotificationHandler : ShowStatusCallback {
val outcome: Outcome
}
fun newModalHandler(@Nls dialogTitle: String,
@Nls detailedMessage: String?
): UnknownSdkModalNotificationHandler = object : ShowStatusCallback, UnknownSdkModalNotificationHandler {
override var outcome: Outcome = noSettingsDialogSuggested
override fun showStatus(allActions: List<UnknownSdkFix>, indicator: ProgressIndicator) {
outcome = kotlin.runCatching { showStatusImpl(dialogTitle, detailedMessage, allActions, indicator) }.getOrElse { noSettingsDialogSuggested }
}
}
private fun showStatusImpl(@Nls dialogTitle: String,
@Nls detailedMessage: String?,
allActions: List<UnknownSdkFix>,
indicator: ProgressIndicator) : Outcome {
if (allActions.isEmpty()) return noSettingsDialogSuggested
val actions = UnknownSdkTracker
.getInstance(project)
.applyAutoFixesAndNotify(allActions, indicator)
val actionsWithFix = actions.mapNotNull { it.suggestedFixAction }
val actionsWithoutFix = actions.filter { it.suggestedFixAction == null }
if (actionsWithFix.isEmpty()) {
//nothing to do. We fallback to the default behaviour because there is nothing we can do better
return noSettingsDialogSuggested
}
val isOk = invokeAndWaitIfNeeded {
createConfirmSdkDownloadFixDialog(dialogTitle, detailedMessage, actionsWithFix, actionsWithoutFix).showAndGet()
}
if (isOk) {
val suggestionsApplied = applySuggestions(actionsWithFix, indicator)
if (!suggestionsApplied) return OPEN_DIALOG
}
if (actionsWithoutFix.isNotEmpty()) {
return OPEN_DIALOG
}
return noSettingsDialogSuggested
}
private fun createConfirmSdkDownloadFixDialog(
@Nls dialogTitle: String,
@Nls detailedMessage: String?,
actions: List<UnknownSdkFixAction>,
actionsWithoutFix: List<UnknownSdkFix>
) = object : DialogWrapper(project) {
init {
require(actions.isNotEmpty()) { "There must be fix suggestions! " }
title = dialogTitle
setResizable(false)
init()
val okMessage = when {
actionsWithoutFix.isEmpty() -> ProjectBundle.message("dialog.button.download.sdks")
else -> ProjectBundle.message("dialog.button.download.sdksAndOpenDialog")
}
myOKAction.putValue(Action.NAME, okMessage)
myCancelAction.putValue(Action.NAME, ProjectBundle.message("dialog.button.open.settings"))
}
override fun createCenterPanel() = panel {
detailedMessage?.let {
row {
label(it)
}
}
row {
label(ProjectBundle.message("dialog.text.resolving.sdks.suggestions"))
}
if (actions.isNotEmpty()) {
actions.sortedBy { it.actionDetailedText.toLowerCase() }.forEach {
row(ProjectBundle.message("dialog.section.bullet")) {
val label = label(it.actionDetailedText)
it.actionTooltipText?.let {
label.comment(it)
}
}
}
}
if (actionsWithoutFix.isNotEmpty()) {
row {
label(ProjectBundle.message("dialog.text.resolving.sdks.unknowns"))
}
actionsWithoutFix.sortedBy { it.notificationText.toLowerCase() }.forEach {
row(ProjectBundle.message("dialog.section.bullet")) {
label(it.notificationText)
}
}
}
}
}
private fun applySuggestions(suggestions: List<UnknownSdkFixAction>, indicator: ProgressIndicator): Boolean {
if (suggestions.isEmpty()) return true
var isFailed = false
for (suggestion in suggestions) {
try {
indicator.withPushPop {
suggestion.applySuggestionBlocking(indicator)
}
}
catch (t: Throwable) {
isFailed = true
if (t is ControlFlowException) break
LOG.warn("Failed to apply suggestion $suggestion. ${t.message}", t)
}
}
return !isFailed
}
}

View File

@@ -57,27 +57,6 @@ public class UnknownSdkTracker {
return Registry.is("unknown.sdk") && UnknownSdkResolver.EP_NAME.hasAnyExtensions();
}
public void collectUnknownSdksBlocking(@NotNull UnknownSdkBlockingCollector collector,
@NotNull ShowStatusCallback showStatus) {
if (!isEnabled()) {
showStatus.showEmptyStatus();
return;
}
ProgressManager.getInstance()
.run(new Task.Modal(myProject, ProjectBundle.message("progress.title.resolving.sdks"), true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
var snapshot = collector.collectSdksBlocking();
var action = createProcessSdksAction(snapshot, showStatus);
if (action == null) return;
action.run(indicator);
}
});
}
public @NotNull List<UnknownSdkFix> collectUnknownSdks(@NotNull UnknownSdkBlockingCollector collector,
@NotNull ProgressIndicator indicator) {
if (!isEnabled()) {

View File

@@ -270,13 +270,7 @@ notification.text.jdk.update.found=Update JDK "{0}" to {1}. The current version
notification.link.jdk.update.apply=Download
notification.link.jdk.update.skip=Skip this update
notification.link.jdk.update.retry=Retry
dialog.text.resolving.sdks.suggestions=Resolve the following SDKs automatically?
dialog.text.resolving.sdks.item={0} "{1}"
dialog.text.resolving.sdks.unknowns=Manual configuration is still required, the Project Structure dialog will be opened for:
dialog.button.download.sdks=Fix
dialog.button.download.sdksAndOpenDialog=Apply Suggestions...
dialog.button.open.settings=Open Project Structure Dialog...
dialog.section.bullet=\u2022
unknown.sdk.with.no.name=<unknown>
list.item.all.frameworks=All Frameworks...
#0 - file name