diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 1f6da57ebd9..0b9dfc45cc4 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -34,6 +34,9 @@ org.jetbrains.jet.plugin.ktSignature.KotlinSignatureInJavaMarkerUpdater + + org.jetbrains.jet.plugin.configuration.ui.AbsentJdkAnnotationsNotifications + @@ -310,7 +313,6 @@ - diff --git a/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinNotificationManager.kt b/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinNotificationManager.kt index 37a68bb46b2..7cdf15b2a5b 100644 --- a/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinNotificationManager.kt +++ b/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinNotificationManager.kt @@ -20,6 +20,9 @@ import com.intellij.openapi.project.Project import org.jetbrains.jet.plugin.configuration.ui.notifications.ConfigureKotlinNotification import com.intellij.notification.NotificationsManager import com.intellij.notification.Notification +import com.intellij.openapi.projectRoots.Sdk +import org.jetbrains.jet.plugin.configuration.ui.notifications.AbsentSdkAnnotationsNotification +import org.jetbrains.jet.plugin.configuration.ui.notifications.* object ConfigureKotlinNotificationManager: KotlinSingleNotificationManager { fun notify(project: Project) { @@ -27,6 +30,12 @@ object ConfigureKotlinNotificationManager: KotlinSingleNotificationManager { + fun notify(project: Project, sdks: Collection) { + notify(project, AbsentSdkAnnotationsNotification(sdks, getNotificationTitle(sdks), getNotificationString(sdks))) + } +} + trait KotlinSingleNotificationManager { fun notify(project: Project, notification: T) { val notificationsManager = NotificationsManager.getNotificationsManager() diff --git a/idea/src/org/jetbrains/jet/plugin/configuration/ui/AbsentJdkAnnotationsNotifications.java b/idea/src/org/jetbrains/jet/plugin/configuration/ui/AbsentJdkAnnotationsNotifications.java index 4ef6dfa1fa2..8ebc9b5668d 100644 --- a/idea/src/org/jetbrains/jet/plugin/configuration/ui/AbsentJdkAnnotationsNotifications.java +++ b/idea/src/org/jetbrains/jet/plugin/configuration/ui/AbsentJdkAnnotationsNotifications.java @@ -16,83 +16,111 @@ package org.jetbrains.jet.plugin.configuration.ui; -import com.intellij.openapi.compiler.CompilerManager; -import com.intellij.openapi.fileEditor.FileEditor; +import com.google.common.collect.Sets; +import com.intellij.ProjectTopics; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.AbstractProjectComponent; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.ModuleRootAdapter; +import com.intellij.openapi.roots.ModuleRootEvent; import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.JavaPsiFacade; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.ui.EditorNotificationPanel; -import com.intellij.ui.EditorNotifications; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter; +import com.intellij.util.messages.MessageBusConnection; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.plugin.JetFileType; +import org.jetbrains.jet.plugin.configuration.AbsentSdkAnnotationsNotificationManager; +import org.jetbrains.jet.plugin.configuration.ConfigureKotlinInProjectUtils; +import org.jetbrains.jet.plugin.configuration.ui.notifications.NotificationsPackage; +import org.jetbrains.jet.plugin.framework.KotlinFrameworkDetector; import org.jetbrains.jet.plugin.versions.KotlinRuntimeLibraryUtil; -public class AbsentJdkAnnotationsNotifications extends EditorNotifications.Provider { - private static final Key KEY = Key.create("add.kotlin.jdk.annotations"); +import java.util.Collection; +import java.util.Set; - private final Project project; +public class AbsentJdkAnnotationsNotifications extends AbstractProjectComponent { - public AbsentJdkAnnotationsNotifications(Project project) { - this.project = project; + protected AbsentJdkAnnotationsNotifications(Project project) { + super(project); } @Override - @Nullable - public EditorNotificationPanel createNotificationPanel(VirtualFile file, FileEditor fileEditor) { - if (file.getFileType() != JetFileType.INSTANCE) return null; - if (CompilerManager.getInstance(project).isExcludedFromCompilation(file)) return null; - - final Module module = ModuleUtil.findModuleForFile(file, project); - if (module == null) return null; - - GlobalSearchScope scope = module.getModuleWithDependenciesAndLibrariesScope(false); - if (JavaPsiFacade.getInstance(project).findClass("jet.JetObject", scope) == null) return null; - - Sdk sdk = ModuleRootManager.getInstance(module).getSdk(); - if (sdk == null) return null; - - return createMissingSdkAnnotationsPanelIfNeeded(module, sdk); - } - - private EditorNotificationPanel createMissingSdkAnnotationsPanelIfNeeded(final Module module, @NotNull Sdk sdk) { - final boolean isAndroidSdk = isAndroidSdk(sdk); - if (KotlinRuntimeLibraryUtil.jdkAnnotationsArePresent(module)) { - if (!isAndroidSdk || KotlinRuntimeLibraryUtil.androidSdkAnnotationsArePresent(module)) { - return null; - } - } - - EditorNotificationPanel panel = new EditorNotificationPanel(); - String sdkKind = isAndroidSdk ? "Android SDK" : "JDK"; - panel.setText("Kotlin external annotations for " + sdkKind + " are not set for '" + sdk.getName() + "'."); - panel.createActionLabel("Set up Kotlin " + sdkKind + " annotations", new Runnable() { + public void projectOpened() { + super.projectOpened(); + MessageBusConnection connection = myProject.getMessageBus().connect(); + connection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() { @Override - public void run() { - if (!KotlinRuntimeLibraryUtil.jdkAnnotationsArePresent(module)) { - KotlinRuntimeLibraryUtil.addJdkAnnotations(module); - } - if (isAndroidSdk && !KotlinRuntimeLibraryUtil.androidSdkAnnotationsArePresent(module)) { - KotlinRuntimeLibraryUtil.addAndroidSdkAnnotations(module); - } + public void enteredDumbMode() { + } + + @Override + public void exitDumbMode() { + showNotificationIfNeeded(); + } + }); + connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { + @Override + public void rootsChanged(ModuleRootEvent event) { + showNotificationIfNeeded(); } }); - return panel; + connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkVirtualFileListenerAdapter(new VirtualFileAdapter() { + @Override + public void fileDeleted(VirtualFileEvent event) { + showNotificationIfNeeded(); + } + + @Override + public void fileCreated(VirtualFileEvent event) { + showNotificationIfNeeded(); + } + + @Override + public void fileMoved(VirtualFileMoveEvent event) { + showNotificationIfNeeded(); + } + + @Override + public void fileCopied(VirtualFileCopyEvent event) { + showNotificationIfNeeded(); + } + })); } - @Override - public Key getKey() { - return KEY; + public void showNotificationIfNeeded() { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + Collection sdks = collectSdksWithoutAnnotations(); + if (sdks.isEmpty()) return; + AbsentSdkAnnotationsNotificationManager.instance$.notify(myProject, sdks); + } + }); } - private static boolean isAndroidSdk(@NotNull Sdk sdk) { - return sdk.getSdkType().getName().equals("Android SDK"); + @NotNull + private Collection collectSdksWithoutAnnotations() { + Set sdks = Sets.newHashSet(); + for (Module module : ConfigureKotlinInProjectUtils.getModulesWithKotlinFiles(myProject)) { + if (KotlinFrameworkDetector.isJavaKotlinModule(module)) { + Sdk sdk = ModuleRootManager.getInstance(module).getSdk(); + if (sdk != null && !isAnnotationsArePresent(sdk)) { + sdks.add(sdk); + } + } + } + return sdks; } -} + + private static boolean isAnnotationsArePresent(@NotNull Sdk sdk) { + if (!KotlinRuntimeLibraryUtil.jdkAnnotationsArePresent(sdk)) { + return false; + } + + boolean isAndroidSdk = NotificationsPackage.isAndroidSdk(sdk); + return !(isAndroidSdk && !KotlinRuntimeLibraryUtil.androidSdkAnnotationsArePresent(sdk)); + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/configuration/ui/notifications/AbsentSdkAnnotationsNotification.kt b/idea/src/org/jetbrains/jet/plugin/configuration/ui/notifications/AbsentSdkAnnotationsNotification.kt new file mode 100644 index 00000000000..6f1faff5721 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/configuration/ui/notifications/AbsentSdkAnnotationsNotification.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2010-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.plugin.configuration.ui.notifications + +import com.intellij.notification.Notification +import com.intellij.notification.NotificationListener +import com.intellij.notification.NotificationType +import com.intellij.openapi.projectRoots.Sdk +import org.jetbrains.jet.plugin.versions.KotlinRuntimeLibraryUtil +import javax.swing.event.HyperlinkEvent + +private val GROUP_ID = "Configure Kotlin: absent sdk annotations" + +public class AbsentSdkAnnotationsNotification(sdks: Collection, val title: String, val text: String) : + Notification( + GROUP_ID, + title, text, + NotificationType.WARNING, + AbsentAnnotationsListener(sdks) // Workaround for KT-4086 + ) +{ + override fun equals(obj: Any?) = obj is AbsentSdkAnnotationsNotification && text == obj.text && title == obj.title + + override fun hashCode(): Int = 31 * title.hashCode() + text.hashCode() +} + +fun isAndroidSdk(sdk: Sdk) = sdk.getSdkType().getName() == "Android SDK" + +fun getNotificationTitle(sdks: Collection) = "Kotlin external annotations for ${getSdkKind(sdks)} are not set for " + getSdkName(sdks) + +fun getNotificationString(sdks: Collection) = "Set up Kotlin ${getSdkKind(sdks)} annotations" + +private class AbsentAnnotationsListener(val sdks: Collection): NotificationListener { + override fun hyperlinkUpdate(notification: Notification, event: HyperlinkEvent) { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + if (event.getDescription() == "configure") { + for (sdk in sdks) { + if (!KotlinRuntimeLibraryUtil.jdkAnnotationsArePresent(sdk)) { + KotlinRuntimeLibraryUtil.addJdkAnnotations(sdk); + } + if (isAndroidSdk(sdk) && !KotlinRuntimeLibraryUtil.androidSdkAnnotationsArePresent(sdk)) { + KotlinRuntimeLibraryUtil.addAndroidSdkAnnotations(sdk); + } + } + } + notification.expire() + } + } +} + +private fun getSdkKind(sdks: Collection) = if (sdks.size() > 1) "SDK" else if (isAndroidSdk(sdks.first())) "Android SDK" else "JDK" + +private fun getSdkName(sdks: Collection) = if (sdks.size() > 1) "several SDKs" else "'${sdks.first().getName()}'" + + diff --git a/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java b/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java index b2ed9acf0e6..d571f35deb3 100644 --- a/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java +++ b/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java @@ -56,7 +56,6 @@ import org.jetbrains.jet.utils.PathUtil; import java.io.File; import java.io.IOException; import java.util.Collection; -import java.util.List; import java.util.Set; import static com.intellij.util.PathUtil.getLocalFile; @@ -95,19 +94,15 @@ public class KotlinRuntimeLibraryUtil { return badRoots; } - public static void addJdkAnnotations(@NotNull Module module) { - addAnnotations(module, PathUtil.getKotlinPathsForIdeaPlugin().getJdkAnnotationsPath()); + public static void addJdkAnnotations(@NotNull Sdk sdk) { + addAnnotations(sdk, PathUtil.getKotlinPathsForIdeaPlugin().getJdkAnnotationsPath()); } - public static void addAndroidSdkAnnotations(@NotNull Module module) { - addAnnotations(module, PathUtil.getKotlinPathsForIdeaPlugin().getAndroidSdkAnnotationsPath()); + public static void addAndroidSdkAnnotations(@NotNull Sdk sdk) { + addAnnotations(sdk, PathUtil.getKotlinPathsForIdeaPlugin().getAndroidSdkAnnotationsPath()); } - private static void addAnnotations(@NotNull Module module, @NotNull File annotationsPath) { - Sdk sdk = ModuleRootManager.getInstance(module).getSdk(); - if (sdk == null) { - return; - } + private static void addAnnotations(@NotNull Sdk sdk, @NotNull File annotationsPath) { if (annotationsPath.exists()) { VirtualFile jdkAnnotationsJar = LocalFileSystem.getInstance().findFileByIoFile(annotationsPath); if (jdkAnnotationsJar != null) { @@ -119,17 +114,15 @@ public class KotlinRuntimeLibraryUtil { } } - public static boolean jdkAnnotationsArePresent(@NotNull Module module) { - return areAnnotationsPresent(module, PathUtil.JDK_ANNOTATIONS_JAR); + public static boolean jdkAnnotationsArePresent(@NotNull Sdk sdk) { + return areAnnotationsPresent(sdk, PathUtil.JDK_ANNOTATIONS_JAR); } - public static boolean androidSdkAnnotationsArePresent(@NotNull Module module) { - return areAnnotationsPresent(module, PathUtil.ANDROID_SDK_ANNOTATIONS_JAR); + public static boolean androidSdkAnnotationsArePresent(@NotNull Sdk sdk) { + return areAnnotationsPresent(sdk, PathUtil.ANDROID_SDK_ANNOTATIONS_JAR); } - private static boolean areAnnotationsPresent(@NotNull Module module, @NotNull final String jarFileName) { - Sdk sdk = ModuleRootManager.getInstance(module).getSdk(); - if (sdk == null) return false; + private static boolean areAnnotationsPresent(@NotNull Sdk sdk, @NotNull final String jarFileName) { return ContainerUtil.exists(sdk.getRootProvider().getFiles(AnnotationOrderRootType.getInstance()), new Condition() { @Override