UnknownSdkTracker - split presentation from logic, componentize

GitOrigin-RevId: 28d753b3e038d04d3bb9ee23273194bea1537830
This commit is contained in:
Eugene Petrenko
2019-12-18 10:32:28 +00:00
committed by intellij-monorepo-bot
parent b2a226a6d3
commit b9cd59380d
7 changed files with 329 additions and 278 deletions
@@ -9,7 +9,7 @@ import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.impl.UnknownSdkTracker;
import com.intellij.openapi.projectRoots.impl.UnknownSdkEditorNotification;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Disposer;
@@ -87,7 +87,7 @@ public class SdkSetupNotificationTest extends JavaCodeInsightFixtureTestCase {
AssertionsForClassTypes.assertThat(editors).hasSize(1);
EditorNotificationsImpl.completeAsyncTasks();
List<? extends EditorNotificationPanel> data = editors[0].getUserData(UnknownSdkTracker.GlobalEditorNotification.NOTIFICATIONS);
List<? extends EditorNotificationPanel> data = editors[0].getUserData(UnknownSdkEditorNotification.NOTIFICATIONS);
if (data == null) return null;
Assert.assertEquals("Only one notification was expected, but were " + data, 1, data.size());
return data.iterator().next();
@@ -23,6 +23,11 @@ import java.util.List;
*/
public class SdkUsagesCollector {
private static final ExtensionPointName<SdkUsagesContributor> EP_NAME = ExtensionPointName.create("com.intellij.sdkUsagesContributor");
private final Project myProject;
public SdkUsagesCollector(@NotNull Project project) {
myProject = project;
}
@NotNull
public static SdkUsagesCollector getInstance(@NotNull Project project) {
@@ -58,9 +63,9 @@ public class SdkUsagesCollector {
* Iterates the project model to detect usages if SDKs
*/
@NotNull
public List<SdkUsage> collectSdkUsages(@NotNull Project project) {
public List<SdkUsage> collectSdkUsages() {
List<SdkUsage> usages = new ArrayList<>();
EP_NAME.forEachExtensionSafe(it -> usages.addAll(it.contributeUsages(project)));
EP_NAME.forEachExtensionSafe(it -> usages.addAll(it.contributeUsages(myProject)));
return usages;
}
@@ -0,0 +1,68 @@
// Copyright 2000-2019 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.notification.NotificationDisplayType;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
import com.intellij.openapi.roots.ui.configuration.SdkListPresenter;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import static com.intellij.notification.NotificationAction.createSimple;
import static com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.*;
public class UnknownSdkBalloonNotification {
private static final NotificationGroup SDK_CONFIGURED_GROUP
= new NotificationGroup("Missing SDKs", NotificationDisplayType.BALLOON, true);
@NotNull
public static UnknownSdkBalloonNotification getInstance(@NotNull Project project) {
return project.getService(UnknownSdkBalloonNotification.class);
}
private final Project myProject;
public UnknownSdkBalloonNotification(@NotNull Project project) {
myProject = project;
}
public void notifyFixedSdks(@NotNull Map<? extends UnknownSdk, LocalSdkFix> localFixes) {
final String title;
final StringBuilder message = new StringBuilder();
if (localFixes.isEmpty()) return;
Set<String> usages = new TreeSet<>();
for (Map.Entry<? extends UnknownSdk, LocalSdkFix> entry : localFixes.entrySet()) {
LocalSdkFix fix = entry.getValue();
String usage = "\"" + entry.getKey().getSdkName() + "\"" +
" is set to " +
fix.getVersionString() +
" <br/> " +
SdkListPresenter.presentDetectedSdkPath(fix.getExistingSdkHome());
usages.add(usage);
}
message.append(StringUtil.join(usages, "<br/><br/>"));
if (localFixes.size() == 1) {
Map.Entry<? extends UnknownSdk, LocalSdkFix> entry = localFixes.entrySet().iterator().next();
UnknownSdk info = entry.getKey();
title = info.getSdkType().getPresentableName() + " is configured";
}
else {
title = "SDKs are configured";
}
SDK_CONFIGURED_GROUP.createNotification(title, message.toString(), NotificationType.INFORMATION, null)
.setImportant(true)
.addAction(createSimple(
"Change in the Project Structure Dialog...",
() -> ProjectSettingsService.getInstance(myProject).openProjectSettings()))
.notify(myProject);
}
}
@@ -0,0 +1,131 @@
// Copyright 2000-2019 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.Disposable;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.DownloadSdkFix;
import com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.UnknownSdk;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.EditorNotificationPanel;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UnknownSdkEditorNotification implements Disposable {
public static final Key<List<MissingSdkNotificationPanel>> NOTIFICATIONS = Key.create("notifications added to the editor");
private static final Key<?> EDITOR_NOTIFICATIONS_KEY = Key.create("SdkSetupNotificationNew");
@NotNull
public static UnknownSdkEditorNotification getInstance(@NotNull Project project) {
return project.getService(UnknownSdkEditorNotification.class);
}
private final Project myProject;
private final FileEditorManager myFileEditorManager;
private Map<UnknownSdk, DownloadSdkFix> myNotifications = new HashMap<>();
UnknownSdkEditorNotification(@NotNull Project project) {
myProject = project;
myFileEditorManager = FileEditorManager.getInstance(myProject);
myProject.getMessageBus()
.connect(this)
.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() {
@Override
public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
for (FileEditor editor : myFileEditorManager.getEditors(file)) {
updateEditorNotifications(editor);
}
}
});
}
private static class MissingSdkNotificationPanel extends EditorNotificationPanel {
private final UnknownSdk myInfo;
private MissingSdkNotificationPanel(@NotNull final UnknownSdk info) {
myInfo = info;
}
public boolean isSameProblemAs(@NotNull MissingSdkNotificationPanel panel) {
return this.myInfo.equals(panel.myInfo);
}
}
@NotNull
private MissingSdkNotificationPanel createPanelFor(@NotNull UnknownSdk info,
@NotNull DownloadSdkFix fix) {
String sdkName = info.getSdkType().getPresentableName();
MissingSdkNotificationPanel panel = new MissingSdkNotificationPanel(info);
panel.setProject(myProject);
panel.setProviderKey(EDITOR_NOTIFICATIONS_KEY);
panel.setText(sdkName + " \"" + info.getSdkName() + "\" is missing");
panel.createActionLabel("Download " + sdkName + " (" + fix.getDownloadDescription() + ")", () -> {
removeNotification(panel);
UnknownSdkTracker.getInstance(myProject).applyDownloadableFix(info, fix);
});
panel.createActionLabel("Configure...", () -> {
UnknownSdkTracker.getInstance(myProject).showSdkSelectionPopup(info, panel, () -> removeNotification(panel));
});
return panel;
}
@Override
public void dispose() { }
public void showNotifications(@NotNull final Map<? extends UnknownSdk, DownloadSdkFix> files) {
myNotifications = new HashMap<>(files);
for (FileEditor editor : myFileEditorManager.getAllEditors()) {
updateEditorNotifications(editor);
}
}
private void removeNotification(@NotNull MissingSdkNotificationPanel expiredPanel) {
myNotifications.remove(expiredPanel.myInfo);
for (FileEditor editor : myFileEditorManager.getAllEditors()) {
List<MissingSdkNotificationPanel> notifications = editor.getUserData(NOTIFICATIONS);
if (notifications == null) continue;
for (MissingSdkNotificationPanel panel : new ArrayList<>(notifications)) {
if (panel.isSameProblemAs(expiredPanel)) {
myFileEditorManager.removeTopComponent(editor, panel);
notifications.remove(panel);
}
}
}
}
private void updateEditorNotifications(@NotNull FileEditor editor) {
if (!editor.isValid()) return;
List<MissingSdkNotificationPanel> notifications = editor.getUserData(NOTIFICATIONS);
if (notifications != null) {
for (JComponent component : notifications) {
myFileEditorManager.removeTopComponent(editor, component);
}
notifications.clear();
} else {
notifications = new SmartList<>();
editor.putUserData(NOTIFICATIONS, notifications);
}
for (Map.Entry<UnknownSdk, DownloadSdkFix> e : myNotifications.entrySet()) {
MissingSdkNotificationPanel notification = createPanelFor(e.getKey(), e.getValue());
notifications.add(notification);
myFileEditorManager.addTopComponent(editor, notification);
}
}
}
@@ -0,0 +1,14 @@
// Copyright 2000-2019 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.project.Project;
import com.intellij.openapi.startup.StartupActivity;
import com.intellij.openapi.startup.StartupManager;
import org.jetbrains.annotations.NotNull;
public class UnknownSdkStartupChecker implements StartupActivity.Background, StartupActivity.DumbAware {
@Override
public void runActivity(@NotNull Project project) {
StartupManager.getInstance(project).runWhenProjectIsInitialized(() -> UnknownSdkTracker.getInstance(project).updateUnknownSdks());
}
}
@@ -1,18 +1,11 @@
// Copyright 2000-2019 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.notification.NotificationAction;
import com.intellij.notification.NotificationDisplayType;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
@@ -24,74 +17,65 @@ import com.intellij.openapi.projectRoots.impl.SdkUsagesCollector.SdkUsage;
import com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.DownloadSdkFix;
import com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.LocalSdkFix;
import com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.UnknownSdkLookup;
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
import com.intellij.openapi.roots.ui.configuration.SdkListModelBuilder;
import com.intellij.openapi.roots.ui.configuration.SdkListPresenter;
import com.intellij.openapi.roots.ui.configuration.SdkPopupFactory;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
import com.intellij.openapi.roots.ui.configuration.projectRoot.SdkDownloadTask;
import com.intellij.openapi.roots.ui.configuration.projectRoot.SdkDownloadTracker;
import com.intellij.openapi.startup.StartupActivity;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.EditorNotificationPanel;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.intellij.openapi.progress.PerformInBackgroundOption.ALWAYS_BACKGROUND;
import static com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.EP_NAME;
import static com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.UnknownSdk;
public class UnknownSdkTracker {
private static final Logger LOG = Logger.getInstance(UnknownSdkTracker.class);
public static class ActivityTracker implements StartupActivity.Background, StartupActivity.DumbAware {
@Override
public void runActivity(@NotNull Project project) {
StartupManager.getInstance(project).runWhenProjectIsInitialized(() -> updateUnknownSdks(project));
}
@NotNull
public static UnknownSdkTracker getInstance(@NotNull Project project) {
return project.getService(UnknownSdkTracker.class);
}
private static void updateUnknownSdks(@NotNull Project project) {
if (!EP_NAME.hasAnyExtensions()) return;
@NotNull private final Project myProject;
public UnknownSdkTracker(@NotNull Project project) {
myProject = project;
}
public void updateUnknownSdks() {
if (!UnknownSdkResolver.EP_NAME.hasAnyExtensions()) return;
ProgressManager.getInstance()
.run(new Task.Backgroundable(project, "Resolving SDKs", false, ALWAYS_BACKGROUND) {
.run(new Task.Backgroundable(myProject, "Resolving SDKs", false, ALWAYS_BACKGROUND) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
List<UnknownSdkLookup> lookups = new ArrayList<>();
EP_NAME.forEachExtensionSafe(ext -> {
UnknownSdkLookup resolver = ext.createResolver(project, indicator);
UnknownSdkResolver.EP_NAME.forEachExtensionSafe(ext -> {
UnknownSdkLookup resolver = ext.createResolver(UnknownSdkTracker.this.myProject, indicator);
if (resolver != null) {
lookups.add(resolver);
}
});
if (lookups.isEmpty()) return;
updateUnknownSdksWithProgress(project, indicator, lookups);
updateUnknownSdksWithProgress(indicator, lookups);
}
});
}
private static void updateUnknownSdksWithProgress(@NotNull Project project,
@NotNull ProgressIndicator indicator,
@NotNull List<UnknownSdkLookup> lookups) {
List<MissingSdkInfo> fixable = ReadAction.compute(() -> collectUnknownSdks(project));
private void updateUnknownSdksWithProgress(@NotNull ProgressIndicator indicator,
@NotNull List<UnknownSdkLookup> lookups) {
List<MissingSdkInfo> fixable = ReadAction.compute(() -> collectUnknownSdks());
if (fixable.isEmpty()) return;
Map<MissingSdkInfo, LocalSdkFix> localFixes = findLocalFixes(indicator, fixable, lookups);
@@ -104,21 +88,39 @@ public class UnknownSdkTracker {
ApplicationManager.getApplication().invokeLater(() -> {
configureLocalSdks(localFixes);
notifyFixedSdks(project, localFixes);
if (!downloadFixes.isEmpty()) {
GlobalEditorNotification.getInstance(project).showNotifications(downloadFixes);
}
UnknownSdkBalloonNotification.getInstance(myProject).notifyFixedSdks(localFixes);
UnknownSdkEditorNotification.getInstance(myProject).showNotifications(downloadFixes);
});
}
private static void applyDownloadableFix(@NotNull Project project,
@NotNull MissingSdkInfo info,
@NotNull DownloadSdkFix fix) {
@NotNull
private List<MissingSdkInfo> collectUnknownSdks() {
Map<String, MissingSdkInfo> myInfos = new HashMap<>();
List<SdkUsage> usages = SdkUsagesCollector.getInstance(myProject).collectSdkUsages();
ProjectJdkTable jdkTable = ProjectJdkTable.getInstance();
for (SdkUsage usage : usages) {
String sdkName = usage.getSdkName();
//we do not track existing SDKs
if (jdkTable.findJdk(sdkName) != null) continue;
MissingSdkInfo info = myInfos.get(sdkName);
if (info == null) {
info = new MissingSdkInfo(sdkName);
myInfos.put(sdkName, info);
}
info.attachSdkType(usage.getSdkTypeName());
}
return ContainerUtil.filter(myInfos.values(), sdk -> sdk.mySdkType != null);
}
public void applyDownloadableFix(@NotNull UnknownSdk info, @NotNull DownloadSdkFix fix) {
SdkDownloadTask task;
String title = "Configuring SDK";
try {
task = ProgressManager.getInstance().run(new Task.WithResult<SdkDownloadTask, RuntimeException>(project, title, true) {
task = ProgressManager.getInstance().run(new Task.WithResult<SdkDownloadTask, RuntimeException>(myProject, title, true) {
@Override
protected SdkDownloadTask compute(@NotNull ProgressIndicator indicator) {
return fix.createTask(indicator);
@@ -134,7 +136,7 @@ public class UnknownSdkTracker {
ApplicationManager.getApplication().invokeLater(() -> {
Disposable lifetime = Disposer.newDisposable();
Disposer.register(project,lifetime);
Disposer.register(myProject, lifetime);
Sdk sdk = createSdkPrototype(info);
@@ -151,6 +153,46 @@ public class UnknownSdkTracker {
});
}
public void showSdkSelectionPopup(@NotNull UnknownSdk info,
@NotNull JComponent panel,
@NotNull Runnable onSelectionMade) {
ProjectSdksModel model = new ProjectSdksModel();
SdkListModelBuilder modelBuilder = new SdkListModelBuilder(
myProject,
model,
sdkType -> Objects.equals(sdkType, info.getSdkType()),
null,
null);
SdkPopupFactory popup = new SdkPopupFactory(
myProject,
model,
modelBuilder
);
AtomicBoolean wasSdkCreated = new AtomicBoolean(false);
model.addListener(new SdkModel.Listener() {
@Override
public void sdkAdded(@NotNull Sdk sdk) {
//it is easier and safer than committing the ProjectSdksModel instance
registerNewSdkInJdkTable(info, sdk);
wasSdkCreated.set(true);
}
});
//FileEditorManager#addTopComponent wraps the panel to implement borders, unwrapping
Container container = panel.getParent();
if (container == null) container = panel;
popup.showUnderneathToTheRightOf(
container,
() -> {
if (wasSdkCreated.get()) {
onSelectionMade.run();
}
}
);
}
private static void configureLocalSdks(@NotNull Map<MissingSdkInfo, LocalSdkFix> localFixes) {
for (Map.Entry<MissingSdkInfo, LocalSdkFix> e : localFixes.entrySet()) {
MissingSdkInfo info = e.getKey();
@@ -206,67 +248,25 @@ public class UnknownSdkTracker {
}
@NotNull
private static List<MissingSdkInfo> collectUnknownSdks(@NotNull Project project) {
Map<String, MissingSdkInfo> myInfos = new HashMap<>();
List<SdkUsage> usages = SdkUsagesCollector.getInstance(project).collectSdkUsages(project);
ProjectJdkTable jdkTable = ProjectJdkTable.getInstance();
for (SdkUsage usage : usages) {
String sdkName = usage.getSdkName();
private static Sdk createSdkPrototype(@NotNull UnknownSdk info) {
return ProjectJdkTable.getInstance().createSdk(info.getSdkName(), info.getSdkType());
}
//we do not track existing SDKs
if (jdkTable.findJdk(sdkName) != null) continue;
MissingSdkInfo info = myInfos.get(sdkName);
if (info == null) {
info = new MissingSdkInfo(sdkName);
myInfos.put(sdkName, info);
private static void registerNewSdkInJdkTable(@NotNull UnknownSdk info, @NotNull Sdk sdk) {
WriteAction.run(() -> {
ProjectJdkTable table = ProjectJdkTable.getInstance();
Sdk clash = table.findJdk(info.getSdkName());
if (clash != null) {
LOG.warn("SDK with name " + info.getSdkName() + " already exists: clash=" + clash + ", new=" + sdk);
return;
}
info.attachSdkType(usage.getSdkTypeName());
}
SdkModificator mod = sdk.getSdkModificator();
mod.setName(info.getSdkName());
mod.commitChanges();
return ContainerUtil.filter(myInfos.values(), sdk -> sdk.mySdkType != null);
}
private static final NotificationGroup SDK_CONFIGURED_GROUP
= new NotificationGroup("Missing SDKs", NotificationDisplayType.BALLOON, true);
private static void notifyFixedSdks(@NotNull Project project,
@NotNull Map<MissingSdkInfo, LocalSdkFix> localFixes) {
final String title;
final StringBuilder message = new StringBuilder();
if (localFixes.isEmpty()) return;
Set<String> usages = new TreeSet<>();
for (Map.Entry<MissingSdkInfo, LocalSdkFix> entry : localFixes.entrySet()) {
LocalSdkFix fix = entry.getValue();
String usage = "\"" + entry.getKey().getSdkName() + "\"" +
" is set to " +
fix.getVersionString() +
" <br/> " +
getLocalFixPresentableHome(fix);
usages.add(usage);
}
message.append(StringUtil.join(usages, "<br/><br/>"));
if (localFixes.size() == 1) {
Map.Entry<MissingSdkInfo, LocalSdkFix> entry = localFixes.entrySet().iterator().next();
MissingSdkInfo info = entry.getKey();
title = info.getSdkType().getPresentableName() + " is configured";
} else {
title = "SDKs are configured";
}
SDK_CONFIGURED_GROUP.createNotification(title, message.toString(), NotificationType.INFORMATION, null)
.setImportant(true)
.addAction(NotificationAction.createSimple("Change in the Project Structure Dialog...", () -> ProjectSettingsService.getInstance(project).openProjectSettings()))
.notify(project);
}
@NotNull
private static String getLocalFixPresentableHome(@NotNull LocalSdkFix fix) {
String home = fix.getExistingSdkHome();
return SdkListPresenter.presentDetectedSdkPath(home);
table.addJdk(sdk);
});
}
private static class MissingSdkInfo implements UnknownSdk {
@@ -319,174 +319,4 @@ public class UnknownSdkTracker {
return Objects.hash(mySdkName);
}
}
public static class GlobalEditorNotification implements Disposable {
public static final Key<List<MissingSdkNotificationPanel>> NOTIFICATIONS = Key.create("notifications added to the editor");
private static final Key<?> EDITOR_NOTIFICATIONS_KEY = Key.create("SdkSetupNotificationNew");
@NotNull
public static GlobalEditorNotification getInstance(@NotNull Project project) {
return project.getService(GlobalEditorNotification.class);
}
private final Project myProject;
private final FileEditorManager myFileEditorManager;
private Map<MissingSdkInfo, DownloadSdkFix> myNotifications = new HashMap<>();
GlobalEditorNotification(@NotNull Project project) {
myProject = project;
myFileEditorManager = FileEditorManager.getInstance(myProject);
myProject.getMessageBus()
.connect(this)
.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() {
@Override
public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
for (FileEditor editor : myFileEditorManager.getEditors(file)) {
updateEditorNotifications(editor);
}
}
});
}
private static class MissingSdkNotificationPanel extends EditorNotificationPanel {
private final MissingSdkInfo myInfo;
private MissingSdkNotificationPanel(@NotNull final MissingSdkInfo info) {
myInfo = info;
}
public boolean isSameProblemAs(@NotNull MissingSdkNotificationPanel panel) {
return this.myInfo.equals(panel.myInfo);
}
}
@NotNull
private MissingSdkNotificationPanel createPanelFor(@NotNull MissingSdkInfo info,
@NotNull DownloadSdkFix fix) {
String sdkName = info.getSdkType().getPresentableName();
MissingSdkNotificationPanel panel = new MissingSdkNotificationPanel(info);
panel.setProject(myProject);
panel.setProviderKey(EDITOR_NOTIFICATIONS_KEY);
panel.setText(sdkName + " \"" + info.getSdkName() + "\" is missing");
panel.createActionLabel("Download " + sdkName + " (" + fix.getDownloadDescription() + ")", () -> {
removeNotification(panel);
applyDownloadableFix(myProject, info, fix);
});
panel.createActionLabel("Configure...", () -> {}).addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
ProjectSdksModel model = new ProjectSdksModel();
SdkListModelBuilder modelBuilder = new SdkListModelBuilder(
myProject,
model,
sdkType -> Objects.equals(sdkType, info.getSdkType()),
null,
null);
SdkPopupFactory popup = new SdkPopupFactory(
myProject,
model,
modelBuilder
);
AtomicBoolean wasSdkCreated = new AtomicBoolean(false);
model.addListener(new SdkModel.Listener() {
@Override
public void sdkAdded(@NotNull Sdk sdk) {
//it is easier and safer than committing the ProjectSdksModel instance
registerNewSdkInJdkTable(info, sdk);
wasSdkCreated.set(true);
}
});
//FileEditorManager#addTopComponent wraps the panel to implement borders, unwrapping
Container container = panel.getParent();
if (container == null) container = panel;
popup.showUnderneathToTheRightOf(
container,
() -> {
if (wasSdkCreated.get()) {
removeNotification(panel);
}
}
);
}
});
return panel;
}
@Override
public void dispose() { }
public void showNotifications(@NotNull final Map<MissingSdkInfo, DownloadSdkFix> files) {
myNotifications = new HashMap<>(files);
for (FileEditor editor : myFileEditorManager.getAllEditors()) {
updateEditorNotifications(editor);
}
}
private void removeNotification(@NotNull MissingSdkNotificationPanel expiredPanel) {
myNotifications.remove(expiredPanel.myInfo);
for (FileEditor editor : myFileEditorManager.getAllEditors()) {
List<MissingSdkNotificationPanel> notifications = editor.getUserData(NOTIFICATIONS);
if (notifications == null) continue;
for (MissingSdkNotificationPanel panel : new ArrayList<>(notifications)) {
if (panel.isSameProblemAs(expiredPanel)) {
myFileEditorManager.removeTopComponent(editor, panel);
notifications.remove(panel);
}
}
}
}
private void updateEditorNotifications(@NotNull FileEditor editor) {
if (!editor.isValid()) return;
List<MissingSdkNotificationPanel> notifications = editor.getUserData(NOTIFICATIONS);
if (notifications != null) {
for (JComponent component : notifications) {
myFileEditorManager.removeTopComponent(editor, component);
}
notifications.clear();
} else {
notifications = new SmartList<>();
editor.putUserData(NOTIFICATIONS, notifications);
}
for (Map.Entry<MissingSdkInfo, DownloadSdkFix> e : myNotifications.entrySet()) {
MissingSdkNotificationPanel notification = createPanelFor(e.getKey(), e.getValue());
notifications.add(notification);
myFileEditorManager.addTopComponent(editor, notification);
}
}
}
@NotNull
private static Sdk createSdkPrototype(@NotNull MissingSdkInfo info) {
return ProjectJdkTable.getInstance().createSdk(info.getSdkName(), info.getSdkType());
}
private static void registerNewSdkInJdkTable(@NotNull MissingSdkInfo info, @NotNull Sdk sdk) {
WriteAction.run(() -> {
ProjectJdkTable table = ProjectJdkTable.getInstance();
Sdk clash = table.findJdk(info.getSdkName());
if (clash != null) {
LOG.warn("SDK with name " + info.getSdkName() + " already exists: clash=" + clash + ", new=" + sdk);
return;
}
SdkModificator mod = sdk.getSdkModificator();
mod.setName(info.getSdkName());
mod.commitChanges();
table.addJdk(sdk);
});
}
}
@@ -50,8 +50,11 @@
<projectService serviceImplementation="com.intellij.openapi.projectRoots.impl.SdkUsagesCollector"/>
<sdkUsagesContributor implementation="com.intellij.openapi.projectRoots.impl.SdkUsagesCollector$ProjectSdkUsages"/>
<sdkUsagesContributor implementation="com.intellij.openapi.projectRoots.impl.SdkUsagesCollector$ModuleSdkUsages"/>
<postStartupActivity implementation="com.intellij.openapi.projectRoots.impl.UnknownSdkTracker$ActivityTracker"/>
<projectService serviceImplementation="com.intellij.openapi.projectRoots.impl.UnknownSdkTracker$GlobalEditorNotification"/>
<postStartupActivity implementation="com.intellij.openapi.projectRoots.impl.UnknownSdkStartupChecker"/>
<projectService serviceImplementation="com.intellij.openapi.projectRoots.impl.UnknownSdkEditorNotification"/>
<projectService serviceImplementation="com.intellij.openapi.projectRoots.impl.UnknownSdkBalloonNotification"/>
<projectService serviceImplementation="com.intellij.openapi.projectRoots.impl.UnknownSdkTracker"/>
<applicationService serviceImplementation="com.intellij.codeInsight.TargetElementUtil"/>
<applicationService serviceInterface="com.intellij.profile.codeInspection.InspectionProfileManager"