IJPL-156789 Rework the Language Services widget to show icons of running services

GitOrigin-RevId: 4250a3b436f9dc1c43f805b1f626f349d21bf32a
This commit is contained in:
Alexander Doroshko
2024-06-19 21:59:49 +02:00
committed by intellij-monorepo-bot
parent 629421de59
commit 73145926fc
4 changed files with 63 additions and 35 deletions

View File

@@ -3710,8 +3710,8 @@ e:com.intellij.platform.lang.lsWidget.LanguageServicePopupSection
- f:createWidgetAction():com.intellij.openapi.actionSystem.AnAction
- p:createWidgetInlineActions():java.util.List
- pa:createWidgetMainAction():com.intellij.openapi.actionSystem.AnAction
- getStatusBarText():java.lang.String
- getStatusBarTooltip():java.lang.String
- a:getStatusBarIcon():javax.swing.Icon
- a:getStatusBarTooltip():java.lang.String
- a:getWidgetActionLocation():com.intellij.platform.lang.lsWidget.LanguageServicePopupSection
- isError():Z
*a:com.intellij.platform.lang.lsWidget.LanguageServiceWidgetItemsProvider

View File

@@ -620,6 +620,7 @@ show.more=Show more
show.less=Show less
language.services.widget=Language Services
language.services.widget.tooltip.running.on.current.file.list=<html>Language Services running on current file:<ul>{0}</ul></html>
language.services.widget.section.running.on.current.file=Running on Current File
language.services.widget.section.running.on.other.files=Running on Other Files
language.services.widget.no.services=No Services

View File

@@ -10,38 +10,26 @@ import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.util.NlsActions
import com.intellij.openapi.util.NlsContexts
import com.intellij.platform.lang.lsWidget.internal.LanguageServiceWidgetActionsService
import com.intellij.ui.ExperimentalUI
import com.intellij.ui.LayeredIcon.Companion.layeredIcon
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import javax.swing.Icon
@ApiStatus.Experimental
abstract class LanguageServiceWidgetItem {
/**
* The default label for the status bar widget is the generic one: "Language Services".
*
* But if
* - this [LanguageServiceWidgetItem] is the only one in the [LanguageServicePopupSection.ForCurrentFile] popup section
* (only for this item the [widgetActionLocation] value is [LanguageServicePopupSection.ForCurrentFile])
* - and the [statusBarText] value is not `null`
*
* then the service-specific text will be shown in the status bar.
*
* If this item is not the only one in the [LanguageServicePopupSection.ForCurrentFile] popup section,
* or it is not in the [LanguageServicePopupSection.ForCurrentFile] popup section at all,
* then the [statusBarText] value is ignored.
* An icon to show in the status bar (size: 16x16).
* The Platform will colorize the icon to be status-bar-friendly in the current UI theme.
* The Platform will add an error mark to the icon if [isError] is `true`.
*/
open val statusBarText: @NlsContexts.StatusBarText String? = null
abstract val statusBarIcon: Icon
/**
* A tooltip for the status bar widget label.
* Used only if this item appears to be the only one in the [LanguageServicePopupSection.ForCurrentFile] popup section.
* Otherwise, it's ignored.
* @see statusBarText
* A tooltip for the status bar widget icon.
*/
open val statusBarTooltip: @NlsContexts.Tooltip String? = null
abstract val statusBarTooltip: @Nls String
/**
* If `true` then the Platform will add the error mark to the icon in the status bar,
@@ -55,7 +43,8 @@ abstract class LanguageServiceWidgetItem {
val mainAction = createWidgetMainAction()
if (isError) {
mainAction.templatePresentation.icon = mainAction.templatePresentation.icon?.let {
layeredIcon(arrayOf(it, AllIcons.Nodes.ErrorMark)) }
layeredIcon(arrayOf(it, AllIcons.Nodes.ErrorMark))
}
}
if (ExperimentalUI.isNewUI()) {

View File

@@ -2,6 +2,7 @@
package com.intellij.platform.lang.lsWidget.impl
import com.intellij.icons.AllIcons
import com.intellij.ide.ui.LafManager
import com.intellij.lang.LangBundle
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.project.DumbAware
@@ -9,7 +10,6 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.popup.ListPopup
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.wm.StatusBarWidget
import com.intellij.openapi.wm.impl.status.EditorBasedStatusBarPopup
@@ -17,8 +17,12 @@ import com.intellij.platform.lang.lsWidget.LanguageServicePopupSection.ForCurren
import com.intellij.platform.lang.lsWidget.LanguageServiceWidgetItem
import com.intellij.platform.lang.lsWidget.LanguageServiceWidgetItemsProvider
import com.intellij.ui.LayeredIcon
import com.intellij.ui.RowIcon
import com.intellij.util.IconUtil
import com.intellij.util.messages.MessageBusConnection
import com.intellij.util.ui.EmptyIcon
import com.intellij.util.ui.JBDimension
import com.intellij.util.ui.JBUI
import kotlinx.coroutines.CoroutineScope
import javax.swing.Icon
@@ -42,20 +46,55 @@ internal class LanguageServiceWidget(project: Project, scope: CoroutineScope) :
override fun getWidgetState(file: VirtualFile?): WidgetState {
if (!Registry.`is`("language.service.status.bar.widget")) return WidgetState.HIDDEN
val allItems = LanguageServiceWidgetItemsProvider.EP_NAME.extensionList.flatMap { it.createWidgetItems(project, file) }
// If there are more than maxIconsInStatusBar services, then not all icons show up in the status bar.
// Sorting helps to make sure that icons with an error marker go first.
val allItems = LanguageServiceWidgetItemsProvider.EP_NAME.extensionList
.flatMap { it.createWidgetItems(project, file) }
.sortedByDescending { it.isError }
cachedWidgetItems = allItems
if (allItems.isEmpty()) return WidgetState.HIDDEN
val fileSpecificItems = file?.let { allItems.filter { it.widgetActionLocation == ForCurrentFile } } ?: emptyList()
val singleFileSpecificItem = fileSpecificItems.singleOrNull()
val text = singleFileSpecificItem?.statusBarText ?: LangBundle.message("language.services.widget")
val maxToolbarTextLength = 30
val shortenedText = StringUtil.shortenTextWithEllipsis(text, maxToolbarTextLength, 0, true)
val tooltip = singleFileSpecificItem?.statusBarTooltip ?: if (text.length > maxToolbarTextLength) text else null
val isError = fileSpecificItems.any { it.isError } // or maybe `allItems.any { it.isError }`?
return WidgetState(tooltip, shortenedText, true).apply {
icon = if (isError) Icons.errorIcon else Icons.normalIcon
val widgetIcon = fileSpecificItems.takeIf { it.isNotEmpty() }?.let { createStatusBarIcon(it) }
?: AllIcons.Json.Object
val widgetText = when {
fileSpecificItems.size > maxIconsInStatusBar -> "+${fileSpecificItems.size - maxIconsInStatusBar + 1}"
else -> ""
}
@Suppress("DialogTitleCapitalization")
val tooltip = when {
fileSpecificItems.isEmpty() -> LangBundle.message("language.services.widget")
else -> LangBundle.message("language.services.widget.tooltip.running.on.current.file.list",
fileSpecificItems.joinToString(separator = "") { "<li>${it.statusBarTooltip}</li>" })
}
return WidgetState(tooltip, widgetText, true).apply {
icon = widgetIcon
}
}
private fun createStatusBarIcon(widgetItems: List<LanguageServiceWidgetItem>): Icon {
// either up to 3 icons or 2 icons and "+N" text
val items = if (widgetItems.size <= maxIconsInStatusBar) widgetItems else widgetItems.subList(0, maxIconsInStatusBar - 1)
if (items.size == 1) return getStatusBarFriendlyIcon(items[0])
val separatorIcon = EmptyIcon.create(3, 16)
val icons = items.flatMap { listOf(getStatusBarFriendlyIcon(it), separatorIcon) }.dropLast(1)
return RowIcon(*icons.toTypedArray())
}
private fun getStatusBarFriendlyIcon(item: LanguageServiceWidgetItem): Icon {
val statusBarFriendlyColor = when {
LafManager.getInstance().currentUIThemeLookAndFeel.isDark -> JBUI.CurrentTheme.StatusBar.Widget.FOREGROUND
else -> JBUI.CurrentTheme.StatusBar.Widget.FOREGROUND.brighter() // without `brighter()` icons are too noisy in light themes
}
val statusBarFriendlyIcon = IconUtil.colorize(item.statusBarIcon, statusBarFriendlyColor)
return when {
item.isError -> LayeredIcon.layeredIcon(arrayOf(statusBarFriendlyIcon, AllIcons.Nodes.ErrorMark))
else -> statusBarFriendlyIcon
}
}
@@ -101,8 +140,7 @@ internal class LanguageServiceWidget(project: Project, scope: CoroutineScope) :
override fun actionPerformed(e: AnActionEvent) {}
}
private object Icons {
val normalIcon: Icon = AllIcons.Json.Object
val errorIcon: Icon = LayeredIcon.layeredIcon(arrayOf(AllIcons.Json.Object, AllIcons.Nodes.ErrorMark))
private companion object {
const val maxIconsInStatusBar = 3
}
}