mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-238791: create tree visitor to update project view
GitOrigin-RevId: ce1619f07621087baf5cfdc8dd2086548eea4137
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4ccc9d206b
commit
3e40f1a28a
@@ -30,7 +30,7 @@ internal class FileNode(parent: Node, val file: VirtualFile) : Node(parent) {
|
||||
presentation.addText(" ${getLocationRelativeToUserHome(url)}", GRAYED_ATTRIBUTES)
|
||||
}
|
||||
val root = findAncestor(Root::class.java)
|
||||
val count = root?.getProblemsCount(file) ?: 0
|
||||
val count = root?.getFileProblemCount(file) ?: 0
|
||||
if (count > 0) {
|
||||
val text = ProblemsViewBundle.message("problems.view.file.problems", count)
|
||||
presentation.addText(" $text", GRAYED_ATTRIBUTES)
|
||||
|
||||
+41
-13
@@ -2,15 +2,16 @@
|
||||
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
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
|
||||
internal class HighlightingFileRoot(panel: ProblemsViewPanel, val file: VirtualFile)
|
||||
: Root(panel, ProblemFilter(panel.state)), ProblemsListener {
|
||||
internal class HighlightingFileRoot(panel: ProblemsViewPanel, val file: VirtualFile) : Root(panel) {
|
||||
|
||||
private val problems = mutableSetOf<HighlightingProblem>()
|
||||
private val filter = ProblemFilter(panel.state)
|
||||
|
||||
private val provider = object : ProblemsProvider {
|
||||
override val project = panel.project
|
||||
@@ -23,24 +24,51 @@ internal class HighlightingFileRoot(panel: ProblemsViewPanel, val file: VirtualF
|
||||
Disposer.register(provider, watcher)
|
||||
}
|
||||
|
||||
fun findProblemNode(highlighter: RangeHighlighterEx): ProblemNode? {
|
||||
val problem = watcher.findProblem(highlighter) ?: return null
|
||||
return super.findProblemNode(file, problem)
|
||||
fun findProblem(highlighter: RangeHighlighterEx) = watcher.findProblem(highlighter)
|
||||
|
||||
override fun getProblemCount() = synchronized(problems) { problems.count(filter) }
|
||||
|
||||
override fun getProblemFiles() = when (getProblemCount() > 0) {
|
||||
true -> listOf(file)
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
override fun getFileProblemCount(file: VirtualFile) = when (this.file == file) {
|
||||
true -> getProblemCount()
|
||||
else -> 0
|
||||
}
|
||||
|
||||
override fun getFileProblems(file: VirtualFile) = when (this.file == file) {
|
||||
true -> synchronized(problems) { problems.filter(filter) }
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
override fun getOtherProblemCount() = 0
|
||||
|
||||
override fun getOtherProblems(): Collection<Problem> = emptyList()
|
||||
|
||||
override fun problemAppeared(problem: Problem) {
|
||||
addProblems(file, problem)
|
||||
if (!ProblemsView.isProjectErrorsEnabled()) return
|
||||
if (problem is HighlightingProblem && problem.severity >= HighlightSeverity.ERROR.myVal) {
|
||||
HighlightingErrorsProvider.getInstance(problem.provider.project).problemsAppeared(file)
|
||||
}
|
||||
if (problem !is HighlightingProblem || problem.file != file) return
|
||||
notify(problem, synchronized(problems) { SetUpdateState.add(problem, problems) })
|
||||
if (!ProblemsView.isProjectErrorsEnabled() || problem.severity < HighlightSeverity.ERROR.myVal) return
|
||||
HighlightingErrorsProvider.getInstance(problem.provider.project).problemsAppeared(file)
|
||||
}
|
||||
|
||||
override fun problemDisappeared(problem: Problem) {
|
||||
removeProblems(file, problem)
|
||||
if (problem !is HighlightingProblem || problem.file != file) return
|
||||
notify(problem, synchronized(problems) { SetUpdateState.remove(problem, problems) })
|
||||
}
|
||||
|
||||
override fun problemUpdated(problem: Problem) {
|
||||
updateProblem(file, problem)
|
||||
if (problem !is HighlightingProblem || problem.file != file) return
|
||||
notify(problem, synchronized(problems) { SetUpdateState.update(problem, problems) })
|
||||
}
|
||||
|
||||
private fun notify(problem: Problem, state: SetUpdateState) = when (state) {
|
||||
SetUpdateState.ADDED -> super.problemAppeared(problem)
|
||||
SetUpdateState.REMOVED -> super.problemDisappeared(problem)
|
||||
SetUpdateState.UPDATED -> super.problemUpdated(problem)
|
||||
SetUpdateState.IGNORED -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-7
@@ -17,7 +17,7 @@ import com.intellij.openapi.fileEditor.*
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.SingleAlarm
|
||||
import com.intellij.util.ui.tree.TreeUtil.promiseSelectFirstLeaf
|
||||
import com.intellij.util.ui.tree.TreeUtil
|
||||
import javax.swing.Icon
|
||||
|
||||
internal class HighlightingPanel(project: Project, state: ProblemsViewState)
|
||||
@@ -28,6 +28,7 @@ internal class HighlightingPanel(project: Project, state: ProblemsViewState)
|
||||
|
||||
init {
|
||||
tree.showsRootHandles = false
|
||||
updateCurrentFile()
|
||||
project.messageBus.connect(this)
|
||||
.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, this)
|
||||
getApplication().messageBus.connect(this)
|
||||
@@ -59,11 +60,6 @@ internal class HighlightingPanel(project: Project, state: ProblemsViewState)
|
||||
if (selected) updateCurrentFile()
|
||||
}
|
||||
|
||||
fun selectHighlighter(highlighter: RangeHighlighterEx) {
|
||||
val root = treeModel.root as? HighlightingFileRoot
|
||||
root?.findProblemNode(highlighter)?.let { select(it) }
|
||||
}
|
||||
|
||||
override fun powerSaveStateChanged() {
|
||||
statusUpdateAlarm.cancelAndRequest(forceRun = true)
|
||||
updateToolWindowContent()
|
||||
@@ -83,11 +79,17 @@ internal class HighlightingPanel(project: Project, state: ProblemsViewState)
|
||||
else {
|
||||
if (root != null && root.file == file) return
|
||||
treeModel.root = HighlightingFileRoot(this, file)
|
||||
promiseSelectFirstLeaf(tree)
|
||||
TreeUtil.promiseSelectFirstLeaf(tree)
|
||||
}
|
||||
powerSaveStateChanged()
|
||||
}
|
||||
|
||||
fun selectHighlighter(highlighter: RangeHighlighterEx) {
|
||||
val root = treeModel.root as? HighlightingFileRoot ?: return
|
||||
val problem = root.findProblem(highlighter) ?: return
|
||||
TreeUtil.promiseSelect(tree, ProblemNodeFinder(problem))
|
||||
}
|
||||
|
||||
private fun findCurrentFile(): VirtualFile? {
|
||||
if (project.isDisposed) return null
|
||||
val fileEditor = FileEditorManager.getInstance(project)?.selectedEditor ?: return null
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
// Copyright 2000-2020 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.analysis.problemsView.FileProblem
|
||||
import com.intellij.analysis.problemsView.Problem
|
||||
import com.intellij.openapi.vfs.VfsUtil.isAncestor
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.ui.tree.TreeVisitor
|
||||
import com.intellij.util.ui.tree.TreeUtil
|
||||
import javax.swing.tree.TreePath
|
||||
|
||||
internal interface ProblemsTreeVisitor : TreeVisitor {
|
||||
override fun visit(path: TreePath) = when (val node = TreeUtil.getLastUserObject(path)) {
|
||||
is Root -> visitRoot(node)
|
||||
is FileNode -> visitFile(node)
|
||||
is ProblemNode -> visitProblem(node)
|
||||
else -> TreeVisitor.Action.SKIP_CHILDREN
|
||||
}
|
||||
|
||||
fun visitRoot(root: Root) = TreeVisitor.Action.CONTINUE
|
||||
fun visitFile(node: FileNode): TreeVisitor.Action
|
||||
fun visitProblem(node: ProblemNode) = TreeVisitor.Action.SKIP_CHILDREN
|
||||
}
|
||||
|
||||
|
||||
internal class FileNodeFinder(private val file: VirtualFile) : ProblemsTreeVisitor {
|
||||
override fun visitFile(node: FileNode) = when {
|
||||
node.file == file -> TreeVisitor.Action.INTERRUPT
|
||||
isAncestor(node.file, file, true) -> TreeVisitor.Action.CONTINUE
|
||||
else -> TreeVisitor.Action.SKIP_CHILDREN
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class ProblemNodeFinder(private val problem: Problem) : ProblemsTreeVisitor {
|
||||
override fun visitFile(node: FileNode) = when {
|
||||
problem !is FileProblem -> TreeVisitor.Action.SKIP_CHILDREN
|
||||
isAncestor(node.file, problem.file, false) -> TreeVisitor.Action.CONTINUE
|
||||
else -> TreeVisitor.Action.SKIP_CHILDREN
|
||||
}
|
||||
|
||||
override fun visitProblem(node: ProblemNode) = when (node.problem) {
|
||||
problem -> TreeVisitor.Action.INTERRUPT
|
||||
else -> TreeVisitor.Action.SKIP_CHILDREN
|
||||
}
|
||||
}
|
||||
+1
-10
@@ -23,7 +23,6 @@ import com.intellij.ui.content.Content;
|
||||
import com.intellij.ui.content.ContentManager;
|
||||
import com.intellij.ui.tree.AsyncTreeModel;
|
||||
import com.intellij.ui.tree.RestoreSelectionListener;
|
||||
import com.intellij.ui.tree.TreeVisitor;
|
||||
import com.intellij.ui.treeStructure.Tree;
|
||||
import com.intellij.util.EditSourceOnDoubleClickHandler;
|
||||
import com.intellij.util.EditSourceOnEnterKeyHandler;
|
||||
@@ -72,7 +71,7 @@ abstract class ProblemsViewPanel extends OnePixelSplitter implements Disposable,
|
||||
if (content == null) return;
|
||||
|
||||
Root root = myTreeModel.getRoot();
|
||||
int count = root == null ? 0 : root.getProblemsCount();
|
||||
int count = root == null ? 0 : root.getProblemCount();
|
||||
content.setDisplayName(getContentDisplayName(count));
|
||||
Icon icon = getToolWindowIcon(count);
|
||||
if (icon != null) window.setIcon(icon);
|
||||
@@ -324,14 +323,6 @@ abstract class ProblemsViewPanel extends OnePixelSplitter implements Disposable,
|
||||
getApplication().invokeLater(runnable, stateForComponent(this));
|
||||
}
|
||||
|
||||
void select(@NotNull Node node) {
|
||||
TreeUtil.promiseSelect(getTree(), createVisitor(node));
|
||||
}
|
||||
|
||||
@NotNull TreeVisitor createVisitor(@NotNull Node node) {
|
||||
return new TreeVisitor.ByTreePath<>(node.getPath(), o -> o);
|
||||
}
|
||||
|
||||
@NotNull Comparator<Node> createComparator() {
|
||||
return new NodeComparator(
|
||||
isNullableOrSelected(getSortFoldersFirst()),
|
||||
|
||||
+2
@@ -103,4 +103,6 @@ private class ProjectErrorsCollector(val project: Project) : ProblemsCollector {
|
||||
?.contents
|
||||
?.mapNotNull { it.component as? ProjectErrorsPanel }
|
||||
?.firstOrNull()
|
||||
?.treeModel
|
||||
?.root
|
||||
}
|
||||
|
||||
+15
-16
@@ -1,35 +1,34 @@
|
||||
// Copyright 2000-2020 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.analysis.problemsView.FileProblem
|
||||
import com.intellij.analysis.problemsView.Problem
|
||||
import com.intellij.analysis.problemsView.ProblemsListener
|
||||
import com.intellij.analysis.problemsView.ProblemsCollector
|
||||
import com.intellij.openapi.actionSystem.ToggleOptionAction.Option
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
|
||||
internal class ProjectErrorsPanel(project: Project, state: ProblemsViewState)
|
||||
: ProblemsViewPanel(project, state), ProblemsListener {
|
||||
|
||||
private val root = Root(this)
|
||||
: ProblemsViewPanel(project, state) {
|
||||
|
||||
init {
|
||||
treeModel.root = root
|
||||
treeModel.root = ProjectErrorsRoot(this)
|
||||
tree.emptyText.text = ProblemsViewBundle.message("problems.view.project.empty")
|
||||
}
|
||||
|
||||
override fun getDisplayName() = ProblemsViewBundle.message("problems.view.project")
|
||||
override fun getSortFoldersFirst(): Option? = null
|
||||
override fun getSortBySeverity(): Option? = null
|
||||
}
|
||||
|
||||
override fun problemAppeared(problem: Problem) {
|
||||
if (problem is FileProblem) root.addProblems(problem.file, problem)
|
||||
}
|
||||
|
||||
override fun problemDisappeared(problem: Problem) {
|
||||
if (problem is FileProblem) root.removeProblems(problem.file, problem)
|
||||
}
|
||||
private class ProjectErrorsRoot(panel: ProblemsViewPanel) : Root(panel) {
|
||||
private val collector = ProblemsCollector.getInstance(panel.project)
|
||||
|
||||
override fun problemUpdated(problem: Problem) {
|
||||
if (problem is FileProblem) root.updateProblem(problem.file, problem)
|
||||
}
|
||||
override fun getProblemCount() = collector.getProblemCount()
|
||||
override fun getProblemFiles() = collector.getProblemFiles()
|
||||
|
||||
override fun getFileProblemCount(file: VirtualFile) = collector.getFileProblemCount(file)
|
||||
override fun getFileProblems(file: VirtualFile) = collector.getFileProblems(file)
|
||||
|
||||
override fun getOtherProblemCount() = collector.getOtherProblemCount()
|
||||
override fun getOtherProblems() = collector.getOtherProblems()
|
||||
}
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
// Copyright 2000-2020 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.analysis.problemsView.FileProblem
|
||||
import com.intellij.analysis.problemsView.Problem
|
||||
import com.intellij.analysis.problemsView.ProblemsCollector
|
||||
import com.intellij.ide.projectView.PresentationData
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.ui.SimpleTextAttributes.REGULAR_ATTRIBUTES
|
||||
import com.intellij.ui.tree.LeafState
|
||||
import com.intellij.ui.tree.TreeVisitor.ByTreePath
|
||||
import com.intellij.util.ui.tree.TreeUtil.promiseExpand
|
||||
import com.intellij.util.ui.tree.TreeUtil
|
||||
import javax.swing.tree.TreePath
|
||||
|
||||
internal open class Root(val panel: ProblemsViewPanel, private val filter: ProblemFilter? = null)
|
||||
: Node(panel.project), Disposable {
|
||||
internal abstract class Root(val panel: ProblemsViewPanel)
|
||||
: Node(panel.project), ProblemsCollector, Disposable {
|
||||
|
||||
private val allProblems = mutableMapOf<VirtualFile, FileProblems>()
|
||||
private val nodes = mutableMapOf<VirtualFile, FileNode>()
|
||||
|
||||
override fun dispose() = Unit
|
||||
|
||||
@@ -26,55 +28,68 @@ internal open class Root(val panel: ProblemsViewPanel, private val filter: Probl
|
||||
presentation.addText(name, REGULAR_ATTRIBUTES)
|
||||
}
|
||||
|
||||
override fun getChildren(): Collection<Node> = synchronized(allProblems) {
|
||||
allProblems.values
|
||||
.filter { it.count(filter) > 0 }
|
||||
.map { it.getFileNode(getParentNode(it.file)) }
|
||||
override fun getChildren(): Collection<Node> {
|
||||
val children = mutableListOf<Node>()
|
||||
val files = getProblemFiles()
|
||||
synchronized(nodes) {
|
||||
files.forEach { children += nodes.computeIfAbsent(it) { file -> FileNode(this, file) } }
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
open fun getChildren(file: VirtualFile): Collection<Node> = synchronized(allProblems) {
|
||||
allProblems[file]?.getProblemNodes(filter) ?: emptyList()
|
||||
open fun getChildren(file: VirtualFile): Collection<Node> {
|
||||
val node = synchronized(nodes) { nodes[file] } ?: return emptyList()
|
||||
return getFileProblems(node.file).map { ProblemNode(node, it) }
|
||||
}
|
||||
|
||||
open fun getProblemsCount(): Int = synchronized(allProblems) {
|
||||
allProblems.values.sumBy { it.count(filter) }
|
||||
}
|
||||
|
||||
open fun getProblemsCount(file: VirtualFile): Int = synchronized(allProblems) {
|
||||
allProblems[file]?.count(filter) ?: 0
|
||||
}
|
||||
|
||||
open fun addProblems(file: VirtualFile, vararg problems: Problem) {
|
||||
val exist = synchronized(allProblems) { allProblems.contains(file) }
|
||||
val node = add(file, problems) ?: return
|
||||
onValidThread {
|
||||
panel.updateToolWindowContent()
|
||||
panel.treeModel.structureChanged(node.getPath())
|
||||
if (!exist) synchronized(allProblems) {
|
||||
allProblems[file]?.getFileNode(getParentNode(file))
|
||||
}?.let {
|
||||
promiseExpand(panel.tree, ByTreePath(it.getPath()) { any: Any? -> any })
|
||||
override fun problemAppeared(problem: Problem) = when (problem) {
|
||||
!is FileProblem -> structureChanged()
|
||||
else -> {
|
||||
val file = problem.file
|
||||
when (1 == getFileProblemCount(file)) {
|
||||
true -> fileAppeared(file)
|
||||
else -> fileUpdated(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun removeProblems(file: VirtualFile, vararg problems: Problem) {
|
||||
val node = remove(file, problems) ?: return
|
||||
onValidThread {
|
||||
panel.updateToolWindowContent()
|
||||
panel.treeModel.structureChanged(node.getPath())
|
||||
override fun problemDisappeared(problem: Problem) = when (problem) {
|
||||
!is FileProblem -> structureChanged()
|
||||
else -> {
|
||||
val file = problem.file
|
||||
when (0 == getFileProblemCount(file)) {
|
||||
true -> fileDisappeared(file)
|
||||
else -> fileUpdated(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun updateProblem(file: VirtualFile, problem: Problem) {
|
||||
val node = findProblemNode(file, problem) ?: return
|
||||
onValidThread {
|
||||
if (node.update()) panel.treeModel.nodeChanged(node.getPath())
|
||||
override fun problemUpdated(problem: Problem) {
|
||||
TreeUtil.promiseVisit(panel.tree, ProblemNodeFinder(problem)).onSuccess { path ->
|
||||
val node = TreeUtil.getLastUserObject(ProblemNode::class.java, path) ?: return@onSuccess
|
||||
onValidThread { if (node.update()) panel.treeModel.nodeChanged(node.getPath()) }
|
||||
}
|
||||
}
|
||||
|
||||
fun findProblemNode(file: VirtualFile, problem: Problem) = synchronized(allProblems) {
|
||||
allProblems[file]?.findProblemNode(problem)
|
||||
private fun fileAppeared(file: VirtualFile) {
|
||||
structureChanged()
|
||||
TreeUtil.promiseExpand(panel.tree, FileNodeFinder(file))
|
||||
}
|
||||
|
||||
private fun fileDisappeared(file: VirtualFile) {
|
||||
synchronized(nodes) { nodes.remove(file) }
|
||||
structureChanged()
|
||||
}
|
||||
|
||||
private fun fileUpdated(file: VirtualFile) {
|
||||
TreeUtil.promiseVisit(panel.tree, FileNodeFinder(file)).onSuccess { path ->
|
||||
path?.let { structureChanged(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun structureChanged(path: TreePath? = null) {
|
||||
panel.updateToolWindowContent()
|
||||
panel.treeModel.structureChanged(path)
|
||||
}
|
||||
|
||||
private fun onValidThread(task: () -> Unit) {
|
||||
@@ -82,28 +97,4 @@ internal open class Root(val panel: ProblemsViewPanel, private val filter: Probl
|
||||
if (panel.treeModel.isRoot(this)) task()
|
||||
}
|
||||
}
|
||||
|
||||
private fun add(file: VirtualFile, problems: Array<out Problem>): Node? = synchronized(allProblems) {
|
||||
val fileProblems = allProblems.computeIfAbsent(file) { FileProblems(it) }
|
||||
val count = fileProblems.count()
|
||||
problems.forEach { fileProblems.add(it) }
|
||||
if (count == fileProblems.count()) return null
|
||||
val parent = getParentNode(file)
|
||||
if (count > 0) return fileProblems.getFileNode(parent)
|
||||
return parent
|
||||
}
|
||||
|
||||
private fun remove(file: VirtualFile, problems: Array<out Problem>): Node? = synchronized(allProblems) {
|
||||
val fileProblems = allProblems[file] ?: return null
|
||||
val count = fileProblems.count()
|
||||
problems.forEach { fileProblems.remove(it) }
|
||||
if (count == fileProblems.count()) return null
|
||||
val parent = getParentNode(file)
|
||||
if (fileProblems.count() > 0) return fileProblems.getFileNode(parent)
|
||||
allProblems.remove(file)
|
||||
return parent
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER") // TODO: support file hierarchy
|
||||
private fun getParentNode(file: VirtualFile): Node = this
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user