Compare commits

...

2 Commits

6 changed files with 76 additions and 37 deletions

View File

@ -50,7 +50,7 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
private val inlays = mutableMapOf<RangeHighlighter, Inlay<LensRenderer>>()
fun show(highlighterWithInfo: HighlighterWithInfo) {
private fun show(highlighterWithInfo: HighlighterWithInfo) {
val (highlighter, info) = highlighterWithInfo
val currentInlay = inlays[highlighter]
if (currentInlay != null && currentInlay.isValid) {
@ -73,10 +73,14 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
executeInInlayBatchMode(highlightersWithInfo.size) { highlightersWithInfo.forEach(::show) }
}
fun hide(highlighter: RangeHighlighter) {
private fun hide(highlighter: RangeHighlighter) {
inlays.remove(highlighter)?.dispose()
}
fun hideAll(highlighters: Collection<RangeHighlighter>) {
executeInInlayBatchMode(highlighters.size) { highlighters.forEach(::hide) }
}
fun hideAll() {
executeInInlayBatchMode(inlays.size) { inlays.values.forEach(Inlay<*>::dispose) }
inlays.clear()

View File

@ -1,6 +1,7 @@
package com.chylex.intellij.inspectionlens.editor
import com.chylex.intellij.inspectionlens.InspectionLensPluginDisposableService
import com.chylex.intellij.inspectionlens.utils.DebouncingInvokeOnDispatchThread
import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.openapi.application.ApplicationManager
@ -21,6 +22,9 @@ import com.jetbrains.rd.util.lifetime.intersect
class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelListener {
private val lens = EditorInlayLensManager.getOrCreate(editor)
private val showOnDispatchThread = DebouncingInvokeOnDispatchThread(lens::showAll)
private val hideOnDispatchThread = DebouncingInvokeOnDispatchThread(lens::hideAll)
override fun afterAdded(highlighter: RangeHighlighterEx) {
showIfValid(highlighter)
}
@ -30,35 +34,19 @@ class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelL
}
override fun beforeRemoved(highlighter: RangeHighlighterEx) {
lens.hide(highlighter)
if (getFilteredHighlightInfo(highlighter) != null) {
hideOnDispatchThread.enqueue(highlighter)
}
}
private fun showIfValid(highlighter: RangeHighlighter) {
runWithHighlighterIfValid(highlighter, lens::show, ::showAsynchronously)
}
private fun showAllValid(highlighters: Array<RangeHighlighter>) {
val immediateHighlighters = mutableListOf<HighlighterWithInfo>()
for (highlighter in highlighters) {
runWithHighlighterIfValid(highlighter, immediateHighlighters::add, ::showAsynchronously)
}
lens.showAll(immediateHighlighters)
runWithHighlighterIfValid(highlighter, showOnDispatchThread::enqueue, ::showAsynchronously)
}
private fun showAsynchronously(highlighterWithInfo: HighlighterWithInfo.Async) {
highlighterWithInfo.requestDescription {
if (highlighterWithInfo.highlighter.isValid && highlighterWithInfo.hasDescription) {
val application = ApplicationManager.getApplication()
if (application.isDispatchThread) {
lens.show(highlighterWithInfo)
}
else {
application.invokeLater {
lens.show(highlighterWithInfo)
}
}
showOnDispatchThread.enqueue(highlighterWithInfo)
}
}
}
@ -66,15 +54,12 @@ class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelL
companion object {
private val MINIMUM_SEVERITY = HighlightSeverity.TEXT_ATTRIBUTES.myVal + 1
private fun getHighlightInfoIfValid(highlighter: RangeHighlighter): HighlightInfo? {
return if (highlighter.isValid)
HighlightInfo.fromRangeHighlighter(highlighter)?.takeIf { it.severity.myVal >= MINIMUM_SEVERITY }
else
null
private fun getFilteredHighlightInfo(highlighter: RangeHighlighter): HighlightInfo? {
return HighlightInfo.fromRangeHighlighter(highlighter)?.takeIf { it.severity.myVal >= MINIMUM_SEVERITY }
}
private inline fun runWithHighlighterIfValid(highlighter: RangeHighlighter, actionForImmediate: (HighlighterWithInfo) -> Unit, actionForAsync: (HighlighterWithInfo.Async) -> Unit) {
val info = getHighlightInfoIfValid(highlighter)
val info = highlighter.takeIf { it.isValid }?.let(::getFilteredHighlightInfo)
if (info != null) {
processHighlighterWithInfo(HighlighterWithInfo.from(highlighter, info), actionForImmediate, actionForAsync)
}
@ -91,7 +76,7 @@ class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelL
/**
* Attaches a new [LensMarkupModelListener] to the document model of the provided [TextEditor], and reports all existing inspection highlights to [EditorInlayLensManager].
*
*
* The [LensMarkupModelListener] will be disposed when either the [TextEditor] is disposed, or via [InspectionLensPluginDisposableService] when the plugin is unloaded.
*/
fun install(textEditor: TextEditor) {
@ -103,7 +88,10 @@ class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelL
val listener = LensMarkupModelListener(editor)
markupModel.addMarkupModelListener(pluginLifetime.intersect(editorLifetime).createNestedDisposable(), listener)
listener.showAllValid(markupModel.allHighlighters)
for (highlighter in markupModel.allHighlighters) {
listener.showIfValid(highlighter)
}
}
}
}

View File

@ -6,7 +6,6 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.Inlay
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.text.StringUtil
import java.awt.Font
import java.awt.Graphics
import java.awt.Rectangle
@ -31,7 +30,7 @@ class LensRenderer(info: HighlightInfo) : HintRenderer(null) {
}
override fun getTextAttributes(editor: Editor): TextAttributes {
return ATTRIBUTES_SINGLETON.also { it.foregroundColor = severity.color }
return severity.colorAttributes
}
override fun useEditorFont(): Boolean {
@ -39,8 +38,6 @@ class LensRenderer(info: HighlightInfo) : HintRenderer(null) {
}
private companion object {
private val ATTRIBUTES_SINGLETON = TextAttributes(null, null, null, null, Font.ITALIC)
private fun getValidDescriptionText(text: String?): String {
return if (text.isNullOrBlank()) " " else addMissingPeriod(convertHtmlToText(text))
}

View File

@ -4,6 +4,7 @@ import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.ui.ColorUtil
import com.intellij.ui.JBColor
import java.awt.Color
import java.awt.Font
/**
* Determines properties of inspection lenses based on severity.
@ -16,12 +17,14 @@ enum class LensSeverity(baseColor: Color, lightThemeDarkening: Int, darkThemeBri
SERVER_PROBLEM (Color(176, 97, 0), lightThemeDarkening = 4, darkThemeBrightening = 2),
OTHER (Color(128, 128, 128), lightThemeDarkening = 1, darkThemeBrightening = 2);
val color: JBColor
val colorAttributes: LensSeverityTextAttributes
init {
val lightThemeColor = ColorUtil.saturate(ColorUtil.darker(baseColor, lightThemeDarkening), 1)
val darkThemeColor = ColorUtil.desaturate(ColorUtil.brighter(baseColor, darkThemeBrightening), 2)
color = JBColor(lightThemeColor, darkThemeColor)
val textColor = JBColor(lightThemeColor, darkThemeColor)
colorAttributes = LensSeverityTextAttributes(foregroundColor = textColor, fontStyle = Font.ITALIC)
}
companion object {

View File

@ -0,0 +1,15 @@
package com.chylex.intellij.inspectionlens.editor
import com.intellij.openapi.editor.markup.UnmodifiableTextAttributes
import com.intellij.ui.JBColor
import java.awt.Font
class LensSeverityTextAttributes(private val foregroundColor: JBColor, private val fontStyle: Int = Font.PLAIN) : UnmodifiableTextAttributes() {
override fun getForegroundColor(): JBColor {
return foregroundColor
}
override fun getFontType(): Int {
return fontStyle
}
}

View File

@ -0,0 +1,32 @@
package com.chylex.intellij.inspectionlens.utils
import com.intellij.openapi.application.ApplicationManager
class DebouncingInvokeOnDispatchThread<T>(private val action: (List<T>) -> Unit) {
private var queuedItems = mutableListOf<T>()
private var isEnqueued = false
fun enqueue(item: T) {
synchronized(this) {
queuedItems.add(item)
// Enqueue even if already on dispatch thread to debounce consecutive calls.
if (!isEnqueued) {
isEnqueued = true
ApplicationManager.getApplication().invokeLater(::process)
}
}
}
private fun process() {
var itemsToProcess: List<T>
synchronized(this) {
itemsToProcess = queuedItems
queuedItems = mutableListOf()
isEnqueued = false
}
action(itemsToProcess)
}
}