Skip to content

Commit e435187

Browse files
committed
feat(bridge): add StylingViewFunctionProvider for CSS file handling #319
Introduce StylingViewFunctionProvider to manage CSS and SCSS file structures. Refactor StructureCommandUtil for better code reuse and maintainability. Add necessary CSS plugins to the build configuration.
1 parent a8f2284 commit e435187

File tree

7 files changed

+164
-96
lines changed

7 files changed

+164
-96
lines changed

build.gradle.kts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,12 +494,26 @@ project(":java") {
494494
}
495495
}
496496

497+
498+
val cssPlugins = listOf(
499+
"com.intellij.css",
500+
"org.jetbrains.plugins.sass",
501+
"org.jetbrains.plugins.less",
502+
// Needed for tests-only
503+
//"org.jetbrains.plugins.stylus:233.11799.172",
504+
"org.intellij.plugins.postcss",
505+
// Needed for tests-only
506+
//"com.jetbrains.plugins.Jade:$targetVersion",
507+
)
508+
497509
project(":javascript") {
498510
dependencies {
499511
intellijPlatform {
500512
intellijIde(prop("ideaVersion"))
501513
intellijPlugins(ideaPlugins)
502514
intellijPlugins(javaScriptPlugins)
515+
intellijPlugins(cssPlugins)
516+
// intellijPlugins("intellij.webpack")
503517
testFramework(TestFrameworkType.Plugin.JavaScript)
504518
}
505519

@@ -618,6 +632,7 @@ project(":exts:ext-vue") {
618632
implementation(project(":core"))
619633
}
620634
}
635+
621636
project(":exts:ext-dependencies") {
622637
dependencies {
623638
intellijPlatform {

core/src/main/kotlin/cc/unitmesh/devti/bridge/BridgeCommandProvider.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,13 @@ sealed class Security(override val name: String) : BridgeCommandProvider {
6767
sealed class ArchViewCommand(override val name: String) : BridgeCommandProvider {
6868
object WebApi : ArchViewCommand("/webapi")
6969
object ModuleView : ArchViewCommand("/moduleView")
70-
object ComponentView : ArchViewCommand("/componentView")
70+
71+
/**
72+
* /componentView
73+
*/
74+
object ComponentView : ArchViewCommand("componentView")
7175
object Structure : ArchViewCommand("/structure")
72-
object Styling : ArchViewCommand("/styling")
76+
object StylingView : ArchViewCommand("stylingView")
7377
}
7478

7579
/**
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package cc.unitmesh.devti.bridge.utils
2+
3+
import com.intellij.ide.structureView.StructureView
4+
import com.intellij.ide.structureView.StructureViewTreeElement
5+
import com.intellij.lang.LanguageStructureViewBuilder
6+
import com.intellij.openapi.editor.Document
7+
import com.intellij.openapi.fileEditor.FileEditor
8+
import com.intellij.openapi.fileEditor.FileEditorManager
9+
import com.intellij.openapi.project.Project
10+
import com.intellij.openapi.vfs.VirtualFile
11+
import com.intellij.psi.PsiDocumentManager
12+
import com.intellij.psi.PsiElement
13+
import com.intellij.psi.PsiFile
14+
15+
object StructureCommandUtil {
16+
/**
17+
* ```
18+
* (1000-9999)
19+
* ```
20+
*/
21+
private val maxLineWith = 11
22+
private val maxDepth = 5
23+
private val maxLinesForShowLinNO = 60
24+
25+
fun getFileStructure(project: Project, file: VirtualFile, psiFile: PsiFile): String {
26+
val viewFactory = LanguageStructureViewBuilder.INSTANCE.forLanguage(psiFile.language)
27+
val fileEditor: FileEditor = FileEditorManager.getInstance(project).getEditors(file).firstOrNull()
28+
?: return "No FileEditor found."
29+
30+
if (viewFactory != null) {
31+
val view: StructureView = viewFactory.getStructureViewBuilder(psiFile)
32+
?.createStructureView(fileEditor, project)
33+
?: return "No StructureView found."
34+
35+
val root: StructureViewTreeElement = view.treeModel.root
36+
return traverseStructure(root, 0, StringBuilder()).toString()
37+
}
38+
39+
return "No StructureViewModel found."
40+
}
41+
42+
/**
43+
* Display format
44+
* ```
45+
* elementName (location) - line number or navigate to element ?
46+
* ```
47+
*/
48+
private fun traverseStructure(element: StructureViewTreeElement, depth: Int, sb: StringBuilder): StringBuilder {
49+
val indent = formatBeforeCode(element, depth)
50+
var str = element.presentation.presentableText
51+
// if (!str.isNullOrBlank() && !element.presentation.locationString.isNullOrBlank()) {
52+
// str += " (${element.presentation.locationString})"
53+
// }
54+
if (!str.isNullOrBlank()) {
55+
sb.append(indent).append(str).append("\n")
56+
}
57+
58+
for (child in element.children) {
59+
if (child is StructureViewTreeElement) {
60+
traverseStructure(child, depth + 1, sb)
61+
}
62+
}
63+
64+
return sb
65+
}
66+
67+
private fun formatBeforeCode(element: StructureViewTreeElement, depth: Int): String {
68+
return if (element.value is PsiElement) {
69+
val psiElement = element.value as PsiElement
70+
val line = formatLine(psiElement)
71+
if (line.length < maxLineWith) {
72+
line + " ".repeat(maxLineWith - line.length) + " ".repeat(depth)
73+
} else {
74+
line + " ".repeat(depth)
75+
}
76+
} else {
77+
" ".repeat(maxLineWith) + " ".repeat(depth)
78+
}
79+
}
80+
81+
private fun formatLine(psiElement: PsiElement): String {
82+
val psiFile: PsiFile = psiElement.containingFile
83+
val document: Document = PsiDocumentManager.getInstance(psiFile.project).getDocument(psiFile) ?: return ""
84+
val start = document.getLineNumber(psiElement.textRange.startOffset)
85+
val end = document.getLineNumber(psiElement.textRange.endOffset)
86+
87+
if (end - start > maxLinesForShowLinNO) {
88+
return "(${start + 1}-${end + 1})"
89+
}
90+
91+
return " ".repeat(maxDepth)
92+
}
93+
}

core/src/main/kotlin/cc/unitmesh/devti/provider/toolchain/ToolchainFunctionProvider.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,5 @@ interface ToolchainFunctionProvider {
2121
it.javaClass.simpleName == providerName
2222
}
2323
}
24-
25-
fun provide(project: Project, funcName: String): ToolchainFunctionProvider? {
26-
return EP_NAME.extensionList.firstOrNull {
27-
it.isApplicable(project, funcName)
28-
}
29-
}
3024
}
3125
}

exts/devins-lang/src/main/kotlin/cc/unitmesh/devti/language/compiler/exec/StructureShireCommand.kt

Lines changed: 3 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
package cc.unitmesh.devti.language.compiler.exec
22

3+
import cc.unitmesh.devti.bridge.utils.StructureCommandUtil
34
import cc.unitmesh.devti.devin.InsCommand
45
import cc.unitmesh.devti.devin.dataprovider.BuiltinCommand
56
import cc.unitmesh.devti.language.utils.lookupFile
6-
import com.intellij.ide.structureView.StructureView
7-
import com.intellij.ide.structureView.StructureViewTreeElement
8-
import com.intellij.lang.LanguageStructureViewBuilder
97
import com.intellij.openapi.application.ApplicationManager
108
import com.intellij.openapi.application.runReadAction
119
import com.intellij.openapi.diagnostic.logger
12-
import com.intellij.openapi.editor.Document
13-
import com.intellij.openapi.fileEditor.FileEditor
14-
import com.intellij.openapi.fileEditor.FileEditorManager
1510
import com.intellij.openapi.project.Project
1611
import com.intellij.openapi.project.guessProjectDir
1712
import com.intellij.openapi.vfs.VirtualFile
18-
import com.intellij.psi.PsiDocumentManager
19-
import com.intellij.psi.PsiElement
2013
import com.intellij.psi.PsiFile
2114
import com.intellij.psi.PsiManager
2215
import kotlinx.coroutines.Dispatchers
@@ -26,15 +19,6 @@ import kotlinx.coroutines.withContext
2619
class StructureInCommand(val myProject: Project, val prop: String) : InsCommand {
2720
override val commandName: BuiltinCommand = BuiltinCommand.STRUCTURE
2821

29-
/**
30-
* ```
31-
* (1000-9999)
32-
* ```
33-
*/
34-
private val maxLineWith = 11
35-
private val maxDepth = 5
36-
private val maxLinesForShowLinNO = 60
37-
3822
private val logger = logger<StructureInCommand>()
3923
override suspend fun execute(): String? {
4024
val virtualFile = file(myProject, prop)
@@ -51,79 +35,10 @@ class StructureInCommand(val myProject: Project, val prop: String) : InsCommand
5135
}.get()
5236
} ?: return null
5337

54-
val structure = getFileStructure(myProject, virtualFile, psiFile)
38+
val structure = StructureCommandUtil.getFileStructure(myProject, virtualFile, psiFile)
5539
val baseDir = myProject.guessProjectDir().toString()
5640
val filepath = virtualFile.path.removePrefix(baseDir)
57-
return "// $filepath\n```\n" + structure + "\n```"
58-
}
59-
60-
fun getFileStructure(project: Project, file: VirtualFile, psiFile: PsiFile): String {
61-
val viewFactory = LanguageStructureViewBuilder.INSTANCE.forLanguage(psiFile.language)
62-
val fileEditor: FileEditor = FileEditorManager.getInstance(project).getEditors(file).firstOrNull()
63-
?: return "No FileEditor found."
64-
65-
if (viewFactory != null) {
66-
val view: StructureView = viewFactory.getStructureViewBuilder(psiFile)
67-
?.createStructureView(fileEditor, project)
68-
?: return "No StructureView found."
69-
70-
val root: StructureViewTreeElement = view.treeModel.root
71-
return traverseStructure(root, 0, StringBuilder()).toString()
72-
}
73-
74-
return "No StructureViewModel found."
75-
}
76-
77-
/**
78-
* Display format
79-
* ```
80-
* elementName (location) - line number or navigate to element ?
81-
* ```
82-
*/
83-
private fun traverseStructure(element: StructureViewTreeElement, depth: Int, sb: StringBuilder): StringBuilder {
84-
val indent = formatBeforeCode(element, depth)
85-
var str = element.presentation.presentableText
86-
// if (!str.isNullOrBlank() && !element.presentation.locationString.isNullOrBlank()) {
87-
// str += " (${element.presentation.locationString})"
88-
// }
89-
if (!str.isNullOrBlank()) {
90-
sb.append(indent).append(str).append("\n")
91-
}
92-
93-
for (child in element.children) {
94-
if (child is StructureViewTreeElement) {
95-
traverseStructure(child, depth + 1, sb)
96-
}
97-
}
98-
99-
return sb
100-
}
101-
102-
private fun formatBeforeCode(element: StructureViewTreeElement, depth: Int): String {
103-
return if (element.value is PsiElement) {
104-
val psiElement = element.value as PsiElement
105-
val line = formatLine(psiElement)
106-
if (line.length < maxLineWith) {
107-
line + " ".repeat(maxLineWith - line.length) + " ".repeat(depth)
108-
} else {
109-
line + " ".repeat(depth)
110-
}
111-
} else {
112-
" ".repeat(maxLineWith) + " ".repeat(depth)
113-
}
114-
}
115-
116-
private fun formatLine(psiElement: PsiElement): String {
117-
val psiFile: PsiFile = psiElement.containingFile
118-
val document: Document = PsiDocumentManager.getInstance(psiFile.project).getDocument(psiFile) ?: return ""
119-
val start = document.getLineNumber(psiElement.textRange.startOffset)
120-
val end = document.getLineNumber(psiElement.textRange.endOffset)
121-
122-
if (end - start > maxLinesForShowLinNO) {
123-
return "(${start + 1}-${end + 1})"
124-
}
125-
126-
return " ".repeat(maxDepth)
41+
return "// $filepath\n```\n$structure\n```"
12742
}
12843

12944
fun file(project: Project, path: String): VirtualFile? {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package cc.unitmesh.ide.javascript.bridge
2+
3+
import cc.unitmesh.devti.bridge.ArchViewCommand
4+
import cc.unitmesh.devti.bridge.utils.StructureCommandUtil
5+
import cc.unitmesh.devti.provider.toolchain.ToolchainFunctionProvider
6+
import com.intellij.openapi.application.runReadAction
7+
import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx
8+
import com.intellij.openapi.project.Project
9+
import com.intellij.openapi.vfs.VirtualFile
10+
import com.intellij.psi.PsiManager
11+
import com.intellij.psi.css.CssFileType
12+
import com.intellij.psi.search.FileTypeIndex
13+
import com.intellij.psi.search.GlobalSearchScope
14+
import com.intellij.psi.search.ProjectScope
15+
16+
class StylingViewFunctionProvider : ToolchainFunctionProvider {
17+
override fun isApplicable(project: Project, funcName: String) = funcName == ArchViewCommand.StylingView.name
18+
19+
override fun execute(
20+
project: Project,
21+
funcName: String,
22+
args: List<Any>,
23+
allVariables: Map<String, Any?>
24+
): Any {
25+
val searchScope: GlobalSearchScope = ProjectScope.getContentScope(project)
26+
val scssType = FileTypeManagerEx.getInstanceEx().getFileTypeByExtension("scss")
27+
var files = FileTypeIndex.getFiles(scssType, searchScope)
28+
if (files.isNotEmpty()) {
29+
files = FileTypeIndex.getFiles(CssFileType.INSTANCE, searchScope)
30+
31+
}
32+
33+
val result = files
34+
.filter(::skipVendorCss)
35+
.mapNotNull { virtualFile ->
36+
val psiFile = runReadAction { PsiManager.getInstance(project).findFile(virtualFile) } ?: return@mapNotNull null
37+
StructureCommandUtil.getFileStructure(project, virtualFile, psiFile)
38+
}
39+
40+
return result
41+
}
42+
43+
/// skip all .module.css files and min,css files and under vendors
44+
private fun skipVendorCss(file: VirtualFile): Boolean =
45+
file.name.contains(".module.css") || file.name.contains(".min.css") || file.path.contains("vendors")
46+
}

javascript/src/main/resources/cc.unitmesh.javascript.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@
4646
<buildSystemProvider implementation="cc.unitmesh.ide.javascript.provider.JavaScriptBuildSystemProvider" />
4747

4848
<uiComponentProvider implementation="cc.unitmesh.ide.javascript.bridge.ReactUIComponentProvider"/>
49+
<toolchainFunctionProvider implementation="cc.unitmesh.ide.javascript.bridge.StylingViewFunctionProvider"/>
4950
</extensions>
5051
</idea-plugin>

0 commit comments

Comments
 (0)