Problems View refactoring - async highlighters support

GitOrigin-RevId: 3283032cd02cc3aa18129c521c79108787d0ecfd
This commit is contained in:
Mark Tkachenko
2021-09-10 16:38:16 +00:00
committed by intellij-monorepo-bot
parent 77ef50b99e
commit 9c3ab2e377
6 changed files with 62 additions and 27 deletions
@@ -0,0 +1,7 @@
package com.intellij.codeInsight.daemon.impl
import org.jetbrains.concurrency.Promise
interface AsyncDescriptionSupplier {
fun requestDescription() : Promise<String>
}
@@ -2,7 +2,6 @@
package com.intellij.analysis.problemsView.toolWindow
import com.intellij.analysis.problemsView.Problem
import com.intellij.analysis.problemsView.ProblemsListener
import com.intellij.analysis.problemsView.ProblemsProvider
import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.openapi.editor.ex.RangeHighlighterEx
@@ -10,16 +9,16 @@ import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vfs.VirtualFile
open class HighlightingFileRoot(panel: ProblemsViewPanel, val file: VirtualFile) : Root(panel) {
internal class HighlightingFileRoot(panel: ProblemsViewPanel, val file: VirtualFile) : Root(panel) {
private val problems = mutableSetOf<HighlightingProblem>()
private val filter = ProblemFilter(panel.state)
protected val provider = object : ProblemsProvider {
private val provider = object : ProblemsProvider {
override val project = panel.project
}
protected open val watcher = createWatcher(provider, this, file, HighlightSeverity.INFORMATION.myVal + 1)
private val watcher = createWatcher(provider, file)
init {
Disposer.register(this, provider)
@@ -45,10 +44,8 @@ open class HighlightingFileRoot(panel: ProblemsViewPanel, val file: VirtualFile)
else -> emptyList()
}
protected open fun createWatcher(provider: ProblemsProvider,
listener: ProblemsListener,
file: VirtualFile,
level: Int): HighlightingWatcher =
private fun createWatcher(provider: ProblemsProvider,
file: VirtualFile): HighlightingWatcher =
HighlightingWatcher(provider, this, file, HighlightSeverity.INFORMATION.myVal + 1)
override fun getOtherProblemCount() = 0
@@ -22,7 +22,7 @@ import com.intellij.util.ui.tree.TreeUtil
import org.jetbrains.annotations.Nls
import javax.swing.Icon
open class HighlightingPanel(project: Project, state: ProblemsViewState)
class HighlightingPanel(project: Project, state: ProblemsViewState)
: ProblemsViewPanel(project, ID, state, ProblemsViewBundle.messagePointer("problems.view.highlighting")),
FileEditorManagerListener, PowerSaveMode.Listener {
@@ -88,7 +88,7 @@ open class HighlightingPanel(project: Project, state: ProblemsViewState)
currentFile = ClientId.withClientId(myClientId) { findCurrentFile() }
}
val currentRoot
internal val currentRoot
get() = treeModel.root as? HighlightingFileRoot
var currentFile
@@ -106,7 +106,7 @@ open class HighlightingPanel(project: Project, state: ProblemsViewState)
powerSaveStateChanged()
}
protected open fun getRoot(file: VirtualFile): HighlightingFileRoot = HighlightingFileRoot(this, file)
internal fun getRoot(file: VirtualFile): HighlightingFileRoot = HighlightingFileRoot(this, file)
fun selectHighlighter(highlighter: RangeHighlighterEx) {
val problem = currentRoot?.findProblem(highlighter) ?: return
@@ -1,39 +1,69 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.analysis.problemsView.toolWindow
import com.intellij.CommonBundle
import com.intellij.analysis.problemsView.FileProblem
import com.intellij.analysis.problemsView.ProblemsProvider
import com.intellij.codeHighlighting.HighlightDisplayLevel
import com.intellij.codeInsight.daemon.HighlightDisplayKey
import com.intellij.codeInsight.daemon.impl.AsyncDescriptionSupplier
import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.ex.RangeHighlighterEx
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.AnimatedIcon
import com.intellij.xml.util.XmlStringUtil.escapeString
import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.Icon
open class HighlightingProblem(
internal class HighlightingProblem(
override val provider: ProblemsProvider,
override val file: VirtualFile,
private val highlighter: RangeHighlighterEx
val highlighter: RangeHighlighterEx
) : FileProblem {
private fun getIcon(level: HighlightDisplayLevel) = if (severity >= level.severity.myVal) level.icon else null
private fun getIcon(level: HighlightDisplayLevel): Icon? = when {
text.isEmpty() || asyncDescriptionRequested.get() -> AnimatedIcon.Default.INSTANCE
severity >= level.severity.myVal -> level.icon
else -> null
}
open val info: HighlightInfo?
private var asyncDescriptionRequested = AtomicBoolean(false)
private var loading = AtomicBoolean(false)
val info: HighlightInfo?
get() {
return HighlightInfo.fromRangeHighlighter(highlighter)
val info = HighlightInfo.fromRangeHighlighter(highlighter)
if (info is AsyncDescriptionSupplier) {
requestAsyncDescription(info)
}
return info
}
private fun requestAsyncDescription(info: AsyncDescriptionSupplier) {
if (!asyncDescriptionRequested.compareAndSet(false, true)) return
loading.set(true)
info.requestDescription().onSuccess {
// we do that to avoid Concurrent modification exception
ApplicationManager.getApplication().invokeLater {
val panel = ProblemsView.getSelectedPanel(provider.project) as? HighlightingPanel
panel?.currentRoot?.problemUpdated(this)
loading.set(false)
}
}
}
override val icon: Icon
get() = HighlightDisplayLevel.find(info?.severity)?.icon
?: getIcon(HighlightDisplayLevel.ERROR)
?: getIcon(HighlightDisplayLevel.WARNING)
?: HighlightDisplayLevel.WEAK_WARNING.icon
?: getIcon(HighlightDisplayLevel.ERROR)
?: getIcon(HighlightDisplayLevel.WARNING)
?: HighlightDisplayLevel.WEAK_WARNING.icon
override val text: String
get() {
val text = info?.description ?: return "Invalid"
val text = info?.description ?: return CommonBundle.getLoadingTreeNodeText()
val pos = text.indexOfFirst { StringUtil.isLineBreak(it) }
return if (pos < 0 || text.startsWith("<html>", ignoreCase = true)) text
else text.substring(0, pos) + StringUtil.ELLIPSIS
@@ -48,6 +78,7 @@ open class HighlightingProblem(
override val description: String?
get() {
val text = info?.description ?: return null
if (text.isEmpty()) return null
val pos = text.indexOfFirst { StringUtil.isLineBreak(it) }
return if (pos < 0 || text.startsWith("<html>", ignoreCase = true)) null
else "<html>" + StringUtil.join(StringUtil.splitByLines(escapeString(text)), "<br/>")
@@ -14,14 +14,14 @@ import com.intellij.openapi.editor.impl.event.MarkupModelListener
import com.intellij.openapi.vfs.VirtualFile
import java.lang.ref.WeakReference
open class HighlightingWatcher(
protected val provider: ProblemsProvider,
protected val listener: ProblemsListener,
protected val file: VirtualFile,
internal class HighlightingWatcher(
private val provider: ProblemsProvider,
private val listener: ProblemsListener,
private val file: VirtualFile,
private val level: Int)
: MarkupModelListener, Disposable {
protected val problems = mutableMapOf<RangeHighlighterEx, Problem>()
private val problems = mutableMapOf<RangeHighlighterEx, Problem>()
private var reference: WeakReference<MarkupModelEx>? = null
init {
@@ -62,7 +62,7 @@ open class HighlightingWatcher(
fun findProblem(highlighter: RangeHighlighterEx) = synchronized(problems) { problems[highlighter] }
protected open fun getHighlightingProblem(highlighter: RangeHighlighterEx): HighlightingProblem
private fun getHighlightingProblem(highlighter: RangeHighlighterEx): HighlightingProblem
= HighlightingProblem(provider, file, highlighter)
private fun getProblem(highlighter: RangeHighlighterEx) = when {
@@ -28,7 +28,7 @@ open class ProblemsViewState : BaseState() {
var sortByName by property(false)
@get:XCollection(style = XCollection.Style.v2)
val hideBySeverity: MutableSet<Int> by property(Collections.newSetFromMap(ConcurrentHashMap()), { it.isEmpty() })
val hideBySeverity: MutableSet<Int> by property(Collections.newSetFromMap(ConcurrentHashMap())) { it.isEmpty() }
}
@State(name = "ProblemsViewState", storages = [(Storage(value = StoragePathMacros.WORKSPACE_FILE))])