diff --git a/build.sbt b/build.sbt index bbd88cd..f6c92f3 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ lazy val scala3Version = "3.3.0" lazy val intellijVersion = "231.9392.1" // https://youtrack.jetbrains.com/articles/IDEA-A-2100661425/IntelliJ-IDEA-2023.1-Latest-Builds -lazy val pluginVersion = s"0.1.4-$intellijVersion" +lazy val pluginVersion = s"0.2.0-RC1-$intellijVersion" ThisBuild / version := pluginVersion diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 00414bc..34b13b3 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -2,7 +2,7 @@ org.bitlap.sbtDependencyAnalyzer Sbt Dependency Analyzer - 0.1.4-231.9392.1 + 0.2.0-RC1-231.9392.1 Bitlap @@ -17,32 +17,46 @@ + + + + + + - - + + + class="bitlap.sbt.analyzer.action.ProjectViewDependencyAnalyzerAction"> + class="bitlap.sbt.analyzer.action.SbtDependencyAnalyzerOpenConfigAction"> - + - + + + + + @@ -93,6 +107,13 @@ 0.2.0-RC1-231.9392.1 +
    +
  • 🎉Add locale zn_CN translation for notification.
  • +
  • 🎉Add plugin update notification.
  • +
  • 🎉Add What's New action in Help menu.
  • +
+

0.1.3-231.9392.1

  • Fix module recognition failure when analyzing cross-build projects for Scala2 and Scala3.
  • diff --git a/src/main/resources/messages/SbtDependencyAnalyzerBundle.properties b/src/main/resources/messages/SbtDependencyAnalyzerBundle.properties index 6e69ce3..6d04a6a 100644 --- a/src/main/resources/messages/SbtDependencyAnalyzerBundle.properties +++ b/src/main/resources/messages/SbtDependencyAnalyzerBundle.properties @@ -1,8 +1,16 @@ -sbt.dependency.analyzer.error=Please ensure to include the 'addDependencyTreePlugin' statement in 'plugins.sbt'. -sbt.dependency.analyzer.error.unknown=Unknown problem occurs: {0} / {1} / {2}, please report the problem to the developer. -sbt.dependency.analyzer.error.parse=Failed to parse the file: {0}, please report the problem to the developer. -sbt.dependency.analyzer.error.title=Failed to use Sbt Dependency Analyzer plugin. -sbt.dependency.analyzer.action.name=Analyze SBT Dependencies... -sbt.dependency.analyzer.notification.goto.plugins.sbt=Add and Goto plugins.sbt -sbt.dependency.analyzer.refresh.dependencies.text=Refresh SBT Dependencies -sbt.dependency.analyzer.refresh.dependencies.description=Refresh SBT dependencies \ No newline at end of file +analyzer.task.error.unknown.title=Please ensure to include the "addDependencyTreePlugin" statement in "plugins.sbt" +analyzer.task.error.unknown.text=Unknown problem occurs: {0} / {1} / {2}, please report the problem to the developer +analyzer.task.error.text=Failed to parse the file: {0}, please report the problem to the developer +analyzer.task.error.title=Failed to use Sbt Dependency Analyzer plugin +analyzer.action.name=Analyze SBT Dependencies... +analyzer.notification.gotoPluginsFile=Add and Goto "plugins.sbt" +analyzer.refresh.dependencies.text=Refresh SBT Dependencies +analyzer.refresh.dependencies.description=Refresh SBT dependencies +analyzer.updated.notification.title={0} plugin updated to v{1} +analyzer.updated.notification.text=Thank you for downloading \ + Sbt Dependency Analyzer! \ +
    Change notes (releases notes)\:
    \ +
    {1}
    +analyzer.updated.notification.gotoBrowser=Go to see +analyzer.action.WhatsNew.text=What''s New in {0} + diff --git a/src/main/resources/messages/SbtDependencyAnalyzerBundle_zh.properties b/src/main/resources/messages/SbtDependencyAnalyzerBundle_zh.properties new file mode 100644 index 0000000..0ed797a --- /dev/null +++ b/src/main/resources/messages/SbtDependencyAnalyzerBundle_zh.properties @@ -0,0 +1,15 @@ +analyzer.task.error.unknown.title=请确保在 “plugins.sbt” 中包含 “addDependencyTreePlugin” 语句 +analyzer.task.error.unknown.text=出现未知问题: {0} / {1} / {2}, 请向开发人员报告问题 +analyzer.task.error.title=无法使用 Sbt Dependency Analyzer 插件 +analyzer.task.error.text=无法分析文件: {0}, 请向开发人员报告问题 +analyzer.action.name=分析 SBT 依赖... +analyzer.notification.gotoPluginsFile=添加并打开 ”plugins.sbt“ +analyzer.refresh.dependencies.text=刷新 SBT 依赖 +analyzer.refresh.dependencies.description=刷新 SBT 依赖 +analyzer.updated.notification.title={0} 插件已更新为 v{1} +analyzer.updated.notification.text=感谢下载 \ + Sbt Dependency Analyzer! \ +
    更新说明 (releases notes)\:
    \ +
    {1}
    +analyzer.updated.notification.gotoBrowser=去看看 +analyzer.action.WhatsNew.text={0} 最新功能 \ No newline at end of file diff --git a/src/main/scala/bitlap/sbt/analyzer/Constants.scala b/src/main/scala/bitlap/sbt/analyzer/Constants.scala index 4638384..c488517 100644 --- a/src/main/scala/bitlap/sbt/analyzer/Constants.scala +++ b/src/main/scala/bitlap/sbt/analyzer/Constants.scala @@ -20,10 +20,10 @@ object Constants: final val Protobuf = "protobuf" - final val timeout = 10.minutes + final val Timeout = 10.minutes - final val intervalTimeout = 1010.milliseconds + final val IntervalTimeout = 1010.milliseconds - final val fileLifespan = 1000 * 60 * 60L + final val FileLifespan = 1000 * 60 * 60L end Constants diff --git a/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerBundle.scala b/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerBundle.scala index 12275db..642a604 100644 --- a/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerBundle.scala +++ b/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerBundle.scala @@ -1,21 +1,79 @@ package bitlap.sbt.analyzer +import java.util.* + import org.jetbrains.annotations.Nls import org.jetbrains.annotations.NotNull import org.jetbrains.annotations.PropertyKey -import com.intellij.DynamicBundle +import com.intellij.* +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.registry.Registry import SbtDependencyAnalyzerBundle.* -final class SbtDependencyAnalyzerBundle extends DynamicBundle(BUNDLE) +final class SbtDependencyAnalyzerBundle(private val pathToBundle: String) extends AbstractBundle(pathToBundle): + private val adaptedControl = ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_PROPERTIES) + + private lazy val adaptedBundle: AbstractBundle = { + val dynamicLocale = getDynamicLocale + if dynamicLocale != null then + if (dynamicLocale.toLanguageTag == Locale.ENGLISH.toLanguageTag) { + new AbstractBundle(pathToBundle) { + override def findBundle( + pathToBundle: String, + loader: ClassLoader, + control: ResourceBundle.Control + ): ResourceBundle = { + val dynamicBundle = ResourceBundle.getBundle(pathToBundle, dynamicLocale, loader, adaptedControl) + if dynamicBundle == null then super.findBundle(pathToBundle, loader, control) else dynamicBundle + } + } + } else null + else null + } + + def getAdaptedMessage(@PropertyKey(resourceBundle = BUNDLE) key: String, params: Any*): String = { + if (adaptedBundle != null) adaptedBundle.getMessage(key, params: _*) else getMessage(key, params: _*) + } + + override def findBundle( + pathToBundle: String, + loader: ClassLoader, + control: ResourceBundle.Control + ): ResourceBundle = + val dynamicLocale = getDynamicLocale + if dynamicLocale != null then + if forceFollowLanguagePack then ResourceBundle.getBundle(pathToBundle, dynamicLocale, loader, adaptedControl) + else ResourceBundle.getBundle(pathToBundle, dynamicLocale, loader, control) + else super.findBundle(pathToBundle, loader, control) + + end findBundle + +end SbtDependencyAnalyzerBundle object SbtDependencyAnalyzerBundle: + private val LOG = Logger.getInstance(classOf[SbtDependencyAnalyzerBundle]) + + private lazy val forceFollowLanguagePack: Boolean = { + Registry.get("bitlap.sbt.analyzer.SbtDependencyAnalyzerBundle").asBoolean() + } + + private lazy val getDynamicLocale: Locale = { + try { + DynamicBundle.getLocale + } catch { + case e: NoSuchMethodError => + LOG.debug("NoSuchMethodError: DynamicBundle.getLocale()") + null + } + } + + final val BUNDLE = "messages.SbtDependencyAnalyzerBundle" - final val BUNDLE = "messages.SbtDependencyAnalyzerBundle" - final val INSTANCE = new SbtDependencyAnalyzerBundle + final val INSTANCE = new SbtDependencyAnalyzerBundle(BUNDLE) @Nls def message(@NotNull @PropertyKey(resourceBundle = BUNDLE) key: String, @NotNull params: AnyRef*): String = - INSTANCE.getMessage(key, params: _*) + INSTANCE.getAdaptedMessage(key, params: _*) end SbtDependencyAnalyzerBundle diff --git a/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerContributor.scala b/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerContributor.scala index 818b7e0..3179f44 100644 --- a/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerContributor.scala +++ b/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerContributor.scala @@ -10,11 +10,12 @@ import scala.collection.mutable.ListBuffer import scala.concurrent.* import scala.jdk.CollectionConverters.* -import bitlap.sbt.analyzer.DependencyUtils.* -import bitlap.sbt.analyzer.component.* +import bitlap.sbt.analyzer.activity.* import bitlap.sbt.analyzer.model.* import bitlap.sbt.analyzer.parser.* import bitlap.sbt.analyzer.task.* +import bitlap.sbt.analyzer.util.{ DependencyUtils, Notifications } +import bitlap.sbt.analyzer.util.DependencyUtils.* import org.jetbrains.plugins.scala.project.ModuleExt import org.jetbrains.sbt.project.SbtProjectSystem @@ -247,7 +248,7 @@ object SbtDependencyAnalyzerContributor: private def isValidFile(file: String): Boolean = { if (isValid.get()) { val lastModified = Path.of(file).toFile.lastModified() - System.currentTimeMillis() <= lastModified + Constants.fileLifespan + System.currentTimeMillis() <= lastModified + Constants.FileLifespan } else { isValid.getAndSet(true) } @@ -382,11 +383,11 @@ object SbtDependencyAnalyzerContributor: } catch { case _: AnalyzerCommandNotFoundException => if (isNotifying.compareAndSet(false, true)) { - SbtDependencyAnalyzerNotifier.notifyAndAddDependencyTreePlugin(project) + Notifications.notifyAndAddDependencyTreePlugin(project) } break() case ue: AnalyzerCommandUnknownException => - SbtDependencyAnalyzerNotifier.notifyUnknownError(project, ue.command, ue.moduleId, ue.scope) + Notifications.notifyUnknownError(project, ue.command, ue.moduleId, ue.scope) break() case e => throw e diff --git a/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerPlugin.scala b/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerPlugin.scala new file mode 100644 index 0000000..918ec11 --- /dev/null +++ b/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerPlugin.scala @@ -0,0 +1,18 @@ +package bitlap.sbt.analyzer + +import com.intellij.ide.plugins.IdeaPluginDescriptor +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.extensions.PluginId + +/** @author + * 梦境迷离 + * @version 1.0,2023/9/1 + */ +object SbtDependencyAnalyzerPlugin { + + val PLUGIN_ID = "org.bitlap.sbtDependencyAnalyzer" + + val descriptor: IdeaPluginDescriptor = PluginManagerCore.getPlugin(PluginId.getId(PLUGIN_ID)) + + lazy val version: String = descriptor.getVersion +} diff --git a/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerAction.scala b/src/main/scala/bitlap/sbt/analyzer/action/SbtDependencyAnalyzerAction.scala similarity index 93% rename from src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerAction.scala rename to src/main/scala/bitlap/sbt/analyzer/action/SbtDependencyAnalyzerAction.scala index cb26829..9ffaf3e 100644 --- a/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerAction.scala +++ b/src/main/scala/bitlap/sbt/analyzer/action/SbtDependencyAnalyzerAction.scala @@ -1,11 +1,15 @@ -package bitlap.sbt.analyzer +package bitlap +package sbt +package analyzer +package action import scala.jdk.CollectionConverters.* +import bitlap.sbt.analyzer.* + import org.jetbrains.sbt.project.SbtProjectSystem -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.PlatformCoreDataKeys +import com.intellij.openapi.actionSystem.* import com.intellij.openapi.externalSystem.dependency.analyzer.* import com.intellij.openapi.externalSystem.model.* import com.intellij.openapi.externalSystem.model.project.* @@ -20,7 +24,7 @@ import com.intellij.openapi.module.Module */ final class ViewDependencyAnalyzerAction extends AbstractDependencyAnalyzerAction[ExternalSystemNode[?]]: - getTemplatePresentation.setText(SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.action.name")) + getTemplatePresentation.setText(SbtDependencyAnalyzerBundle.message("analyzer.action.name")) getTemplatePresentation.setIcon(SbtDependencyAnalyzerIcons.ICON) override def getDependencyScope(anActionEvent: AnActionEvent, selectedData: ExternalSystemNode[_]): String = @@ -67,7 +71,7 @@ end ViewDependencyAnalyzerAction final class ProjectViewDependencyAnalyzerAction extends AbstractDependencyAnalyzerAction[Module]: - getTemplatePresentation.setText(SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.action.name")) + getTemplatePresentation.setText(SbtDependencyAnalyzerBundle.message("analyzer.action.name")) getTemplatePresentation.setIcon(SbtDependencyAnalyzerIcons.ICON) override def getDependencyScope(anActionEvent: AnActionEvent, data: Module): String = null @@ -93,7 +97,7 @@ end ProjectViewDependencyAnalyzerAction final class ToolbarDependencyAnalyzerAction extends DependencyAnalyzerAction(): - getTemplatePresentation.setText(SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.action.name")) + getTemplatePresentation.setText(SbtDependencyAnalyzerBundle.message("analyzer.action.name")) getTemplatePresentation.setIcon(SbtDependencyAnalyzerIcons.ICON) private val viewAction = ViewDependencyAnalyzerAction() diff --git a/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerGoToAction.scala b/src/main/scala/bitlap/sbt/analyzer/action/SbtDependencyAnalyzerGoToAction.scala similarity index 94% rename from src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerGoToAction.scala rename to src/main/scala/bitlap/sbt/analyzer/action/SbtDependencyAnalyzerGoToAction.scala index 4e293f0..e8b0a3b 100644 --- a/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerGoToAction.scala +++ b/src/main/scala/bitlap/sbt/analyzer/action/SbtDependencyAnalyzerGoToAction.scala @@ -1,8 +1,14 @@ -package bitlap.sbt.analyzer +package bitlap +package sbt +package analyzer +package action import scala.jdk.CollectionConverters.* import scala.util.Try +import bitlap.sbt.analyzer.* +import bitlap.sbt.analyzer.util.DependencyUtils + import org.jetbrains.sbt.project.SbtProjectSystem import com.intellij.buildsystem.model.DeclaredDependency diff --git a/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerOpenConfigAction.scala b/src/main/scala/bitlap/sbt/analyzer/action/SbtDependencyAnalyzerOpenConfigAction.scala similarity index 68% rename from src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerOpenConfigAction.scala rename to src/main/scala/bitlap/sbt/analyzer/action/SbtDependencyAnalyzerOpenConfigAction.scala index 8f65c5e..802235d 100644 --- a/src/main/scala/bitlap/sbt/analyzer/SbtDependencyAnalyzerOpenConfigAction.scala +++ b/src/main/scala/bitlap/sbt/analyzer/action/SbtDependencyAnalyzerOpenConfigAction.scala @@ -1,11 +1,16 @@ -package bitlap.sbt.analyzer +package bitlap +package sbt +package analyzer +package action import org.jetbrains.sbt.project.SbtProjectSystem import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.externalSystem.dependency.analyzer.DependencyAnalyzerDependency as Dependency -import com.intellij.openapi.externalSystem.dependency.analyzer.DependencyAnalyzerView -import com.intellij.openapi.externalSystem.dependency.analyzer.ExternalSystemDependencyAnalyzerOpenConfigAction +import com.intellij.openapi.externalSystem.dependency.analyzer.{ + DependencyAnalyzerDependency as Dependency, + DependencyAnalyzerView, + ExternalSystemDependencyAnalyzerOpenConfigAction +} final class SbtDependencyAnalyzerOpenConfigAction extends ExternalSystemDependencyAnalyzerOpenConfigAction(SbtProjectSystem.Id): diff --git a/src/main/scala/bitlap/sbt/analyzer/SbtRefreshDependenciesAction.scala b/src/main/scala/bitlap/sbt/analyzer/action/SbtRefreshDependenciesAction.scala similarity index 73% rename from src/main/scala/bitlap/sbt/analyzer/SbtRefreshDependenciesAction.scala rename to src/main/scala/bitlap/sbt/analyzer/action/SbtRefreshDependenciesAction.scala index 566c7db..d6a160a 100644 --- a/src/main/scala/bitlap/sbt/analyzer/SbtRefreshDependenciesAction.scala +++ b/src/main/scala/bitlap/sbt/analyzer/action/SbtRefreshDependenciesAction.scala @@ -1,4 +1,7 @@ -package bitlap.sbt.analyzer +package bitlap +package sbt +package analyzer +package action import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.externalSystem.action.RefreshAllExternalProjectsAction @@ -10,11 +13,11 @@ import com.intellij.openapi.externalSystem.action.RefreshAllExternalProjectsActi final class SbtRefreshDependenciesAction extends RefreshAllExternalProjectsAction: getTemplatePresentation.setText( - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.refresh.dependencies.text") + SbtDependencyAnalyzerBundle.message("analyzer.refresh.dependencies.text") ) getTemplatePresentation.setDescription( - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.refresh.dependencies.description") + SbtDependencyAnalyzerBundle.message("analyzer.refresh.dependencies.description") ) override def beforeActionPerformedUpdate(e: AnActionEvent): Unit = diff --git a/src/main/scala/bitlap/sbt/analyzer/activity/BaseProjectActivity.scala b/src/main/scala/bitlap/sbt/analyzer/activity/BaseProjectActivity.scala new file mode 100644 index 0000000..9d10190 --- /dev/null +++ b/src/main/scala/bitlap/sbt/analyzer/activity/BaseProjectActivity.scala @@ -0,0 +1,34 @@ +package bitlap.sbt.analyzer.activity + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity + +import kotlin.coroutines.Continuation + +/** @author + * 梦境迷离 + * @version 1.0,2023/9/1 + */ +abstract class BaseProjectActivity(private val runOnlyOnce: Boolean = false) extends ProjectActivity { + private var veryFirstProjectOpening: Boolean = true + + override def execute(project: Project, continuation: Continuation[_ >: kotlin.Unit]): AnyRef = { + if ( + ApplicationManager.getApplication.isUnitTestMode || (runOnlyOnce && !veryFirstProjectOpening) || project.isDisposed + ) { + return continuation + } + // FIXME: should use continuation + veryFirstProjectOpening = false + if (onBeforeRunActivity(project)) { + onRunActivity(project) + } + continuation + } + + private def onBeforeRunActivity(project: Project): Boolean = true + + protected def onRunActivity(project: Project): Unit + +} diff --git a/src/main/scala/bitlap/sbt/analyzer/activity/PluginUpdateActivity.scala b/src/main/scala/bitlap/sbt/analyzer/activity/PluginUpdateActivity.scala new file mode 100644 index 0000000..ef0b47a --- /dev/null +++ b/src/main/scala/bitlap/sbt/analyzer/activity/PluginUpdateActivity.scala @@ -0,0 +1,125 @@ +package bitlap +package sbt +package analyzer +package activity + +import bitlap.sbt.analyzer.* + +import org.jetbrains.plugins.scala.project.Version + +import com.intellij.icons.AllIcons +import com.intellij.ide.BrowserUtil +import com.intellij.ide.plugins.IdeaPluginDescriptor +import com.intellij.ide.util.PropertiesComponent +import com.intellij.notification.* +import com.intellij.notification.impl.NotificationsManagerImpl +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.* +import com.intellij.openapi.ui.popup.Balloon +import com.intellij.openapi.util.* +import com.intellij.openapi.util.registry.Registry +import com.intellij.openapi.wm.IdeFrame +import com.intellij.ui.* +import com.intellij.ui.awt.RelativePoint +import com.intellij.ui.jcef.JBCefApp +import com.intellij.ui.scale.JBUIScale +import com.intellij.util.ui.JBUI + +/** @author + * 梦境迷离 + * @version 1.0,2023/9/1 + */ +object PluginUpdateActivity: + private val InitialVersion = "0.0.0" + private lazy val Log = Logger.getInstance(classOf[PluginUpdateActivity]) + private lazy val UpdateNotificationGroup = "Sbt.DependencyAnalyzer.UpdateNotification" + private lazy val VersionProperty = s"${SbtDependencyAnalyzerPlugin.PLUGIN_ID}.version" + + private class UrlAction(version: Version) + extends DumbAwareAction( + SbtDependencyAnalyzerBundle.message("analyzer.updated.notification.gotoBrowser"), + null, + AllIcons.General.Web + ) { + + override def actionPerformed(e: AnActionEvent) = { + + BrowserUtil.browse(WhatsNew.getReleaseNotes(version)) + } + } + +end PluginUpdateActivity + +final class PluginUpdateActivity extends BaseProjectActivity { + + import PluginUpdateActivity.* + import WhatsNew.* + + override def onRunActivity(project: Project) = { + checkUpdate(project) + } + + private def checkUpdate(project: Project): Unit = { + val plugin = SbtDependencyAnalyzerPlugin.descriptor + val versionString = plugin.getVersion + val properties = PropertiesComponent.getInstance() + val lastVersionString = properties.getValue(VersionProperty, InitialVersion) + if (versionString == lastVersionString) { + return + } + + val version = Version(versionString) + val lastVersion = Version(lastVersionString) + if (version == lastVersion) { + return + } + + // Simple handling of notifications + val isNewVersion = version > lastVersion + if (isNewVersion && showUpdateNotification(project, plugin, version)) { + properties.setValue(VersionProperty, versionString) + } + } + + private def showUpdateNotification( + project: Project, + plugin: IdeaPluginDescriptor, + version: Version + ): Boolean = { + val title = SbtDependencyAnalyzerBundle.message( + "analyzer.updated.notification.title", + plugin.getName, + version.presentation + ) + val partStyle = s"margin-top: ${JBUI.scale(8)}px;" + val content = SbtDependencyAnalyzerBundle.message( + "analyzer.updated.notification.text", + partStyle, + if (plugin.getChangeNotes == null) "
    " else plugin.getChangeNotes, + version.presentation + ) + + val notificationGroup = NotificationGroupManager.getInstance().getNotificationGroup(UpdateNotificationGroup) + if (notificationGroup == null) return false + + val notification = notificationGroup + .createNotification(content, NotificationType.INFORMATION) + .setTitle(title) + .setImportant(true) + .setIcon(SbtDependencyAnalyzerIcons.ICON) + + if (!canBrowseInHTMLEditor) { + notification.addAction(new UrlAction(version)) + } else { + + notification.whenExpired(() => BrowserUtil.browse(WhatsNew.getReleaseNotes(version))) + waitInterval(10000) + notification.expire() + } + + notification.notify(project) + + true + } +} diff --git a/src/main/scala/bitlap/sbt/analyzer/activity/WhatsNew.scala b/src/main/scala/bitlap/sbt/analyzer/activity/WhatsNew.scala new file mode 100644 index 0000000..7a3b3a8 --- /dev/null +++ b/src/main/scala/bitlap/sbt/analyzer/activity/WhatsNew.scala @@ -0,0 +1,66 @@ +package bitlap.sbt.analyzer.activity + +import java.util.* + +import bitlap.sbt.analyzer.SbtDependencyAnalyzerBundle + +import org.jetbrains.plugins.scala.project.Version + +import com.intellij.icons.AllIcons +import com.intellij.ide.BrowserUtil +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.fileEditor.impl.HTMLEditorProvider +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Conditions +import com.intellij.ui.jcef.JBCefApp +import com.intellij.util.ui.UIUtil + +/** @author + * 梦境迷离 + * @version 1.0,2023/9/2 + */ +object WhatsNew: + private lazy val Log = Logger.getInstance(classOf[WhatsNew.type]) + private val ReleaseNotes = "https://github.com/bitlap/intellij-sbt-dependency-analyzer/releases/tag/v" + + def canBrowseInHTMLEditor: Boolean = JBCefApp.isSupported + + def getReleaseNotes(version: Version): String = ReleaseNotes + version.presentation + + def browse(version: Version, project: Project): Unit = { + val url = ReleaseNotes + version.presentation + if (project != null && canBrowseInHTMLEditor) { + ApplicationManager.getApplication.invokeLater( + () => { + try { + HTMLEditorProvider.openEditor( + project, + SbtDependencyAnalyzerBundle + .message("analyzer.action.WhatsNew.text", "Sbt Dependency Analyzer"), + url, + // language=HTML + s"""
    + |
    Failed to load!
    + | + |
    """.stripMargin + ) + } catch { + case e: Throwable => + Log.warn("""Failed to load "What's New" page""", e) + BrowserUtil.browse(url) + } + }, + ModalityState.NON_MODAL, + Conditions.is(project.getDisposed) + ) + } else { + BrowserUtil.browse(url) + } + } + +end WhatsNew diff --git a/src/main/scala/bitlap/sbt/analyzer/activity/WhatsNewAction.scala b/src/main/scala/bitlap/sbt/analyzer/activity/WhatsNewAction.scala new file mode 100644 index 0000000..60c6d37 --- /dev/null +++ b/src/main/scala/bitlap/sbt/analyzer/activity/WhatsNewAction.scala @@ -0,0 +1,20 @@ +package bitlap +package sbt +package analyzer +package activity + +import org.jetbrains.plugins.scala.project.Version + +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.DumbAwareAction + +final class WhatsNewAction extends DumbAwareAction { + + getTemplatePresentation.setText( + SbtDependencyAnalyzerBundle.message("analyzer.action.WhatsNew.text", "Sbt Dependency Analyzer") + ) + + override def actionPerformed(e: AnActionEvent): Unit = { + WhatsNew.browse(Version(SbtDependencyAnalyzerPlugin.descriptor.getVersion), e.getProject) + } +} diff --git a/src/main/scala/bitlap/sbt/analyzer/package.scala b/src/main/scala/bitlap/sbt/analyzer/package.scala index 7a16b23..78fc6f9 100644 --- a/src/main/scala/bitlap/sbt/analyzer/package.scala +++ b/src/main/scala/bitlap/sbt/analyzer/package.scala @@ -74,9 +74,9 @@ def findModule(project: Project, projectPath: String): Module = { findModule(project, moduleNode.getData) } -def commandInterval(): Unit = { +def waitInterval(sleep: Long = Constants.IntervalTimeout.toMillis): Unit = { try { - Thread.sleep(Constants.intervalTimeout.toMillis) + Thread.sleep(sleep) } catch { case ignore: Throwable => } diff --git a/src/main/scala/bitlap/sbt/analyzer/parser/DOTDependencyParser.scala b/src/main/scala/bitlap/sbt/analyzer/parser/DOTDependencyParser.scala index 092105e..82c92b0 100644 --- a/src/main/scala/bitlap/sbt/analyzer/parser/DOTDependencyParser.scala +++ b/src/main/scala/bitlap/sbt/analyzer/parser/DOTDependencyParser.scala @@ -6,9 +6,9 @@ import java.util.concurrent.atomic.* import scala.collection.mutable import scala.jdk.CollectionConverters.* -import bitlap.sbt.analyzer.DependencyUtils -import bitlap.sbt.analyzer.DependencyUtils.* import bitlap.sbt.analyzer.model.* +import bitlap.sbt.analyzer.util.DependencyUtils +import bitlap.sbt.analyzer.util.DependencyUtils.* import org.jetbrains.sbt.language.utils.SbtDependencyCommon diff --git a/src/main/scala/bitlap/sbt/analyzer/parser/DOTUtil.scala b/src/main/scala/bitlap/sbt/analyzer/parser/DOTUtil.scala index a371848..264250b 100644 --- a/src/main/scala/bitlap/sbt/analyzer/parser/DOTUtil.scala +++ b/src/main/scala/bitlap/sbt/analyzer/parser/DOTUtil.scala @@ -6,8 +6,8 @@ import java.nio.file.Path import scala.util.Try import bitlap.sbt.analyzer.Constants -import bitlap.sbt.analyzer.component.SbtDependencyAnalyzerNotifier import bitlap.sbt.analyzer.model.ModuleContext +import bitlap.sbt.analyzer.util.Notifications import org.jetbrains.plugins.scala.extensions.inReadAction import org.jetbrains.plugins.scala.project.VirtualFileExt @@ -47,9 +47,9 @@ object DOTUtil { if (vfsFile != null) { VfsUtil.markDirtyAndRefresh(false, false, false, vfsFile) } else { - if (System.currentTimeMillis() - start > Constants.timeout.toMillis) { + if (System.currentTimeMillis() - start > Constants.Timeout.toMillis) { LOG.error(s"Cannot get dot file: $file") - SbtDependencyAnalyzerNotifier.notifyParseFileError(file) + Notifications.notifyParseFileError(file) return null } } @@ -61,7 +61,7 @@ object DOTUtil { } catch { case e: Throwable => - SbtDependencyAnalyzerNotifier.notifyParseFileError(file) + Notifications.notifyParseFileError(file) LOG.error(s"Cannot parse dot file: $file", e) null } diff --git a/src/main/scala/bitlap/sbt/analyzer/task/DependencyDotTask.scala b/src/main/scala/bitlap/sbt/analyzer/task/DependencyDotTask.scala index 5158e5b..998fb99 100644 --- a/src/main/scala/bitlap/sbt/analyzer/task/DependencyDotTask.scala +++ b/src/main/scala/bitlap/sbt/analyzer/task/DependencyDotTask.scala @@ -1,9 +1,11 @@ -package bitlap.sbt.analyzer.task +package bitlap +package sbt +package analyzer +package task -import bitlap.sbt.analyzer.* -import bitlap.sbt.analyzer.DependencyUtils.* import bitlap.sbt.analyzer.model.* import bitlap.sbt.analyzer.parser.* +import bitlap.sbt.analyzer.util.DependencyUtils.* import org.jetbrains.plugins.scala.project.ModuleExt diff --git a/src/main/scala/bitlap/sbt/analyzer/task/SbtShellDependencyAnalysisTask.scala b/src/main/scala/bitlap/sbt/analyzer/task/SbtShellDependencyAnalysisTask.scala index 457401a..4e35f7b 100644 --- a/src/main/scala/bitlap/sbt/analyzer/task/SbtShellDependencyAnalysisTask.scala +++ b/src/main/scala/bitlap/sbt/analyzer/task/SbtShellDependencyAnalysisTask.scala @@ -6,9 +6,9 @@ package task import scala.concurrent.* import bitlap.sbt.analyzer.* -import bitlap.sbt.analyzer.DependencyUtils.* import bitlap.sbt.analyzer.model.* import bitlap.sbt.analyzer.parser.* +import bitlap.sbt.analyzer.util.DependencyUtils.* import org.jetbrains.sbt.shell.SbtShellCommunication @@ -61,7 +61,7 @@ trait SbtShellDependencyAnalysisTask: parserTypeEnum.cmd, moduleId, scope, - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.error.title") + SbtDependencyAnalyzerBundle.message("analyzer.task.error.title") ) ) } @@ -69,7 +69,7 @@ trait SbtShellDependencyAnalysisTask: if (line.startsWith(s"[error]") && line.contains(parserTypeEnum.cmd) && !promise.isCompleted) { promise.failure( AnalyzerCommandNotFoundException( - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.error.title") + SbtDependencyAnalyzerBundle.message("analyzer.task.error.title") ) ) } else if (line.startsWith(s"[error]") && !promise.isCompleted) { @@ -78,7 +78,7 @@ trait SbtShellDependencyAnalysisTask: parserTypeEnum.cmd, moduleId, scope, - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.error.title") + SbtDependencyAnalyzerBundle.message("analyzer.task.error.title") ) ) } @@ -88,7 +88,7 @@ trait SbtShellDependencyAnalysisTask: ) .flatMap(_ => promise.future) - Await.result(result, Constants.timeout) + Await.result(result, Constants.Timeout) rootNode(file) } diff --git a/src/main/scala/bitlap/sbt/analyzer/task/SbtShellOutputAnalysisTask.scala b/src/main/scala/bitlap/sbt/analyzer/task/SbtShellOutputAnalysisTask.scala index 242a092..e550d4c 100644 --- a/src/main/scala/bitlap/sbt/analyzer/task/SbtShellOutputAnalysisTask.scala +++ b/src/main/scala/bitlap/sbt/analyzer/task/SbtShellOutputAnalysisTask.scala @@ -7,7 +7,7 @@ import scala.concurrent.* import bitlap.sbt.analyzer.* import bitlap.sbt.analyzer.Constants.* -import bitlap.sbt.analyzer.SbtUtils.getClass +import bitlap.sbt.analyzer.util.SbtUtils.getClass import org.jetbrains.sbt.shell.SbtShellCommunication @@ -30,14 +30,14 @@ trait SbtShellOutputAnalysisTask[T]: new StringBuilder(), SbtShellCommunication.messageAggregator ) - val res = Await.result(executed.map(_.result()), Constants.timeout) + val res = Await.result(executed.map(_.result()), Constants.Timeout) val result = res.split(Constants.Line_Separator).toList.filter(_.startsWith("[info]")) if (result.isEmpty) { log.warn("Sbt Dependency Analyzer cannot find any output lines") } // see https://github.com/JetBrains/intellij-scala/blob/idea232.x/sbt/sbt-impl/src/org/jetbrains/sbt/shell/communication.scala // 1 second between multiple commands - commandInterval() + waitInterval() result end getCommandOutputLines diff --git a/src/main/scala/bitlap/sbt/analyzer/DependencyUtils.scala b/src/main/scala/bitlap/sbt/analyzer/util/DependencyUtils.scala similarity index 99% rename from src/main/scala/bitlap/sbt/analyzer/DependencyUtils.scala rename to src/main/scala/bitlap/sbt/analyzer/util/DependencyUtils.scala index be3a0b9..49f251f 100644 --- a/src/main/scala/bitlap/sbt/analyzer/DependencyUtils.scala +++ b/src/main/scala/bitlap/sbt/analyzer/util/DependencyUtils.scala @@ -1,4 +1,7 @@ -package bitlap.sbt.analyzer +package bitlap +package sbt +package analyzer +package util import java.util.Collections import java.util.concurrent.atomic.AtomicLong diff --git a/src/main/scala/bitlap/sbt/analyzer/component/SbtDependencyAnalyzerNotifier.scala b/src/main/scala/bitlap/sbt/analyzer/util/Notifications.scala similarity index 79% rename from src/main/scala/bitlap/sbt/analyzer/component/SbtDependencyAnalyzerNotifier.scala rename to src/main/scala/bitlap/sbt/analyzer/util/Notifications.scala index bcbf980..98e18d0 100644 --- a/src/main/scala/bitlap/sbt/analyzer/component/SbtDependencyAnalyzerNotifier.scala +++ b/src/main/scala/bitlap/sbt/analyzer/util/Notifications.scala @@ -1,4 +1,4 @@ -package bitlap.sbt.analyzer.component +package bitlap.sbt.analyzer.util import java.nio.file.Path @@ -17,9 +17,9 @@ import com.intellij.openapi.vfs.VfsUtil /** SbtDependencyAnalyzer global notifier */ -object SbtDependencyAnalyzerNotifier { +object Notifications { - private lazy val GROUP = + private lazy val NotificationGroup = NotificationGroupManager.getInstance().getNotificationGroup("Sbt.DependencyAnalyzer.Notification") private def getTextForAnalyzer(project: Project): String = { @@ -38,10 +38,10 @@ object SbtDependencyAnalyzerNotifier { def notifyParseFileError(file: String): Unit = { // add notification when gets vfsFile timeout - val notification = GROUP + val notification = NotificationGroup .createNotification( - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.error.title"), - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.error.parse", file), + SbtDependencyAnalyzerBundle.message("analyzer.task.error.title"), + SbtDependencyAnalyzerBundle.message("analyzer.task.error.text", file), NotificationType.ERROR ) .setIcon(SbtDependencyAnalyzerIcons.ICON) @@ -50,10 +50,10 @@ object SbtDependencyAnalyzerNotifier { def notifyUnknownError(project: Project, command: String, moduleId: String, scope: DependencyScopeEnum): Unit = { // add notification - val notification = GROUP + val notification = NotificationGroup .createNotification( - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.error.title"), - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.error.unknown", moduleId, scope.toString, command), + SbtDependencyAnalyzerBundle.message("analyzer.task.error.title"), + SbtDependencyAnalyzerBundle.message("analyzer.task.error.unknown.text", moduleId, scope.toString, command), NotificationType.ERROR ) .setIcon(SbtDependencyAnalyzerIcons.ICON) @@ -66,17 +66,18 @@ object SbtDependencyAnalyzerNotifier { val pluginsSbtFile = VfsUtil.findRelativeFile(projectPath, "project", "plugins.sbt") // add notification - val notification = GROUP + val notification = NotificationGroup .createNotification( - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.error.title"), - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.error"), + SbtDependencyAnalyzerBundle.message("analyzer.task.error.title"), + SbtDependencyAnalyzerBundle.message("analyzer.task.error.unknown.title"), NotificationType.ERROR ) .setIcon(SbtDependencyAnalyzerIcons.ICON) + .setImportant(true) if (pluginsSbtFile != null) { notification.addAction( new NotificationAction( - SbtDependencyAnalyzerBundle.message("sbt.dependency.analyzer.notification.goto.plugins.sbt") + SbtDependencyAnalyzerBundle.message("analyzer.notification.gotoPluginsFile") ) { override def actionPerformed(e: AnActionEvent, notification: Notification): Unit = { val doc = FileDocumentManager.getInstance().getDocument(pluginsSbtFile) diff --git a/src/main/scala/bitlap/sbt/analyzer/SbtUtils.scala b/src/main/scala/bitlap/sbt/analyzer/util/SbtUtils.scala similarity index 96% rename from src/main/scala/bitlap/sbt/analyzer/SbtUtils.scala rename to src/main/scala/bitlap/sbt/analyzer/util/SbtUtils.scala index f6ac940..a090bba 100644 --- a/src/main/scala/bitlap/sbt/analyzer/SbtUtils.scala +++ b/src/main/scala/bitlap/sbt/analyzer/util/SbtUtils.scala @@ -1,7 +1,6 @@ -package bitlap.sbt.analyzer +package bitlap.sbt.analyzer.util -import java.io.{ BufferedInputStream, File, FileInputStream } -import java.io.File +import java.io.* import java.net.URI import java.util.Properties import java.util.jar.JarFile diff --git a/src/test/scala/bitlap/sbt/analyzer/DOTUtilSpec.scala b/src/test/scala/bitlap/sbt/analyzer/DOTUtilSpec.scala index 2477492..32f161b 100644 --- a/src/test/scala/bitlap/sbt/analyzer/DOTUtilSpec.scala +++ b/src/test/scala/bitlap/sbt/analyzer/DOTUtilSpec.scala @@ -5,9 +5,9 @@ import java.util.concurrent.atomic.AtomicInteger import scala.jdk.CollectionConverters.* -import bitlap.sbt.analyzer.DependencyUtils import bitlap.sbt.analyzer.model.* import bitlap.sbt.analyzer.parser.DOTUtil +import bitlap.sbt.analyzer.util.DependencyUtils import org.scalatest.flatspec.AnyFlatSpec