diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensRefresher.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensRefresher.kt new file mode 100644 index 0000000..c9558bf --- /dev/null +++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensRefresher.kt @@ -0,0 +1,25 @@ +package com.chylex.intellij.inspectionlens + +import com.intellij.openapi.application.ApplicationManager + +object InspectionLensRefresher { + private var needsRefresh = false + + fun scheduleRefresh() { + synchronized(this) { + if (!needsRefresh) { + needsRefresh = true + ApplicationManager.getApplication().invokeLater(::refresh) + } + } + } + + private fun refresh() { + synchronized(this) { + if (needsRefresh) { + needsRefresh = false + InspectionLens.refresh() + } + } + } +} diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt index 1343fd2..1bd3873 100644 --- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt +++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt @@ -30,6 +30,10 @@ class EditorLensManager private constructor(private val editor: Editor) { private fun show(highlighterWithInfo: HighlighterWithInfo) { val (highlighter, info) = highlighterWithInfo + if (!highlighter.isValid) { + return + } + val existingLens = lenses[highlighter] if (existingLens != null) { if (existingLens.update(info)) { @@ -48,22 +52,10 @@ class EditorLensManager private constructor(private val editor: Editor) { } } - fun show(highlightersWithInfo: Collection<HighlighterWithInfo>) { - executeInBatchMode(highlightersWithInfo.size) { - highlightersWithInfo.forEach(::show) - } - } - private fun hide(highlighter: RangeHighlighter) { lenses.remove(highlighter)?.hide() } - fun hide(highlighters: Collection<RangeHighlighter>) { - executeInBatchMode(highlighters.size) { - highlighters.forEach(::hide) - } - } - fun hideAll() { executeInBatchMode(lenses.size) { lenses.values.forEach(EditorLens::hide) @@ -71,6 +63,28 @@ class EditorLensManager private constructor(private val editor: Editor) { } } + fun execute(commands: Collection<Command>) { + executeInBatchMode(commands.size) { + commands.forEach { it.apply(this) } + } + } + + sealed interface Command { + fun apply(lensManager: EditorLensManager) + + class Show(private val highlighter: HighlighterWithInfo) : Command { + override fun apply(lensManager: EditorLensManager) { + lensManager.show(highlighter) + } + } + + class Hide(private val highlighter: RangeHighlighter) : Command { + override fun apply(lensManager: EditorLensManager) { + lensManager.hide(highlighter) + } + } + } + /** * Batch mode affects both inlays and highlighters used for line colors. */ diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManagerDispatcher.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManagerDispatcher.kt new file mode 100644 index 0000000..6db54b7 --- /dev/null +++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManagerDispatcher.kt @@ -0,0 +1,41 @@ +package com.chylex.intellij.inspectionlens.editor + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.markup.RangeHighlighter + +class EditorLensManagerDispatcher(private val lensManager: EditorLensManager) { + private var queuedItems = mutableListOf<EditorLensManager.Command>() + private var isEnqueued = false + + fun show(highlighterWithInfo: HighlighterWithInfo) { + enqueue(EditorLensManager.Command.Show(highlighterWithInfo)) + } + + fun hide(highlighter: RangeHighlighter) { + enqueue(EditorLensManager.Command.Hide(highlighter)) + } + + private fun enqueue(item: EditorLensManager.Command) { + 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<EditorLensManager.Command> + + synchronized(this) { + itemsToProcess = queuedItems + queuedItems = mutableListOf() + isEnqueued = false + } + + lensManager.execute(itemsToProcess) + } +} diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensMarkupModelListener.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensMarkupModelListener.kt index b368f59..8a2fa22 100644 --- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensMarkupModelListener.kt +++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensMarkupModelListener.kt @@ -1,6 +1,5 @@ package com.chylex.intellij.inspectionlens.editor -import com.chylex.intellij.inspectionlens.utils.DebouncingInvokeOnDispatchThread import com.intellij.codeInsight.daemon.impl.HighlightInfo import com.intellij.lang.annotation.HighlightSeverity import com.intellij.openapi.Disposable @@ -17,10 +16,7 @@ import com.intellij.openapi.util.Key * Listens for inspection highlights and reports them to [EditorLensManager]. */ internal class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelListener { - private val lensManager = EditorLensManager.getOrCreate(editor) - - private val showOnDispatchThread = DebouncingInvokeOnDispatchThread(lensManager::show) - private val hideOnDispatchThread = DebouncingInvokeOnDispatchThread(lensManager::hide) + private val lensManagerDispatcher = EditorLensManagerDispatcher(EditorLensManager.getOrCreate(editor)) override fun afterAdded(highlighter: RangeHighlighterEx) { showIfValid(highlighter) @@ -32,18 +28,18 @@ internal class LensMarkupModelListener private constructor(editor: Editor) : Mar override fun beforeRemoved(highlighter: RangeHighlighterEx) { if (getFilteredHighlightInfo(highlighter) != null) { - hideOnDispatchThread.enqueue(highlighter) + lensManagerDispatcher.hide(highlighter) } } private fun showIfValid(highlighter: RangeHighlighter) { - runWithHighlighterIfValid(highlighter, showOnDispatchThread::enqueue, ::showAsynchronously) + runWithHighlighterIfValid(highlighter, lensManagerDispatcher::show, ::showAsynchronously) } private fun showAsynchronously(highlighterWithInfo: HighlighterWithInfo.Async) { highlighterWithInfo.requestDescription { if (highlighterWithInfo.highlighter.isValid && highlighterWithInfo.hasDescription) { - showOnDispatchThread.enqueue(highlighterWithInfo) + lensManagerDispatcher.show(highlighterWithInfo) } } } diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt index 412c7c0..e452a4f 100644 --- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt +++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt @@ -1,7 +1,6 @@ package com.chylex.intellij.inspectionlens.editor -import com.chylex.intellij.inspectionlens.InspectionLens -import com.chylex.intellij.inspectionlens.utils.DebouncingInvokeOnDispatchThread +import com.chylex.intellij.inspectionlens.InspectionLensRefresher import com.intellij.lang.annotation.HighlightSeverity import com.intellij.spellchecker.SpellCheckerSeveritiesProvider import com.intellij.ui.ColorUtil @@ -47,14 +46,12 @@ enum class LensSeverity(baseColor: Color, lightThemeDarkening: Int, darkThemeBri SpellCheckerSeveritiesProvider.TYPO to TYPO, )) - private val refreshLater = DebouncingInvokeOnDispatchThread<Unit> { InspectionLens.refresh() } - /** * Registers a mapping from a [HighlightSeverity] to a [LensSeverity], and refreshes all open editors. */ internal fun registerMapping(severity: HighlightSeverity, lensSeverity: LensSeverity) { if (mapping.put(severity, lensSeverity) != lensSeverity) { - refreshLater.enqueue(Unit) + InspectionLensRefresher.scheduleRefresh() } } diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/utils/DebouncingInvokeOnDispatchThread.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/utils/DebouncingInvokeOnDispatchThread.kt deleted file mode 100644 index cae2a45..0000000 --- a/src/main/kotlin/com/chylex/intellij/inspectionlens/utils/DebouncingInvokeOnDispatchThread.kt +++ /dev/null @@ -1,32 +0,0 @@ -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) - } -}