1+ package com.github.xepozz.php_dump.panel
2+
3+ import com.github.xepozz.php_dump.actions.CollapseTreeAction
4+ import com.github.xepozz.php_dump.actions.ExpandTreeAction
5+ import com.github.xepozz.php_dump.actions.OpenPhpSettingsAction
6+ import com.github.xepozz.php_dump.actions.RefreshAction
7+ import com.github.xepozz.php_dump.panel.tabs.CompositeWindowTabsState
8+ import com.github.xepozz.php_dump.services.CustomTreeDumperService
9+ import com.github.xepozz.php_dump.stubs.token_object.TokensList
10+ import com.github.xepozz.php_dump.tree.RootNode
11+ import com.github.xepozz.php_dump.tree.TokenNode
12+ import com.github.xepozz.php_dump.tree.TokensTreeStructure
13+ import com.intellij.ide.util.treeView.AbstractTreeStructure
14+ import com.intellij.openapi.Disposable
15+ import com.intellij.openapi.actionSystem.ActionManager
16+ import com.intellij.openapi.actionSystem.DefaultActionGroup
17+ import com.intellij.openapi.application.EDT
18+ import com.intellij.openapi.editor.markup.EffectType
19+ import com.intellij.openapi.editor.markup.HighlighterLayer
20+ import com.intellij.openapi.editor.markup.HighlighterTargetArea
21+ import com.intellij.openapi.editor.markup.TextAttributes
22+ import com.intellij.openapi.fileEditor.FileEditorManager
23+ import com.intellij.openapi.project.Project
24+ import com.intellij.openapi.ui.SimpleToolWindowPanel
25+ import com.intellij.pom.Navigatable
26+ import com.intellij.psi.PsiDocumentManager
27+ import com.intellij.ui.JBColor
28+ import com.intellij.ui.TreeUIHelper
29+ import com.intellij.ui.components.JBScrollPane
30+ import com.intellij.ui.tree.AsyncTreeModel
31+ import com.intellij.ui.tree.StructureTreeModel
32+ import com.intellij.ui.treeStructure.Tree
33+ import com.intellij.util.ui.tree.TreeUtil
34+ import kotlinx.coroutines.CoroutineScope
35+ import kotlinx.coroutines.Dispatchers
36+ import kotlinx.coroutines.launch
37+ import java.awt.BorderLayout
38+ import java.awt.GridLayout
39+ import javax.swing.JPanel
40+ import javax.swing.JProgressBar
41+ import javax.swing.SwingUtilities
42+ import javax.swing.tree.DefaultMutableTreeNode
43+ import javax.swing.tree.DefaultTreeModel
44+ import javax.swing.tree.TreePath
45+
46+ class CustomTreePanel (
47+ private val tabConfig : CompositeWindowTabsState .TabConfig ,
48+ private val project : Project ,
49+ ) :
50+ SimpleToolWindowPanel (false , false ),
51+ RefreshablePanel , Disposable {
52+ val fileEditorManager = FileEditorManager .getInstance(project)
53+ private val progressBar = JProgressBar ()
54+
55+ private val treeModel = StructureTreeModel (TokensTreeStructure (RootNode (null )), this )
56+ private val tree = Tree (DefaultTreeModel (DefaultMutableTreeNode ())).apply {
57+ setModel(AsyncTreeModel (treeModel, this @CustomTreePanel))
58+ isRootVisible = true
59+ showsRootHandles = true
60+
61+ TreeUIHelper .getInstance()
62+ .installTreeSpeedSearch(this , { path ->
63+ val treeNode = path.lastPathComponent as ? DefaultMutableTreeNode
64+ val tokenNode = treeNode?.userObject as ? TokenNode
65+
66+ tokenNode?.node?.value
67+ }, true )
68+ }
69+ val service: CustomTreeDumperService = project.getService(CustomTreeDumperService ::class .java)
70+
71+
72+ init {
73+ treeModel.invalidateAsync()
74+
75+ createToolbar()
76+ createContent()
77+
78+ addTreeListeners()
79+
80+ SwingUtilities .invokeLater { refreshData() }
81+ }
82+
83+ fun createToolbar () {
84+ val actionGroup = DefaultActionGroup ().apply {
85+ add(RefreshAction { refreshData() })
86+ addSeparator()
87+ add(ExpandTreeAction (tree))
88+ add(CollapseTreeAction (tree))
89+ add(OpenPhpSettingsAction ())
90+ }
91+
92+ val actionToolbar = ActionManager .getInstance().createActionToolbar(" Tree Toolbar" , actionGroup, false )
93+ actionToolbar.targetComponent = this
94+
95+ val toolBarPanel = JPanel (GridLayout ())
96+ toolBarPanel.add(actionToolbar.component)
97+
98+ toolbar = toolBarPanel
99+ }
100+
101+ private fun createContent () {
102+ val responsivePanel = JPanel (BorderLayout ())
103+ responsivePanel.add(progressBar, BorderLayout .NORTH )
104+ responsivePanel.add(JBScrollPane (tree), BorderLayout .CENTER )
105+
106+ setContent(responsivePanel)
107+ }
108+
109+ private fun addTreeListeners () {
110+ tree.addTreeSelectionListener { event ->
111+ event.path?.let { navigation(it) }
112+ }
113+ }
114+
115+ private fun navigation (closestPathForLocation : TreePath ) {
116+ val lastUserObject = TreeUtil .getLastUserObject(closestPathForLocation)
117+ if (lastUserObject is TokenNode ) {
118+ val fileEditor = FileEditorManager .getInstance(project)
119+ val textEditor = fileEditor.selectedTextEditor!!
120+ val psiFile = PsiDocumentManager .getInstance(project).getPsiFile(textEditor.document)
121+ val node = lastUserObject.node
122+ val element = psiFile?.findElementAt(node.pos)
123+ if (element is Navigatable ) {
124+ val markupModel = textEditor.markupModel
125+ val textAttributes = TextAttributes ().apply {
126+ effectType = EffectType .BOXED
127+ effectColor = JBColor .RED
128+ backgroundColor = null
129+ }
130+
131+ markupModel.removeAllHighlighters()
132+ markupModel.addRangeHighlighter(
133+ node.pos,
134+ node.endPos,
135+ HighlighterLayer .SELECTION + 1 ,
136+ textAttributes,
137+ HighlighterTargetArea .EXACT_RANGE
138+ )
139+ }
140+ }
141+ }
142+
143+ private fun refreshData () {
144+ CoroutineScope (Dispatchers .EDT ).launch {
145+ progressBar.setIndeterminate(true )
146+ progressBar.isVisible = true
147+ tree.emptyText.text = " Loading..."
148+
149+ val result = getViewData()
150+ tree.emptyText.text = " Nothing to show"
151+ rebuildTree(result)
152+
153+ progressBar.setIndeterminate(false )
154+ progressBar.isVisible = false
155+ }
156+ }
157+
158+ private fun rebuildTree (list : TokensList ? ) {
159+ val treeModel = StructureTreeModel <AbstractTreeStructure >(TokensTreeStructure (RootNode (list)), this )
160+ tree.setModel(AsyncTreeModel (treeModel, this ))
161+ tree.setRootVisible(false )
162+ treeModel.invalidateAsync()
163+
164+ TreeUtil .expandAll(tree)
165+ }
166+
167+ private suspend fun getViewData (): TokensList {
168+ val result = TokensList ()
169+ val editor = fileEditorManager.selectedTextEditor ? : return result
170+ val virtualFile = editor.virtualFile ? : return result
171+
172+ service.phpSnippet = tabConfig.snippet
173+ val runBlocking = service.dump(virtualFile)
174+ // println("result is $runBlocking")
175+
176+ return runBlocking as ? TokensList ? : result
177+ }
178+
179+ override fun refresh (project : Project , type : RefreshType ) {
180+ refreshData()
181+ }
182+
183+ override fun dispose () {
184+ }
185+ }
0 commit comments