mirror of
https://github.com/chylex/IntelliJ-Inspection-Lens.git
synced 2025-04-23 03:15:46 +02:00
Fix markup model listener accessing UI from non-EDT threads in 2023.2 EAP
Closes #17
This commit is contained in:
parent
c993b4f203
commit
eb2faa2518
src/main/kotlin/com/chylex/intellij/inspectionlens
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user