├── .gitignore ├── settings.gradle.kts ├── src └── main │ ├── resources │ ├── jcef │ │ └── only_mermaid_9_can_use_in_offline │ ├── META-INF │ │ ├── javafx.xml │ │ ├── javafxp.xml │ │ ├── c.xml │ │ ├── go.xml │ │ ├── php.xml │ │ ├── xml.xml │ │ ├── java.xml │ │ ├── ruby.xml │ │ ├── rust.xml │ │ ├── groovy.xml │ │ ├── kotlin.xml │ │ ├── python.xml │ │ ├── rust_old.xml │ │ ├── scala.xml │ │ ├── js.xml │ │ ├── pluginIcon.svg │ │ ├── pluginIcon_dark.svg │ │ └── plugin.xml │ ├── DrawGraphSetting.properties │ └── messages │ │ ├── DrawGraphBundle.properties │ │ └── DrawGraphBundle_zh.properties │ ├── kotlin │ └── com │ │ └── github │ │ └── linwancen │ │ └── plugin │ │ ├── graph │ │ ├── parser │ │ │ ├── package-info.java │ │ │ ├── js │ │ │ │ ├── HtmlParser.kt │ │ │ │ ├── JsComment.kt │ │ │ │ ├── JsModifier.kt │ │ │ │ └── JsParser.kt │ │ │ ├── relfile │ │ │ │ ├── RelFile.kt │ │ │ │ └── RelFileMvc.kt │ │ │ ├── ParserUtils.kt │ │ │ ├── rust │ │ │ │ ├── RustModifier.kt │ │ │ │ └── RustParser.kt │ │ │ ├── ruby │ │ │ │ ├── RubyModifier.kt │ │ │ │ └── RubyParser.kt │ │ │ ├── python │ │ │ │ ├── PythonComment.kt │ │ │ │ └── PythonParser.kt │ │ │ ├── php │ │ │ │ ├── PhpComment.kt │ │ │ │ ├── PhpModifier.kt │ │ │ │ └── PhpParser.kt │ │ │ ├── go │ │ │ │ ├── GoComment.kt │ │ │ │ └── GoParser.kt │ │ │ ├── RelData.kt │ │ │ ├── Call.kt │ │ │ ├── xml │ │ │ │ ├── ParserXml.kt │ │ │ │ └── RelServicePom.kt │ │ │ ├── CommentUtils.kt │ │ │ ├── Parser.kt │ │ │ └── ParserLang.kt │ │ ├── openapi │ │ │ └── StringMap.kt │ │ ├── printer │ │ │ ├── PrinterData.kt │ │ │ ├── InstallMermaid.kt │ │ │ ├── Printer.kt │ │ │ ├── RelData2Effect.kt │ │ │ ├── RelData2SQLite.kt │ │ │ ├── PrinterMermaid.kt │ │ │ ├── PrinterPlantuml.kt │ │ │ └── PrinterGraphviz.kt │ │ ├── settings │ │ │ ├── Setting.kt │ │ │ ├── PathInit.kt │ │ │ ├── DrawGraphProjectState.kt │ │ │ ├── DrawGraphAppState.kt │ │ │ └── AbstractDrawGraphState.kt │ │ ├── ui │ │ │ ├── DrawGraphBundle.kt │ │ │ ├── HtmlFileController.kt │ │ │ ├── GraphWindowFactory.kt │ │ │ ├── PlantUmlFileController.kt │ │ │ └── RelDataController.kt │ │ ├── listeners │ │ │ └── TabListener.kt │ │ └── action │ │ │ ├── FileMethodCallAction.kt │ │ │ ├── MethodCallAction.kt │ │ │ ├── MethodUsageAction.kt │ │ │ └── ElementAction.kt │ │ └── common │ │ ├── text │ │ ├── DocText.kt │ │ ├── ArrayNewLinePrinter.kt │ │ ├── Skip.kt │ │ ├── Formats.kt │ │ ├── TsvUtils.kt │ │ └── JsonValueParser.kt │ │ ├── psi │ │ ├── LangUtils.kt │ │ └── PsiUnSaveUtils.kt │ │ ├── vfile │ │ └── ChildFileUtils.kt │ │ ├── TaskTool.kt │ │ └── ui │ │ └── UiUtils.kt │ ├── java │ └── com │ │ └── github │ │ └── linwancen │ │ └── plugin │ │ └── graph │ │ └── ui │ │ ├── TriConsumer.java │ │ └── webview │ │ ├── extension │ │ ├── BrowserExtensionPointJcef.java │ │ ├── BrowserExtensionPointJavaFx.java │ │ └── BrowserExtensionPoint.java │ │ ├── Browser.java │ │ ├── BrowserJavaFx.java │ │ ├── BrowserJcef.java │ │ ├── BrowserFactory.java │ │ └── JcefNavigateHandler.java │ ├── other_lang_class │ └── com │ │ └── github │ │ └── linwancen │ │ └── plugin │ │ └── graph │ │ └── parser │ │ └── c │ │ ├── CParser.class │ │ └── CModifier.class │ ├── clion │ └── com │ │ └── github │ │ └── linwancen │ │ └── plugin │ │ └── graph │ │ └── parser │ │ └── c │ │ ├── CModifier.kt │ │ └── CParser.kt │ └── idea │ └── com │ └── github │ └── linwancen │ └── plugin │ └── graph │ └── parser │ ├── java │ ├── GetSetIs.kt │ ├── JavaModifier.kt │ ├── JavaAnno.kt │ ├── JavaComment.kt │ ├── JavaParserUtils.kt │ └── JavaParser.kt │ ├── kotlin │ ├── KotlinComment.kt │ ├── KotlinModifier.kt │ └── KotlinParser.kt │ ├── groovy │ └── GroovyParser.kt │ └── scala │ ├── ScalaComment.kt │ └── ScalaParser.kt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── qodana.yml ├── .github ├── dependabot.yml └── workflows │ ├── run-ui-tests.yml │ ├── release.yml │ └── build.yml ├── .idea └── gradle.xml ├── .run ├── Run IDE for UI Tests.run.xml ├── Run Plugin Tests.run.xml ├── Run Qodana.run.xml ├── Run Plugin.run.xml ├── Build Plugin.run.xml └── Run Plugin Verification.run.xml ├── gradle.properties ├── CHANGELOG.md ├── gradlew.bat └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | .qodana 4 | build 5 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "draw-graph" 2 | -------------------------------------------------------------------------------- /src/main/resources/jcef/only_mermaid_9_can_use_in_offline: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/javafx.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/javafxp.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinWanCen/draw-graph/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * parser lang 3 | */ 4 | package com.github.linwancen.plugin.graph.parser; -------------------------------------------------------------------------------- /src/main/java/com/github/linwancen/plugin/graph/ui/TriConsumer.java: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui; 2 | 3 | public interface TriConsumer { 4 | void accept(K k, V v, S s); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/openapi/StringMap.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.openapi 2 | 3 | class StringMap : MutableMap> by HashMap() -------------------------------------------------------------------------------- /src/main/other_lang_class/com/github/linwancen/plugin/graph/parser/c/CParser.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinWanCen/draw-graph/HEAD/src/main/other_lang_class/com/github/linwancen/plugin/graph/parser/c/CParser.class -------------------------------------------------------------------------------- /src/main/other_lang_class/com/github/linwancen/plugin/graph/parser/c/CModifier.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinWanCen/draw-graph/HEAD/src/main/other_lang_class/com/github/linwancen/plugin/graph/parser/c/CModifier.class -------------------------------------------------------------------------------- /qodana.yml: -------------------------------------------------------------------------------- 1 | # Qodana configuration: 2 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html 3 | 4 | version: 1.0 5 | profile: 6 | name: qodana.recommended 7 | exclude: 8 | - name: All 9 | paths: 10 | - .qodana 11 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/c.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/go.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/xml.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/java.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/ruby.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/rust.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/groovy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/kotlin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/python.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/rust_old.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/scala.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /src/main/resources/DrawGraphSetting.properties: -------------------------------------------------------------------------------- 1 | online=false 2 | mermaid_js_link=https://cdn.jsdelivr.net/npm/mermaid@9.4.3/dist/mermaid.js 3 | # use / not \, split by ; 4 | temp_path_windows=D:/;C:/Users/Public/ 5 | temp_path_mac=/Applications/ 6 | temp_path_linux=~ 7 | anno_doc= 8 | effect_anno= -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/js/HtmlParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.js 2 | 3 | import com.intellij.lang.html.HTMLLanguage 4 | 5 | class HtmlParser : JsParser() { 6 | override fun id(): String { 7 | return HTMLLanguage.INSTANCE.id 8 | } 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/printer/PrinterData.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.printer 2 | 3 | import com.intellij.openapi.project.Project 4 | 5 | /** 6 | * bean 7 | */ 8 | class PrinterData( 9 | var src: String?, 10 | var js: String?, 11 | var project: Project, 12 | ) 13 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/js.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/java/com/github/linwancen/plugin/graph/ui/webview/extension/BrowserExtensionPointJcef.java: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui.webview.extension; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | public class BrowserExtensionPointJcef extends BrowserExtensionPoint { 6 | 7 | @NotNull 8 | protected String className() { 9 | return "com.github.linwancen.plugin.graph.ui.webview.BrowserJcef"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/github/linwancen/plugin/graph/ui/webview/extension/BrowserExtensionPointJavaFx.java: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui.webview.extension; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | public class BrowserExtensionPointJavaFx extends BrowserExtensionPoint { 6 | 7 | @NotNull 8 | protected String className() { 9 | return "com.github.linwancen.plugin.graph.ui.webview.BrowserJavaFx"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/relfile/RelFile.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.relfile 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.vfs.VirtualFile 5 | 6 | class RelFile { 7 | companion object{ 8 | @JvmStatic 9 | fun relFileOf(project: Project, files: List): List { 10 | return RelFileMvc.relFileOf(project, files) 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/clion/com/github/linwancen/plugin/graph/parser/c/CModifier.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.c 2 | 3 | import com.jetbrains.cidr.lang.psi.OCFunctionDefinition 4 | 5 | /** 6 | * func.is* 7 | */ 8 | object CModifier { 9 | /** 10 | * S static 11 | */ 12 | fun symbol(func: OCFunctionDefinition): String { 13 | val sb = StringBuilder() 14 | if (func.isStatic) { 15 | sb.append("S") 16 | } 17 | return sb.toString() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/ParserUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser 2 | 3 | object ParserUtils { 4 | 5 | /** 6 | * for overloading method 7 | * Java、Kotlin、Groovy、Scala、C++、TypeScript 8 | */ 9 | fun signParams(funMap: MutableMap): String { 10 | val sign = funMap["sign"] ?: return "" 11 | val i = sign.indexOf('(') 12 | if (i < 0) { 13 | return "" 14 | } 15 | return sign.substring(i) 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/rust/RustModifier.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.rust 2 | 3 | import org.rust.lang.core.psi.RsFunction 4 | 5 | object RustModifier { 6 | /** 7 | * + public 8 | *
A abstract 9 | */ 10 | fun symbol(func: RsFunction): String { 11 | val sb = StringBuilder() 12 | if (func.isPublic) { 13 | sb.append("+") 14 | } 15 | if (func.isAbstract) { 16 | sb.append("A") 17 | } 18 | return sb.toString() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration: 2 | # https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | # Maintain dependencies for Gradle dependencies 7 | - package-ecosystem: "gradle" 8 | directory: "/" 9 | target-branch: "next" 10 | # schedule: 11 | # interval: "daily" 12 | # Maintain dependencies for GitHub Actions 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | target-branch: "next" 16 | # schedule: 17 | # interval: "daily" 18 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/common/text/DocText.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.common.text 2 | 3 | import java.util.regex.Pattern 4 | 5 | object DocText { 6 | 7 | private val pattern: Pattern = Pattern.compile("<[^>]++>") 8 | 9 | fun addHtmlText(s: String?, vararg builders: StringBuilder): String? { 10 | val deleteHtml: String = pattern.matcher(s ?: return null).replaceAll("").trim() 11 | if (deleteHtml.isNotEmpty()) { 12 | builders.forEach { builder -> builder.append(deleteHtml).append(" ") } 13 | } 14 | return deleteHtml 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/ruby/RubyModifier.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.ruby 2 | 3 | import org.jetbrains.plugins.ruby.ruby.lang.psi.controlStructures.methods.RMethod 4 | 5 | object RubyModifier { 6 | /** 7 | * C constructor x deprecated 8 | */ 9 | fun symbol(func: RMethod): String { 10 | val sb = StringBuilder() 11 | if (func.isConstructor) { 12 | sb.append("C") 13 | } 14 | if (func.isDeprecated) { 15 | sb.append("x") 16 | } 17 | return sb.toString() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/common/psi/LangUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.common.psi 2 | 3 | import com.intellij.psi.PsiElement 4 | 5 | object LangUtils { 6 | 7 | @JvmStatic 8 | fun matchBaseLanguageId(psiElement: PsiElement, vararg languages: String): String? { 9 | var language = psiElement.language 10 | while (true) { 11 | val languageId = language.id 12 | if (languages.contains(languageId)) { 13 | return languageId 14 | } 15 | language = language.baseLanguage ?: return null 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/common/text/ArrayNewLinePrinter.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.common.text 2 | 3 | import com.fasterxml.jackson.core.util.DefaultIndenter 4 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter 5 | 6 | /** 7 | * jackson array to multi line 8 | */ 9 | class ArrayNewLinePrinter : DefaultPrettyPrinter() { 10 | init { 11 | _arrayIndenter = DefaultIndenter.SYSTEM_LINEFEED_INSTANCE 12 | _objectIndenter = DefaultIndenter.SYSTEM_LINEFEED_INSTANCE 13 | } 14 | 15 | override fun createInstance(): DefaultPrettyPrinter { 16 | return ArrayNewLinePrinter() 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/python/PythonComment.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.python 2 | 3 | import com.jetbrains.python.psi.StructuredDocString 4 | 5 | object PythonComment { 6 | fun addDocParam(docComment: StructuredDocString?, map: MutableMap) { 7 | if (docComment == null) { 8 | return 9 | } 10 | addDescription(docComment, map) 11 | } 12 | 13 | private fun addDescription(docComment: StructuredDocString, map: MutableMap) { 14 | map["@0"] = docComment.summary 15 | map["@1"] = docComment.summary 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/java/GetSetIs.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.java 2 | 3 | import com.intellij.psi.PsiClass 4 | import com.intellij.psi.PsiMethod 5 | import java.util.regex.Pattern 6 | 7 | object GetSetIs { 8 | private val getSetIsPattern: Pattern = Pattern.compile("^(?:get|set|is)(.++)$") 9 | 10 | @JvmStatic 11 | fun isGetSetIs(method: PsiMethod, psiClass: PsiClass): Boolean { 12 | val matcher = getSetIsPattern.matcher(method.name) 13 | if (!matcher.find()) { 14 | return false 15 | } 16 | val fieldName = matcher.group(1) ?: return false 17 | return psiClass.findFieldByName(fieldName.decapitalize(), true) != null 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/settings/Setting.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.settings 2 | 3 | import com.intellij.DynamicBundle 4 | import org.jetbrains.annotations.NonNls 5 | import org.jetbrains.annotations.PropertyKey 6 | 7 | @NonNls 8 | private const val BUNDLE = "DrawGraphSetting" 9 | 10 | object Setting : DynamicBundle(BUNDLE) { 11 | 12 | @Suppress("SpreadOperator") 13 | @JvmStatic 14 | fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 15 | getMessage(key, *params) 16 | 17 | @Suppress("SpreadOperator", "unused") 18 | @JvmStatic 19 | fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 20 | getLazyMessage(key, *params) 21 | } -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/kotlin/KotlinComment.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.kotlin 2 | 3 | import com.github.linwancen.plugin.graph.parser.CommentUtils 4 | import org.jetbrains.kotlin.kdoc.psi.api.KDoc 5 | 6 | object KotlinComment { 7 | fun addDocParam(docComment: KDoc?, map: MutableMap) { 8 | if (docComment == null) { 9 | return 10 | } 11 | addDescription(docComment, map) 12 | } 13 | 14 | private fun addDescription(docComment: KDoc, map: MutableMap) { 15 | val content = docComment.getDefaultSection().getContent() 16 | val doc = CommentUtils.doc(content) 17 | map["@0"] = doc 18 | CommentUtils.split(doc, map) 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/common/text/Skip.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.common.text 2 | 3 | import java.util.regex.Pattern 4 | 5 | object Skip { 6 | fun skip(text: String, include: Pattern, exclude: Pattern): Boolean { 7 | return if (exclude(text, exclude)) { 8 | true 9 | } else !include(text, include) 10 | } 11 | 12 | fun include(text: String, include: Pattern): Boolean { 13 | return if (include.pattern().isEmpty()) { 14 | true 15 | } else include.matcher(text).find() 16 | } 17 | 18 | fun exclude(text: String, exclude: Pattern): Boolean { 19 | return if (exclude.pattern().isEmpty()) { 20 | false 21 | } else exclude.matcher(text).find() 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/ui/DrawGraphBundle.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui 2 | 3 | import com.intellij.DynamicBundle 4 | import org.jetbrains.annotations.NonNls 5 | import org.jetbrains.annotations.PropertyKey 6 | 7 | @NonNls 8 | private const val BUNDLE = "messages.DrawGraphBundle" 9 | 10 | object DrawGraphBundle : DynamicBundle(BUNDLE) { 11 | 12 | @Suppress("SpreadOperator") 13 | @JvmStatic 14 | fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 15 | getMessage(key, *params) 16 | 17 | @Suppress("SpreadOperator", "unused") 18 | @JvmStatic 19 | fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 20 | getLazyMessage(key, *params) 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/php/PhpComment.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.php 2 | 3 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils 4 | import com.github.linwancen.plugin.graph.parser.CommentUtils 5 | import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment 6 | 7 | object PhpComment { 8 | fun addDocParam(docComment: PhpDocComment?, map: MutableMap) { 9 | if (docComment == null) { 10 | return 11 | } 12 | addDescription(docComment, map) 13 | } 14 | 15 | private fun addDescription(docComment: PhpDocComment, map: MutableMap) { 16 | val doc = CommentUtils.doc(PsiUnSaveUtils.getText(docComment)) 17 | map["@0"] = doc 18 | CommentUtils.split(doc, map) 19 | } 20 | } -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/common/text/Formats.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.common.text 2 | 3 | object Formats { 4 | @JvmStatic 5 | val DOLLAR_KEY = Regex("\\$\\{([^}]++)}") 6 | 7 | /** 8 | * @param default null is keep key 9 | */ 10 | @JvmStatic 11 | fun text(format: String, param: Map, default: String? = "", regex: Regex = DOLLAR_KEY): String { 12 | val split = regex.split(format) 13 | val findAll = regex.findAll(format) 14 | val result = StringBuilder() 15 | for ((index, match) in findAll.withIndex()) { 16 | val key = match.groups[1]?.value 17 | result.append(split[index]) 18 | result.append(param[key] ?: default ?: match.value) 19 | } 20 | result.append(split[split.size - 1]) 21 | return result.toString() 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/listeners/TabListener.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.listeners 2 | 3 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 4 | import com.github.linwancen.plugin.graph.ui.RelController 5 | import com.intellij.openapi.fileEditor.FileEditorManagerEvent 6 | import com.intellij.openapi.fileEditor.FileEditorManagerListener 7 | import com.intellij.openapi.project.DumbService 8 | 9 | object TabListener : FileEditorManagerListener { 10 | 11 | override fun selectionChanged(event: FileEditorManagerEvent) { 12 | val project = event.manager.project 13 | if (DumbService.isDumb(project)) { 14 | return 15 | } 16 | if (DrawGraphProjectState.of(project).autoLoad) { 17 | RelController.forFile(project, arrayOf(event.newFile ?: return), false) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/com/github/linwancen/plugin/graph/ui/webview/Browser.java: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui.webview; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import javax.swing.*; 7 | import java.lang.reflect.Method; 8 | 9 | public abstract class Browser { 10 | 11 | public Class clazz; 12 | 13 | public abstract void addImpl(@NotNull JPanel out, Project project); 14 | 15 | public abstract void loadImpl(String html); 16 | 17 | public String load(String html) { 18 | try { 19 | if (html != null) { 20 | Method method = clazz.getDeclaredMethod("loadImpl", String.class); 21 | method.invoke(this, html); 22 | } 23 | return null; 24 | } catch (Throwable e) { 25 | return e.toString(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/action/FileMethodCallAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.action 2 | 3 | import com.github.linwancen.plugin.graph.ui.DrawGraphBundle 4 | import com.github.linwancen.plugin.graph.ui.RelController 5 | import com.intellij.ide.actions.CopyReferenceAction 6 | import com.intellij.openapi.actionSystem.AnActionEvent 7 | import com.intellij.openapi.actionSystem.CommonDataKeys 8 | 9 | object FileMethodCallAction : CopyReferenceAction() { 10 | 11 | override fun update(e: AnActionEvent) { 12 | e.presentation.text = DrawGraphBundle.message("file.method.call.graph") 13 | } 14 | 15 | override fun actionPerformed(event: AnActionEvent) { 16 | val project = event.project ?: return 17 | val files = event.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return 18 | RelController.forFile(project, files, true) 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/go/GoComment.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.go 2 | 3 | import com.github.linwancen.plugin.graph.parser.CommentUtils 4 | import com.goide.documentation.GoDocumentationProvider 5 | import com.intellij.psi.PsiElement 6 | 7 | object GoComment { 8 | fun addDocParam(docComment: PsiElement?, map: MutableMap) { 9 | if (docComment == null) { 10 | return 11 | } 12 | addDescription(docComment, map) 13 | } 14 | 15 | private fun addDescription(docComment: PsiElement, map: MutableMap) { 16 | val comments = GoDocumentationProvider.getCommentsForElement(docComment) 17 | val commentText = GoDocumentationProvider.getCommentText(comments, false) 18 | val doc = CommentUtils.doc(commentText) 19 | map["@0"] = doc 20 | CommentUtils.split(doc, map) 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/action/MethodCallAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.action 2 | 3 | import com.github.linwancen.plugin.graph.ui.DrawGraphBundle 4 | import com.github.linwancen.plugin.graph.ui.RelController 5 | import com.intellij.ide.actions.CopyReferenceAction 6 | import com.intellij.openapi.actionSystem.AnActionEvent 7 | import com.intellij.openapi.actionSystem.CommonDataKeys 8 | 9 | object MethodCallAction : CopyReferenceAction() { 10 | 11 | override fun update(e: AnActionEvent) { 12 | e.presentation.text = DrawGraphBundle.message("method.call.graph") 13 | } 14 | 15 | override fun actionPerformed(event: AnActionEvent) { 16 | val project = event.project ?: return 17 | val psiElement = event.getData(CommonDataKeys.PSI_ELEMENT) ?: return 18 | RelController.forElement(project, psiElement, call()) 19 | } 20 | 21 | private fun call() = true 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/action/MethodUsageAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.action 2 | 3 | import com.github.linwancen.plugin.graph.ui.DrawGraphBundle 4 | import com.github.linwancen.plugin.graph.ui.RelController 5 | import com.intellij.ide.actions.CopyReferenceAction 6 | import com.intellij.openapi.actionSystem.AnActionEvent 7 | import com.intellij.openapi.actionSystem.CommonDataKeys 8 | 9 | object MethodUsageAction : CopyReferenceAction() { 10 | 11 | override fun update(e: AnActionEvent) { 12 | e.presentation.text = DrawGraphBundle.message("method.usage.graph") 13 | } 14 | 15 | override fun actionPerformed(event: AnActionEvent) { 16 | val project = event.project ?: return 17 | val psiElement = event.getData(CommonDataKeys.PSI_ELEMENT) ?: return 18 | RelController.forElement(project, psiElement, call()) 19 | } 20 | 21 | private fun call() = false 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/settings/PathInit.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.settings 2 | 3 | import org.apache.commons.lang3.SystemUtils 4 | import java.io.File 5 | 6 | object PathInit { 7 | 8 | val path = when { 9 | SystemUtils.IS_OS_WINDOWS -> validPath("temp_path_windows") 10 | SystemUtils.IS_OS_MAC -> validPath("temp_path_mac") 11 | SystemUtils.IS_OS_LINUX -> validPath("temp_path_linux") 12 | else -> validPath("temp_path_linux") 13 | } 14 | 15 | private fun validPath(pathKey: String): String? { 16 | val paths = Setting.message(pathKey).split(';') 17 | for (path in paths) { 18 | val file = File(path, "draw-graph") 19 | 20 | try { 21 | file.mkdirs() 22 | } catch (_: Exception) { 23 | } 24 | 25 | if (file.exists()) { 26 | return path 27 | } 28 | } 29 | return null 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/js/JsComment.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.js 2 | 3 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils 4 | import com.github.linwancen.plugin.graph.parser.CommentUtils 5 | import com.intellij.lang.javascript.documentation.JSDocumentationUtils 6 | import com.intellij.lang.javascript.psi.JSFunction 7 | 8 | object JsComment { 9 | fun addDocParam(docComment: JSFunction?, map: MutableMap) { 10 | if (docComment == null) { 11 | return 12 | } 13 | addDescription(docComment, map) 14 | } 15 | 16 | private fun addDescription(docComment: JSFunction, map: MutableMap) { 17 | val psiComment = JSDocumentationUtils.findDocCommentWider(docComment) ?: return 18 | val text = PsiUnSaveUtils.getText(psiComment) 19 | val doc = CommentUtils.doc(text) 20 | map["@0"] = doc 21 | CommentUtils.split(doc, map) 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/RelData.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser 2 | 3 | class RelData { 4 | val parentChildMap = mutableMapOf>() 5 | /** child print in parentChildMap root, so filter */ 6 | val childSet = mutableSetOf() 7 | val itemMap = mutableMapOf>() 8 | val callSet = mutableSetOf>() 9 | 10 | fun regParentChild(parentMap: MutableMap, childMap: MutableMap? = null) { 11 | val parent = parentMap["sign"] ?: return 12 | itemMap[parent] = parentMap 13 | val child = childMap?.get("sign") ?: return 14 | itemMap.putIfAbsent(child, childMap) 15 | parentChildMap.computeIfAbsent(parent) { mutableSetOf() }.add(child) 16 | childSet.add(child) 17 | } 18 | 19 | fun regCall(usageSign: String, callSign: String) { 20 | callSet.add(Pair(usageSign, callSign)) 21 | } 22 | } -------------------------------------------------------------------------------- /.run/Run IDE for UI Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 15 | 17 | true 18 | true 19 | false 20 | 21 | 22 | -------------------------------------------------------------------------------- /.run/Run Plugin Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/action/ElementAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.action 2 | 3 | import com.github.linwancen.plugin.graph.ui.DrawGraphBundle 4 | import com.github.linwancen.plugin.graph.ui.RelController 5 | import com.intellij.ide.actions.CopyReferenceAction 6 | import com.intellij.openapi.actionSystem.AnActionEvent 7 | import com.intellij.openapi.actionSystem.DataKey 8 | import com.intellij.psi.PsiFile 9 | 10 | object ElementAction : CopyReferenceAction() { 11 | 12 | @JvmStatic 13 | val languages = arrayOf("RegExp", "JSON", "yaml", "HTML") 14 | 15 | @JvmStatic 16 | val INJECTED_PSI_ELEMENT: DataKey = DataKey.create("\$injected\$.psi.File") 17 | 18 | 19 | override fun update(e: AnActionEvent) { 20 | e.presentation.text = DrawGraphBundle.message("element.graph") 21 | } 22 | 23 | override fun actionPerformed(event: AnActionEvent) { 24 | val project = event.project ?: return 25 | val psiFile = event.getData(INJECTED_PSI_ELEMENT) ?: return 26 | RelController.forInjectedElement(project, psiFile) 27 | } 28 | } -------------------------------------------------------------------------------- /.run/Run Qodana.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 16 | 19 | 21 | true 22 | true 23 | false 24 | 25 | 26 | -------------------------------------------------------------------------------- /.run/Run Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | -------------------------------------------------------------------------------- /.run/Build Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/common/psi/PsiUnSaveUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.common.psi 2 | 3 | import com.intellij.openapi.fileEditor.FileDocumentManager 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.vfs.VirtualFile 6 | import com.intellij.psi.PsiDocumentManager 7 | import com.intellij.psi.PsiElement 8 | import com.intellij.psi.PsiManager 9 | import java.util.regex.Pattern 10 | 11 | object PsiUnSaveUtils { 12 | 13 | @JvmStatic 14 | val LINE_END_PATTERN: Pattern = Pattern.compile("\r|\n|\r\n") 15 | 16 | @JvmStatic 17 | fun getText(element: PsiElement): String { 18 | try { 19 | val psiFile = element.containingFile ?: return element.text 20 | val doc = PsiDocumentManager.getInstance(psiFile.project).getDocument(psiFile) ?: return element.text 21 | return doc.getText(element.textRange) 22 | } catch (_: Throwable) { 23 | return element.text 24 | } 25 | } 26 | 27 | @JvmStatic 28 | fun fileText(project: Project, file: VirtualFile): String? { 29 | return FileDocumentManager.getInstance().getDocument(file)?.text 30 | ?: PsiManager.getInstance(project).findFile(file)?.text 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/php/PhpModifier.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.php 2 | 3 | import com.jetbrains.php.lang.psi.elements.Function 4 | import com.jetbrains.php.lang.psi.elements.Method 5 | 6 | object PhpModifier { 7 | /** 8 | * + public - private # protected ~ package private(default) 9 | * S static A abstract F final D dynamic 10 | * [PlantUML Class Diagram](https://plantuml.com/en/class-diagram) 11 | */ 12 | fun symbol(func: Function): String { 13 | if (func !is Method) { 14 | return "" 15 | } 16 | val sb = StringBuilder() 17 | val modifier = func.modifier 18 | sb.append(when { 19 | modifier.isPublic -> "+" 20 | modifier.isPrivate -> "-" 21 | modifier.isProtected -> "#" 22 | else -> "~" 23 | }) 24 | if (modifier.isStatic) { 25 | sb.append("S") 26 | } 27 | if (modifier.isAbstract) { 28 | sb.append("A") 29 | } 30 | if (modifier.isFinal) { 31 | sb.append("F") 32 | } 33 | if (modifier.isDynamic) { 34 | sb.append("D") 35 | } 36 | return sb.toString() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.run/Run Plugin Verification.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/java/JavaModifier.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.java 2 | 3 | import com.intellij.psi.PsiMethod 4 | import com.intellij.psi.PsiModifier 5 | 6 | /** 7 | * modifierList.hasModifierProperty(PsiModifier.*) 8 | */ 9 | object JavaModifier { 10 | /** 11 | * + public - private # protected ~ package private(default) 12 | * S static O Override A abstract C Constructor F final 13 | * [PlantUML Class Diagram](https://plantuml.com/en/class-diagram) 14 | */ 15 | fun symbol(method: PsiMethod): String { 16 | val modifierList = method.modifierList 17 | return when { 18 | modifierList.hasModifierProperty(PsiModifier.PUBLIC) -> '+' 19 | modifierList.hasModifierProperty(PsiModifier.PRIVATE) -> '-' 20 | modifierList.hasModifierProperty(PsiModifier.PROTECTED) -> '#' 21 | else -> '~' 22 | } + when { 23 | modifierList.hasModifierProperty(PsiModifier.STATIC) -> "S" 24 | method.hasAnnotation("java.lang.Override") -> "O" 25 | method.isConstructor -> "C" 26 | modifierList.hasModifierProperty(PsiModifier.ABSTRACT) -> "A" 27 | method.hasModifierProperty(PsiModifier.FINAL) -> "F" 28 | else -> "" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/linwancen/plugin/graph/ui/webview/BrowserJavaFx.java: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui.webview; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import javafx.application.Platform; 5 | import javafx.embed.swing.JFXPanel; 6 | import javafx.scene.Group; 7 | import javafx.scene.Scene; 8 | import javafx.scene.web.WebEngine; 9 | import javafx.scene.web.WebView; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import javax.swing.*; 13 | import java.awt.*; 14 | 15 | public class BrowserJavaFx extends Browser { 16 | 17 | private WebEngine engine; 18 | 19 | @Override 20 | public void addImpl(@NotNull JPanel out, Project project) { 21 | @NotNull JFXPanel jfxPanel = new JFXPanel(); 22 | Platform.runLater(() -> { 23 | @NotNull Group group = new Group(); 24 | @NotNull WebView webView = new WebView(); 25 | group.getChildren().add(webView); 26 | @NotNull Scene scene = new Scene(group); 27 | jfxPanel.setScene(scene); 28 | out.add(jfxPanel, BorderLayout.CENTER); 29 | 30 | engine = webView.getEngine(); 31 | }); 32 | } 33 | 34 | @Override 35 | public void loadImpl(String html) { 36 | if (engine != null && html != null) { 37 | Platform.runLater(() -> engine.loadContent(html)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/common/text/TsvUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.common.text 2 | 3 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils 4 | import com.intellij.openapi.project.DumbService 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.psi.search.FilenameIndex 7 | import com.intellij.psi.search.GlobalSearchScope 8 | 9 | object TsvUtils { 10 | 11 | @JvmStatic 12 | fun load(project: Project, end: String): MutableMap { 13 | val mapperTableMap = mutableMapOf() 14 | DumbService.getInstance(project).runReadActionInSmartMode { 15 | val projectScope = GlobalSearchScope.projectScope(project) 16 | val files = FilenameIndex.getAllFilesByExt(project, "tsv", projectScope) 17 | for (file in files) { 18 | if (file.name.endsWith(end)) { 19 | val text = PsiUnSaveUtils.fileText(project, file) 20 | for (line in PsiUnSaveUtils.LINE_END_PATTERN.split(text)) { 21 | val split = line.split('\t', limit = 2) 22 | if (split.size == 2) { 23 | mapperTableMap[split[0]] = split[1] 24 | } 25 | } 26 | } 27 | } 28 | } 29 | return mapperTableMap 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/resources/messages/DrawGraphBundle.properties: -------------------------------------------------------------------------------- 1 | window=Graph 2 | reload=Reload 3 | open.dir=File 4 | reset=Reset 5 | out=out 6 | temp=temp 7 | html=html 8 | src=src 9 | file.method.call.graph=File Method Call Graph 10 | file.method.usage.graph=File Method Usage Graph 11 | method.call.graph=Method Call Graph 12 | method.usage.graph=Method Usage Graph 13 | element.graph=Regexp, JSON, yaml Graph or HTML 14 | auto.load=Auto Load 15 | skip.lib=Skip lib 16 | 17 | web.load.err.msg=jcef(chrome)/JavaFx is not loaded, please click the "reset" button! 18 | mermaid.msg=Fast! If open java/pom.xml file but no graph, may be mermaid.js is not found, see src to copy it. 19 | graphviz.msg=If open java/pom.xml file but no graph, may be Graphviz is not to install, or not in env Path. 20 | 21 | setting=Setting 22 | skipGetSetIs=skip get/set/is 23 | LR=LR 24 | doc=doc 25 | limit=node limit 26 | include=className#memberName include Regexp: 27 | exclude=className#memberName exclude Regexp: 28 | otherInclude=Other include Regexp: 29 | otherExclude=Other exclude Regexp: 30 | effect=Effect 31 | effectInclude=Effect include Regexp: 32 | effectExclude=Effect exclude Regexp: 33 | annoDoc=Anno doc, AnnoFullName#methodName, Separated by \\n: 34 | effectAnno=Effect Anno, AnnoFullName#methodName, Separated by \\n: 35 | 36 | tip_online=Mermaid.js Online 37 | tip_mermaid_js_link=Mermaid.js link 38 | tip_temp_path=Temp path\uFF0C use / not \\ -------------------------------------------------------------------------------- /src/main/java/com/github/linwancen/plugin/graph/ui/webview/BrowserJcef.java: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui.webview; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.ui.jcef.JBCefBrowser; 5 | import org.cef.browser.CefMessageRouter; 6 | import org.cef.browser.CefMessageRouter.CefMessageRouterConfig; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import javax.swing.*; 11 | import java.awt.*; 12 | 13 | public class BrowserJcef extends Browser { 14 | 15 | private JBCefBrowser jbCefBrowser; 16 | 17 | @Override 18 | public void addImpl(@NotNull JPanel out, Project project) { 19 | jbCefBrowser = new JBCefBrowser(); 20 | @NotNull JComponent browserComponent = jbCefBrowser.getComponent(); 21 | 22 | @NotNull CefMessageRouterConfig config = new CefMessageRouterConfig("java", "javaCancel"); 23 | CefMessageRouter router = CefMessageRouter.create(config); 24 | router.addHandler(new JcefNavigateHandler(project, jbCefBrowser), true); 25 | jbCefBrowser.getJBCefClient().getCefClient().addMessageRouter(router); 26 | 27 | out.add(browserComponent, BorderLayout.CENTER); 28 | } 29 | 30 | @Override 31 | public void loadImpl(@Nullable String html) { 32 | if (jbCefBrowser != null && html != null) { 33 | jbCefBrowser.loadHTML(html); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/github/linwancen/plugin/graph/ui/webview/BrowserFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui.webview; 2 | 3 | import com.github.linwancen.plugin.graph.ui.DrawGraphBundle; 4 | import com.github.linwancen.plugin.graph.ui.webview.extension.BrowserExtensionPoint; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.ui.components.JBTextArea; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import javax.swing.*; 11 | import java.awt.*; 12 | import java.util.List; 13 | 14 | public class BrowserFactory { 15 | @Nullable 16 | public static Browser of(@NotNull JPanel out, Project project) { 17 | List browsers = BrowserExtensionPoint.BROWSER_EPN.getExtensionList(); 18 | out.removeAll(); 19 | out.setLayout(new BorderLayout()); 20 | @NotNull StringBuilder errMsg = new StringBuilder(); 21 | for (BrowserExtensionPoint browser : browsers) { 22 | try { 23 | return browser.add(out, project); 24 | } catch (Throwable e) { 25 | errMsg.append(e).append("\n"); 26 | } 27 | } 28 | errMsg.insert(0, DrawGraphBundle.message("web.load.err.msg")); 29 | @NotNull JBTextArea errTip = new JBTextArea(); 30 | errTip.setText(errMsg.toString()); 31 | out.add(errTip); 32 | return null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/js/JsModifier.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.js 2 | 3 | import com.intellij.lang.javascript.psi.JSFunction 4 | import com.intellij.lang.javascript.psi.ecmal4.JSAttributeList 5 | 6 | /** 7 | * func.hasModifier(JSAttributeList.ModifierType.*) 8 | */ 9 | object JsModifier { 10 | /** 11 | * S static O Override A abstract C Constructor F final D dynamic 12 | * [PlantUML Class Diagram](https://plantuml.com/en/class-diagram) 13 | */ 14 | fun symbol(func: JSFunction): String { 15 | val sb = StringBuilder() 16 | if (func.attributeList?.hasModifier(JSAttributeList.ModifierType.STATIC) == true) { 17 | sb.append("S") 18 | } 19 | if (func.attributeList?.hasModifier(JSAttributeList.ModifierType.OVERRIDE) == true) { 20 | sb.append("O") 21 | } 22 | if (func.attributeList?.hasModifier(JSAttributeList.ModifierType.ABSTRACT) == true) { 23 | sb.append("A") 24 | } 25 | if (func.isConstructor) { 26 | sb.append("C") 27 | } 28 | if (func.attributeList?.hasModifier(JSAttributeList.ModifierType.FINAL) == true) { 29 | sb.append("F") 30 | } 31 | if (func.attributeList?.hasModifier(JSAttributeList.ModifierType.DYNAMIC) == true) { 32 | sb.append("D") 33 | } 34 | return sb.toString() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/kotlin/KotlinModifier.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.kotlin 2 | 3 | import org.jetbrains.kotlin.lexer.KtTokens 4 | import org.jetbrains.kotlin.psi.KtNamedFunction 5 | 6 | /** 7 | * modifierList.hasModifier(KtTokens.*_KEYWORD) 8 | */ 9 | object KotlinModifier { 10 | /** 11 | * + public - private # protected ~ package private(default) 12 | *
S static o open O Override A abstract C Constructor 13 | *
[PlantUML Class Diagram](https://plantuml.com/en/class-diagram) 14 | */ 15 | fun symbol(func: KtNamedFunction): String { 16 | val modifierList = func.modifierList 17 | modifierList ?: return "" 18 | val sb = StringBuilder() 19 | if (modifierList.hasModifier(KtTokens.PRIVATE_KEYWORD)) { 20 | sb.append("-") 21 | } 22 | if (modifierList.hasModifier(KtTokens.PROTECTED_KEYWORD)) { 23 | sb.append("#") 24 | } 25 | if (modifierList.hasModifier(KtTokens.INTERNAL_KEYWORD)) { 26 | sb.append("I") 27 | } 28 | if (modifierList.hasModifier(KtTokens.OPEN_KEYWORD)) { 29 | sb.append("o") 30 | } 31 | when { 32 | modifierList.hasModifier(KtTokens.ABSTRACT_KEYWORD) -> sb.append("A") 33 | modifierList.hasModifier(KtTokens.OVERRIDE_KEYWORD) -> sb.append("O") 34 | } 35 | return sb.toString() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/ui/HtmlFileController.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui 2 | 3 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils 4 | import com.intellij.openapi.application.runInEdt 5 | import com.intellij.openapi.project.DumbService 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.vfs.VirtualFile 8 | 9 | object HtmlFileController { 10 | 11 | @JvmStatic 12 | fun forHtmlFiles(project: Project, window: GraphWindow, files: Array) { 13 | if (files.size == 1 && files[0].name.endsWith(".html")) { 14 | DumbService.getInstance(project).runReadActionInSmartMode { 15 | val src = PsiUnSaveUtils.fileText(project, files[0]) ?: return@runReadActionInSmartMode 16 | forHtmlSrc(window, src) 17 | } 18 | } 19 | } 20 | 21 | @JvmStatic 22 | fun forHtmlSrc(window: GraphWindow, htmlSrc: String?) { 23 | runInEdt { 24 | window.toolWindow.activate(null) 25 | window.plantumlHtml.text = htmlSrc 26 | if (window.plantumlBrowser != null) window.plantumlBrowser?.load(htmlSrc) 27 | window.mermaidHtml.text = htmlSrc 28 | if (window.mermaidBrowser != null) window.mermaidBrowser?.load(htmlSrc) 29 | window.graphvizHtml.text = htmlSrc 30 | if (window.graphvizBrowser != null) window.graphvizBrowser?.load(htmlSrc) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/Call.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.intellij.psi.search.GlobalSearchScope 5 | import com.intellij.psi.search.searches.ReferencesSearch 6 | import com.intellij.psi.util.PsiTreeUtil 7 | 8 | object Call { 9 | /** 10 | * ```mermaid 11 | * usage ..> call 12 | * ``` 13 | */ 14 | inline fun find(func: F, call: Boolean, vararg refClass: Class) = 15 | if (call) findRefs(PsiTreeUtil.findChildrenOfAnyType(func, *refClass)).filterIsInstance() 16 | // I don't know why cannot find usage for KotlinParser.callList in this project 17 | else ReferencesSearch.search(func, GlobalSearchScope.projectScope(func.project)).findAll() 18 | .mapNotNull { PsiTreeUtil.getParentOfType(it.element, F::class.java) }.distinct() 19 | 20 | /** 21 | * ```mermaid 22 | * usage ..> call 23 | * ``` 24 | */ 25 | fun findRefs(refs: Collection) = refs 26 | .flatMap { it.references.toList() } 27 | .map { 28 | try { 29 | it.resolve() 30 | // byte to src, not used here, because class calls are many, very slow 31 | // resolve?.navigationElement ?: resolve 32 | } catch (_: Throwable) { 33 | // ignore 34 | } 35 | } 36 | .distinct() 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/ui/GraphWindowFactory.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui 2 | 3 | import com.github.linwancen.plugin.graph.printer.InstallMermaid 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.wm.ToolWindow 6 | import com.intellij.openapi.wm.ToolWindowFactory 7 | import com.intellij.ui.content.ContentFactory 8 | 9 | class GraphWindowFactory : ToolWindowFactory { 10 | 11 | override fun init(toolWindow: ToolWindow) { 12 | val title: String = DrawGraphBundle.message("window", arrayOfNulls(0)) 13 | toolWindow.stripeTitle = title 14 | toolWindow.title = title 15 | } 16 | 17 | companion object { 18 | @JvmStatic 19 | val winMap = mutableMapOf() 20 | } 21 | 22 | override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { 23 | InstallMermaid.checkAndInstall() 24 | val authorWindow = GraphWindow(project, toolWindow) 25 | val iterator = winMap.iterator() 26 | for (entry in iterator) { 27 | if (entry.key.isDisposed) { 28 | iterator.remove() 29 | } 30 | } 31 | winMap[project] = authorWindow 32 | // 222.2680.4 delete SERVICE 33 | val contentFactory = ContentFactory.SERVICE.getInstance() 34 | val content = contentFactory.createContent(authorWindow.mainPanel, "", false) 35 | toolWindow.contentManager.addContent(content) 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/common/vfile/ChildFileUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.common.vfile 2 | 3 | import com.intellij.openapi.vfs.VfsUtil 4 | import com.intellij.openapi.vfs.VirtualFile 5 | import com.intellij.openapi.vfs.VirtualFileVisitor 6 | 7 | object ChildFileUtils { 8 | 9 | @JvmStatic 10 | fun recurExtChildFile(files: Array): MutableMap> { 11 | val map = mutableMapOf>() 12 | for (file in files) { 13 | VfsUtil.visitChildrenRecursively(file, object : VirtualFileVisitor() { 14 | override fun visitFile(file: VirtualFile): Boolean { 15 | if (file.isDirectory) { 16 | return true 17 | } 18 | map.computeIfAbsent(file.extension ?: return true) { mutableListOf() }.add(file) 19 | return true 20 | } 21 | }) 22 | } 23 | return map 24 | } 25 | 26 | @JvmStatic 27 | fun mostExt(map: MutableMap>): MutableMap.MutableEntry>? { 28 | var e: MutableMap.MutableEntry>? = null 29 | for (entry in map) { 30 | if (e == null || entry.value.size > e.value.size) { 31 | e = entry 32 | } 33 | } 34 | return e 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/settings/DrawGraphProjectState.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.settings 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.components.PersistentStateComponent 5 | import com.intellij.openapi.components.State 6 | import com.intellij.openapi.components.Storage 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.util.xmlb.XmlSerializerUtil 9 | 10 | @State( 11 | name = "com.github.linwancen.plugin.graph.settings.ProjectState", 12 | storages = [Storage("draw-graph-settings/DrawGraphProjectState.xml")] 13 | ) 14 | class DrawGraphProjectState : PersistentStateComponent, AbstractDrawGraphState() { 15 | 16 | var autoLoad = true 17 | 18 | override fun getState(): DrawGraphProjectState { 19 | return this 20 | } 21 | 22 | override fun loadState(state: DrawGraphProjectState) { 23 | XmlSerializerUtil.copyBean(state, this) 24 | } 25 | 26 | companion object { 27 | @JvmStatic 28 | val default: DrawGraphProjectState = DrawGraphProjectState() 29 | 30 | @JvmStatic 31 | fun of(project: Project? = null): DrawGraphProjectState { 32 | val manager = project ?: ApplicationManager.getApplication() 33 | return manager.getService(DrawGraphProjectState::class.java) ?: return DrawGraphProjectState() 34 | } 35 | } 36 | 37 | fun reset() { 38 | super.resetAbstract(default) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/linwancen/plugin/graph/ui/webview/extension/BrowserExtensionPoint.java: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui.webview.extension; 2 | 3 | import com.github.linwancen.plugin.graph.ui.webview.Browser; 4 | import com.intellij.openapi.extensions.ExtensionPointName; 5 | import com.intellij.openapi.project.Project; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import javax.swing.*; 12 | import java.lang.reflect.Method; 13 | 14 | public abstract class BrowserExtensionPoint { 15 | private static final Logger LOG = LoggerFactory.getLogger(BrowserExtensionPoint.class); 16 | 17 | public static final ExtensionPointName BROWSER_EPN = 18 | ExtensionPointName.create("com.github.linwancen.drawgraph.browser"); 19 | 20 | @NotNull 21 | protected abstract String className(); 22 | 23 | @Nullable 24 | public Browser add(@NotNull JPanel out, Project project) throws Exception { 25 | Class clazz = Class.forName(className()); 26 | Object instance = clazz.getConstructor().newInstance(); 27 | if (instance instanceof Browser) { 28 | ((Browser) instance).clazz = clazz; 29 | Method method = clazz.getDeclaredMethod("addImpl", JPanel.class, Project.class); 30 | method.invoke(instance, out, project); 31 | LOG.info("Browser add: {}", instance); 32 | return (Browser) instance; 33 | } 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/settings/DrawGraphAppState.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.settings 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.components.PersistentStateComponent 5 | import com.intellij.openapi.components.State 6 | import com.intellij.openapi.components.Storage 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.util.xmlb.XmlSerializerUtil 9 | 10 | @State( 11 | name = "com.github.linwancen.plugin.graph.settings.DrawGraphAppState", 12 | storages = [Storage("draw-graph-settings/DrawGraphAppState.xml")] 13 | ) 14 | class DrawGraphAppState : PersistentStateComponent, AbstractDrawGraphState() { 15 | 16 | var limit = 1000 17 | var online = Setting.message("online") == "true" 18 | var path = PathInit.path 19 | var tempPath = if (path != null) "$path/draw-graph" else "draw-graph" 20 | val mermaidOffline 21 | get() = "file:///$tempPath/mermaid.js" 22 | var mermaidOnline = Setting.message("mermaid_js_link") 23 | 24 | override fun getState(): DrawGraphAppState { 25 | return this 26 | } 27 | 28 | override fun loadState(state: DrawGraphAppState) { 29 | XmlSerializerUtil.copyBean(state, this) 30 | } 31 | 32 | companion object { 33 | @JvmStatic 34 | fun of(project: Project? = null): DrawGraphAppState { 35 | val manager = project ?: ApplicationManager.getApplication() 36 | return manager.getService(DrawGraphAppState::class.java) ?: return DrawGraphAppState() 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/relfile/RelFileMvc.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.relfile 2 | 3 | import com.github.linwancen.plugin.graph.settings.DrawGraphAppState 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.vfs.VirtualFile 6 | import com.intellij.psi.search.FilenameIndex 7 | import com.intellij.psi.search.GlobalSearchScope 8 | import java.util.regex.Pattern 9 | 10 | object RelFileMvc { 11 | @JvmStatic 12 | private val mvcPattern = Pattern.compile("^I?([A-Z]\\w+)(?:Controller|Service|ServiceImpl|Mapper)\\.java$") 13 | 14 | @JvmStatic 15 | fun relFileOf(project: Project, files: List): List { 16 | if (!DrawGraphAppState.of().mvc) { 17 | return files 18 | } 19 | val matcher = mvcPattern.matcher(files[0].name) 20 | if (!matcher.find()) { 21 | return files 22 | } 23 | val prefix = matcher.group(1) ?: return files 24 | val relFiles = mutableListOf() 25 | val scope = GlobalSearchScope.projectScope(project) 26 | relFiles.addAll(FilenameIndex.getVirtualFilesByName(project, "${prefix}Controller.java", scope)) 27 | relFiles.addAll(FilenameIndex.getVirtualFilesByName(project, "${prefix}Service.java", scope)) 28 | relFiles.addAll(FilenameIndex.getVirtualFilesByName(project, "I${prefix}Service.java", scope)) 29 | relFiles.addAll(FilenameIndex.getVirtualFilesByName(project, "${prefix}ServiceImpl.java", scope)) 30 | relFiles.addAll(FilenameIndex.getVirtualFilesByName(project, "${prefix}Mapper.java", scope)) 31 | return relFiles 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/groovy/GroovyParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.groovy 2 | 3 | import com.github.linwancen.plugin.graph.parser.Call 4 | import com.github.linwancen.plugin.graph.parser.ParserLang 5 | import com.github.linwancen.plugin.graph.parser.RelData 6 | import com.github.linwancen.plugin.graph.parser.java.JavaParserUtils 7 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 8 | import org.jetbrains.plugins.groovy.GroovyLanguage 9 | import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression 10 | import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod 11 | 12 | class GroovyParser : ParserLang() { 13 | 14 | override fun id(): String { 15 | return GroovyLanguage.id 16 | } 17 | 18 | override fun funClass(): Class { 19 | return GrMethod::class.java 20 | } 21 | 22 | override fun skipFun(state: DrawGraphProjectState, func: GrMethod): Boolean { 23 | return JavaParserUtils.skipFun(state, func) 24 | } 25 | 26 | override fun toSign(func: GrMethod): String? { 27 | return JavaParserUtils.sign(func) 28 | } 29 | 30 | override fun funMap(funMap: MutableMap, func: GrMethod) { 31 | JavaParserUtils.funMap(func, funMap) 32 | } 33 | 34 | override fun classMap(func: GrMethod, relData: RelData): MutableMap? { 35 | return JavaParserUtils.classMap(func) 36 | } 37 | 38 | override fun callList(func: GrMethod, call: Boolean): List { 39 | return Call.find(func, call, GrReferenceExpression::class.java) 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/xml/ParserXml.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.xml 2 | 3 | import com.github.linwancen.plugin.graph.parser.Parser 4 | import com.github.linwancen.plugin.graph.parser.RelData 5 | import com.intellij.lang.xml.XMLLanguage 6 | import com.intellij.openapi.progress.ProgressIndicator 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.openapi.vfs.VirtualFile 9 | import com.intellij.psi.PsiManager 10 | import com.intellij.psi.search.FilenameIndex 11 | import com.intellij.psi.search.GlobalSearchScope 12 | import com.intellij.psi.xml.XmlFile 13 | 14 | /** 15 | * for pom.xml 16 | */ 17 | class ParserXml : Parser() { 18 | 19 | companion object { 20 | private const val POM_FILE = "pom.xml" 21 | } 22 | 23 | override fun id(): String { 24 | return XMLLanguage.INSTANCE.id 25 | } 26 | 27 | override fun srcImpl(project: Project, relData: RelData, files: List, indicator: ProgressIndicator?) { 28 | val callSetMap = mutableMapOf>() 29 | val psiFiles = if (files.size == 1 && POM_FILE == files[0].name) { 30 | val path = files[0].path 31 | val dir = path.substring(0, path.length - POM_FILE.length) 32 | FilenameIndex.getFilesByName(project, POM_FILE, GlobalSearchScope.projectScope(project)).toList() 33 | .filter { it.virtualFile.path.startsWith(dir) } 34 | } else { 35 | files.mapNotNull { PsiManager.getInstance(project).findFile(it) }.filter { POM_FILE == it.name } 36 | } 37 | for (psiFile in psiFiles) { 38 | if (psiFile !is XmlFile) { 39 | continue 40 | } 41 | RelServicePom.parsePom(psiFile, callSetMap, relData) 42 | } 43 | regCall(callSetMap, relData) 44 | } 45 | } -------------------------------------------------------------------------------- /.github/workflows/run-ui-tests.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions Workflow for launching UI tests on Linux, Windows, and Mac in the following steps: 2 | # - prepare and launch IDE with your plugin and robot-server plugin, which is needed to interact with UI 3 | # - wait for IDE to start 4 | # - run UI tests with separate Gradle task 5 | # 6 | # Please check https://github.com/JetBrains/intellij-ui-test-robot for information about UI tests with IntelliJ Platform 7 | # 8 | # Workflow is triggered manually. 9 | 10 | name: Run UI Tests 11 | on: 12 | workflow_dispatch 13 | 14 | jobs: 15 | 16 | testUI: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | include: 22 | - os: ubuntu-latest 23 | runIde: | 24 | export DISPLAY=:99.0 25 | Xvfb -ac :99 -screen 0 1920x1080x16 & 26 | gradle runIdeForUiTests & 27 | - os: windows-latest 28 | runIde: start gradlew.bat runIdeForUiTests 29 | - os: macos-latest 30 | runIde: ./gradlew runIdeForUiTests & 31 | 32 | steps: 33 | 34 | # Check out current repository 35 | - name: Fetch Sources 36 | uses: actions/checkout@v3 37 | 38 | # Setup Java 11 environment for the next steps 39 | - name: Setup Java 40 | uses: actions/setup-java@v3 41 | with: 42 | distribution: zulu 43 | java-version: 11 44 | 45 | # Run IDEA prepared for UI testing 46 | - name: Run IDE 47 | run: ${{ matrix.runIde }} 48 | 49 | # Wait for IDEA to be started 50 | - name: Health Check 51 | uses: jtalk/url-health-check-action@v3 52 | with: 53 | url: http://127.0.0.1:8082 54 | max-attempts: 15 55 | retry-delay: 30s 56 | 57 | # Run tests 58 | - name: Tests 59 | run: ./gradlew test 60 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/python/PythonParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.python 2 | 3 | import com.github.linwancen.plugin.graph.parser.Call 4 | import com.github.linwancen.plugin.graph.parser.ParserLang 5 | import com.github.linwancen.plugin.graph.parser.RelData 6 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 7 | import com.jetbrains.python.PythonLanguage 8 | import com.jetbrains.python.psi.PyFunction 9 | import com.jetbrains.python.psi.PyReferenceExpression 10 | 11 | class PythonParser : ParserLang() { 12 | 13 | override fun id(): String { 14 | return PythonLanguage.INSTANCE.id 15 | } 16 | 17 | override fun funClass(): Class { 18 | return PyFunction::class.java 19 | } 20 | 21 | override fun skipFun(state: DrawGraphProjectState, func: PyFunction): Boolean { 22 | return "__init__" == func.name 23 | } 24 | 25 | override fun toSign(func: PyFunction): String? { 26 | return func.qualifiedName 27 | } 28 | 29 | override fun funMap(funMap: MutableMap, func: PyFunction) { 30 | funMap["name"] = "${func.name}" 31 | PythonComment.addDocParam(func.structuredDocString, funMap) 32 | } 33 | 34 | override fun classMap(func: PyFunction, relData: RelData): MutableMap? { 35 | val psiClass = func.containingClass ?: return null 36 | val classMap = mutableMapOf() 37 | psiClass.name?.let { classMap["name"] = it } 38 | psiClass.qualifiedName?.let { classMap["sign"] = it } 39 | PythonComment.addDocParam(psiClass.structuredDocString, classMap) 40 | return classMap 41 | } 42 | 43 | override fun callList(func: PyFunction, call: Boolean): List { 44 | return Call.find(func, call, PyReferenceExpression::class.java) 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/scala/ScalaComment.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.scala 2 | 3 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils 4 | import com.github.linwancen.plugin.common.text.DocText 5 | import com.github.linwancen.plugin.graph.parser.java.JavaComment 6 | import com.intellij.psi.PsiElement 7 | import com.intellij.psi.javadoc.PsiDocComment 8 | import com.intellij.psi.util.elementType 9 | import org.jetbrains.plugins.scala.lang.scaladoc.lexer.ScalaDocTokenType 10 | import org.jetbrains.plugins.scala.lang.scaladoc.psi.api.ScDocInlinedTag 11 | 12 | class ScalaComment : JavaComment() { 13 | companion object { 14 | private val INSTANCE = ScalaComment() 15 | 16 | fun addDocParam(docComment: PsiDocComment?, map: MutableMap) { 17 | INSTANCE.addDocParam(docComment, map) 18 | } 19 | } 20 | 21 | override fun addDocParam(docComment: PsiDocComment?, map: MutableMap) { 22 | if (docComment == null) { 23 | return 24 | } 25 | addDescription(docComment.children.asSequence(), map) 26 | addTag(docComment, map) 27 | } 28 | 29 | /** 30 | * @return is a new line 31 | */ 32 | override fun appendElementText(element: PsiElement, all: StringBuilder, currLine: StringBuilder): Boolean { 33 | if (PsiUnSaveUtils.getText(element).contains("\n") && currLine.isNotEmpty()) { 34 | return true 35 | } 36 | if (element is ScDocInlinedTag) { 37 | val children = element.children 38 | if (children.size >= 2) { 39 | DocText.addHtmlText(PsiUnSaveUtils.getText(children[children.size - 1]), all, currLine) 40 | } 41 | } else if (ScalaDocTokenType.DOC_COMMENT_DATA == element.elementType) { 42 | DocText.addHtmlText(PsiUnSaveUtils.getText(element), all, currLine) 43 | } 44 | return false 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/common/TaskTool.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.common 2 | 3 | import com.intellij.openapi.progress.ProgressIndicator 4 | import java.time.Duration 5 | 6 | /** 7 | * Calculate progress, time remaining, return null when canceled 8 | */ 9 | class TaskTool(var indicator: ProgressIndicator, var length: Int) { 10 | private var refTime: Long = System.currentTimeMillis() 11 | private var refUnitTime: Long = 0 12 | private var refIndex: Int = 0 13 | 14 | init { 15 | indicator.isIndeterminate = false 16 | } 17 | 18 | /** 19 | * taskTool.beforeNext(index, text2) ?: break 20 | */ 21 | fun beforeNext(index: Int, text2: String): String? { 22 | if (indicator.isCanceled) { 23 | return null 24 | } 25 | indicator.fraction = 1.0 * index / length 26 | indicator.text2 = text2 27 | if (index == 0) { 28 | return "" 29 | } 30 | val remain = length - index 31 | val currTime = System.currentTimeMillis() 32 | if (index > refIndex * 2) { 33 | val refUseTime = currTime - refTime 34 | refTime = currTime 35 | refUnitTime = refUseTime / (index - refIndex) 36 | refIndex = index 37 | } 38 | val remainTime = refUnitTime * remain 39 | val timeStr = timeStr(remainTime) 40 | indicator.text = "$index / $length need $timeStr" 41 | return "" 42 | } 43 | 44 | companion object { 45 | @JvmStatic 46 | fun timeStr(millis: Long): String { 47 | val time = Duration.ofMillis(millis) 48 | val hour = if (time.toHours() > 0) "${time.toHours()}h " else "" 49 | val minutes = if (time.toMinutesPart() > 0) "${time.toMinutesPart()}m " else "" 50 | val seconds = if (time.toSecondsPart() > 0) "${time.toSecondsPart()}s" else "" 51 | return "$hour$minutes$seconds" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/go/GoParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.go 2 | 3 | import com.github.linwancen.plugin.graph.parser.Call 4 | import com.github.linwancen.plugin.graph.parser.ParserLang 5 | import com.github.linwancen.plugin.graph.parser.RelData 6 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 7 | import com.goide.GoLanguage 8 | import com.goide.psi.GoFunctionDeclaration 9 | import com.goide.psi.GoReferenceExpression 10 | 11 | class GoParser : ParserLang() { 12 | 13 | override fun id(): String { 14 | return GoLanguage.INSTANCE.id 15 | } 16 | 17 | override fun funClass(): Class { 18 | return GoFunctionDeclaration::class.java 19 | } 20 | 21 | override fun skipFun(state: DrawGraphProjectState, func: GoFunctionDeclaration): Boolean { 22 | return false 23 | } 24 | 25 | override fun toSign(func: GoFunctionDeclaration): String { 26 | return "${func.qualifiedName}" 27 | } 28 | 29 | override fun funMap(funMap: MutableMap, func: GoFunctionDeclaration) { 30 | funMap["name"] = "${func.name}" 31 | GoComment.addDocParam(func, funMap) 32 | } 33 | 34 | override fun classMap(func: GoFunctionDeclaration, relData: RelData): MutableMap? { 35 | val psiClass = func.containingFile.`package` ?: return null 36 | val funcSign = func.qualifiedName ?: return null 37 | val classMap = mutableMapOf() 38 | psiClass.name?.let { classMap["name"] = it } 39 | classMap["sign"] = funcSign.substring(0, funcSign.lastIndexOf(".")) 40 | GoComment.addDocParam(psiClass, classMap) 41 | return classMap 42 | } 43 | 44 | override fun callList(func: GoFunctionDeclaration, call: Boolean): List { 45 | return Call.find(func, call, GoReferenceExpression::class.java) 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/scala/ScalaParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.scala 2 | 3 | import com.github.linwancen.plugin.graph.parser.Call 4 | import com.github.linwancen.plugin.graph.parser.ParserLang 5 | import com.github.linwancen.plugin.graph.parser.RelData 6 | import com.github.linwancen.plugin.graph.parser.java.JavaParserUtils 7 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 8 | import org.jetbrains.plugins.scala.ScalaLanguage 9 | import org.jetbrains.plugins.scala.lang.psi.api.expr.ScReferenceExpression 10 | import org.jetbrains.plugins.scala.lang.psi.api.statements.ScFunctionDefinition 11 | 12 | class ScalaParser : ParserLang() { 13 | 14 | override fun id(): String { 15 | return ScalaLanguage.INSTANCE.id 16 | } 17 | 18 | override fun funClass(): Class { 19 | return ScFunctionDefinition::class.java 20 | } 21 | 22 | override fun skipFun(state: DrawGraphProjectState, func: ScFunctionDefinition): Boolean { 23 | return JavaParserUtils.skipFun(state, func) 24 | } 25 | 26 | override fun toSign(func: ScFunctionDefinition): String? { 27 | return JavaParserUtils.sign(func) 28 | } 29 | 30 | override fun funMap(funMap: MutableMap, func: ScFunctionDefinition) { 31 | JavaParserUtils.funMapWithoutDoc(func, funMap) 32 | ScalaComment.addDocParam(func.docComment, funMap) 33 | } 34 | 35 | override fun classMap(func: ScFunctionDefinition, relData: RelData): MutableMap? { 36 | val (psiClass, classMap) = JavaParserUtils.classMapWithoutDoc(func) ?: return null 37 | ScalaComment.addDocParam(psiClass.docComment, classMap) 38 | return classMap 39 | } 40 | 41 | override fun callList(func: ScFunctionDefinition, call: Boolean): List { 42 | return Call.find(func, call, ScReferenceExpression::class.java) 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/java/JavaAnno.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.java 2 | 3 | import com.github.linwancen.plugin.graph.settings.DrawGraphAppState 4 | import com.intellij.psi.JavaPsiFacade 5 | import com.intellij.psi.PsiField 6 | import com.intellij.psi.PsiModifierListOwner 7 | import com.intellij.psi.PsiReference 8 | 9 | open class JavaAnno { 10 | companion object { 11 | private val INSTANCE = JavaAnno() 12 | 13 | fun addAnno(psiAnnotationOwner: PsiModifierListOwner?, map: MutableMap) { 14 | INSTANCE.addAnno(psiAnnotationOwner, map) 15 | } 16 | } 17 | 18 | open fun addAnno(psiAnnotationOwner: PsiModifierListOwner?, map: MutableMap) { 19 | psiAnnotationOwner ?: return 20 | val eval = JavaPsiFacade.getInstance(psiAnnotationOwner.project).constantEvaluationHelper 21 | for (anno in psiAnnotationOwner.annotations) { 22 | val annoName = anno.qualifiedName 23 | for (it in anno.parameterList.attributes) { 24 | var value = it.literalValue 25 | // PsiField reference value 26 | if (value == null) { 27 | val v = it.value 28 | if (v !is PsiReference) { 29 | continue 30 | } 31 | try { 32 | val resolve = v.resolve() 33 | if (resolve is PsiField) { 34 | val initializer = resolve.initializer ?: continue 35 | value = eval.computeConstantExpression(initializer).toString() 36 | } 37 | } catch (ignored: Throwable) {} 38 | } 39 | map["$annoName#${it.name ?: "value"}"] = value ?: continue 40 | } 41 | } 42 | val state = DrawGraphAppState.of() 43 | for (key in state.annoDocArr) { 44 | if (key.isBlank()) { 45 | continue 46 | } 47 | map[key]?.let { map["@1"] = it; return@addAnno } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/js/JsParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.js 2 | 3 | import com.github.linwancen.plugin.graph.parser.Call 4 | import com.github.linwancen.plugin.graph.parser.ParserLang 5 | import com.github.linwancen.plugin.graph.parser.RelData 6 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 7 | import com.intellij.lang.javascript.JavascriptLanguage 8 | import com.intellij.lang.javascript.psi.JSFunction 9 | import com.intellij.lang.javascript.psi.JSReferenceExpression 10 | 11 | open class JsParser : ParserLang() { 12 | 13 | override fun id(): String { 14 | return JavascriptLanguage.INSTANCE.id 15 | } 16 | 17 | override fun funClass(): Class { 18 | return JSFunction::class.java 19 | } 20 | 21 | override fun skipFun(state: DrawGraphProjectState, func: JSFunction): Boolean { 22 | if (func.isConstructor && func.parameters.isEmpty()) { 23 | return true 24 | } 25 | if (!state.skipGetSetIs) { 26 | return false 27 | } 28 | val name = func.name ?: return false 29 | return name.startsWith("get") || name.startsWith("get") 30 | } 31 | 32 | override fun toSign(func: JSFunction): String { 33 | return func.qualifiedName.toString() 34 | } 35 | 36 | override fun funMap(funMap: MutableMap, func: JSFunction) { 37 | val v = JsModifier.symbol(func) 38 | funMap["name"] = "$v ${func.name}" 39 | JsComment.addDocParam(func, funMap) 40 | } 41 | 42 | override fun classMap(func: JSFunction, relData: RelData): MutableMap? { 43 | val psiClass = func.namespace ?: return null 44 | val classMap = mutableMapOf() 45 | psiClass.name.let { classMap["name"] = it } 46 | psiClass.qualifiedName.let { classMap["sign"] = it } 47 | return classMap 48 | } 49 | 50 | override fun callList(func: JSFunction, call: Boolean): List { 51 | // not support JSProperty fun yet 52 | return Call.find(func, call, JSReferenceExpression::class.java) 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/php/PhpParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.php 2 | 3 | import com.github.linwancen.plugin.graph.parser.Call 4 | import com.github.linwancen.plugin.graph.parser.ParserLang 5 | import com.github.linwancen.plugin.graph.parser.RelData 6 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 7 | import com.jetbrains.php.lang.PhpLanguage 8 | import com.jetbrains.php.lang.psi.elements.Function 9 | import com.jetbrains.php.lang.psi.elements.Method 10 | import com.jetbrains.php.lang.psi.elements.PhpReference 11 | 12 | class PhpParser : ParserLang() { 13 | 14 | override fun id(): String { 15 | return PhpLanguage.INSTANCE.id 16 | } 17 | 18 | override fun funClass(): Class { 19 | return Function::class.java 20 | } 21 | 22 | override fun skipFun(state: DrawGraphProjectState, func: Function): Boolean { 23 | return func.name.startsWith("__") 24 | } 25 | 26 | override fun toSign(func: Function): String { 27 | return func.fqn 28 | } 29 | 30 | override fun funMap(funMap: MutableMap, func: Function) { 31 | val v = PhpModifier.symbol(func) 32 | funMap["name"] = "$v ${func.name}" 33 | PhpComment.addDocParam(func.docComment, funMap) 34 | } 35 | 36 | override fun classMap(func: Function, relData: RelData): MutableMap? { 37 | val classMap = mutableMapOf() 38 | if (func is Method) { 39 | func.containingClass?.let { 40 | classMap["sign"] = it.fqn 41 | classMap["name"] = it.name 42 | PhpComment.addDocParam(it.docComment, classMap) 43 | return classMap 44 | } 45 | } 46 | if (func.namespaceName == "\\") { 47 | return null 48 | } 49 | classMap["sign"] = func.namespaceName 50 | classMap["name"] = func.namespaceName 51 | return classMap 52 | } 53 | 54 | override fun callList(func: Function, call: Boolean): List { 55 | return Call.find(func, call, PhpReference::class.java) 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/resources/messages/DrawGraphBundle_zh.properties: -------------------------------------------------------------------------------- 1 | window=\u56FE 2 | reload=\u5237\u65B0 3 | open.dir=\u6587\u4EF6 4 | reset=\u91CD\u7F6E 5 | out=\u8F93\u51FA 6 | temp=\u6A21\u677F 7 | html=html 8 | src=\u6E90\u7801 9 | file.method.call.graph=\u6587\u4EF6\u65B9\u6CD5\u8C03\u7528\u56FE 10 | file.method.usage.graph=\u6587\u4EF6\u65B9\u6CD5\u88AB\u8C03\u7528\u56FE 11 | method.call.graph=\u65B9\u6CD5\u8C03\u7528\u56FE 12 | method.usage.graph=\u65B9\u6CD5\u88AB\u8C03\u7528\u56FE 13 | element.graph=\u6B63\u5219, JSON, yaml \u56FE\u6216 HTML 14 | auto.load=\u81EA\u52A8\u8BFB\u53D6 15 | skip.lib=\u5FFD\u7565 lib 16 | 17 | web.load.err.msg=jcef\uFF08\u5185\u5D4C\u8C37\u6B4C chrome \u6D4F\u89C8\u5668\uFF09/JavaFx \u6D4F\u89C8\u5668 \u672A\u52A0\u8F7D\uFF0C\u8BF7\u5C1D\u8BD5\u70B9\u51FB\u201C\u91CD\u7F6E\u201D\u6309\u94AE\uFF01 18 | mermaid.msg=\u5982\u679C\u6253\u5F00\u4E86 java/pom.xml \u6587\u4EF6\u4F46\u6CA1\u6709\u56FE\u7247\uFF0C\u53EF\u80FD\u662F mermaid.js \u6CA1\u627E\u5230\uFF0C\u53EF\u4EE5\u53C2\u8003\u6E90\u7801\u624B\u52A8\u653E\u7F6E\u6587\u4EF6\u5230\u76F8\u5E94\u76EE\u5F55\u3002 19 | graphviz.msg=\u5982\u679C\u6253\u5F00\u4E86 java/pom.xml \u6587\u4EF6\u4F46\u6CA1\u6709\u56FE\u7247\uFF0C\u53EF\u80FD\u662F Graphviz \u6CA1\u5B89\u88C5\uFF0C\u6216\u8005\u6CA1\u6709\u8BBE\u7F6E\u4E3A\u73AF\u5883\u53D8\u91CF\u3002 20 | 21 | 22 | setting=\u8BBE\u7F6E 23 | skipGetSetIs=\u5FFD\u7565get/set/is 24 | LR=\u5DE6\u53F3 25 | doc=\u6CE8\u91CA 26 | limit=\u8282\u70B9\u9650\u5236 27 | include=\u7C7B#\u65B9\u6CD5 \u5305\u542B \u6B63\u5219\uFF1A 28 | exclude=\u7C7B#\u65B9\u6CD5 \u6392\u9664 \u6B63\u5219\uFF1A 29 | otherInclude=\u5176\u4ED6 \u5305\u542B \u6B63\u5219\uFF1A 30 | otherExclude=\u5176\u4ED6 \u6392\u9664 \u6B63\u5219\uFF1A 31 | effect=\u5F71\u54CD 32 | effectInclude=\u5F71\u54CD \u5305\u542B \u6B63\u5219\uFF1A 33 | effectExclude=\u5F71\u54CD \u6392\u9664 \u6B63\u5219\uFF1A 34 | annoDoc=\u6CE8\u89E3\u6CE8\u91CA\uFF0C\u6CE8\u89E3\u5168\u540D#\u65B9\u6CD5\u540D \u6362\u884C\u5206\u9694\uFF1A 35 | effectAnno=\u5F71\u54CD\u6CE8\u91CA\uFF0C\u6CE8\u89E3\u5168\u540D#\u65B9\u6CD5\u540D \u6362\u884C\u5206\u9694\uFF1A 36 | 37 | tip_online=Mermaid.js \u5728\u7EBF\u6A21\u5F0F 38 | tip_mermaid_js_link=Mermaid.js \u94FE\u63A5 39 | tip_temp_path=\u4E34\u65F6\u76EE\u5F55\uFF0C\u4F7F\u7528 / \u4E0D\u7528 \\ -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/ruby/RubyParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.ruby 2 | 3 | import com.github.linwancen.plugin.graph.parser.Call 4 | import com.github.linwancen.plugin.graph.parser.ParserLang 5 | import com.github.linwancen.plugin.graph.parser.RelData 6 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 7 | import com.intellij.psi.util.PsiTreeUtil 8 | import org.jetbrains.plugins.ruby.ruby.lang.RubyLanguage 9 | import org.jetbrains.plugins.ruby.ruby.lang.psi.controlStructures.classes.RClass 10 | import org.jetbrains.plugins.ruby.ruby.lang.psi.controlStructures.methods.RMethod 11 | import org.jetbrains.plugins.ruby.ruby.lang.psi.references.RDotReference 12 | import org.jetbrains.plugins.ruby.ruby.lang.psi.variables.RIdentifier 13 | 14 | class RubyParser : ParserLang() { 15 | 16 | override fun id(): String { 17 | return RubyLanguage.INSTANCE.id 18 | } 19 | 20 | override fun funClass(): Class { 21 | return RMethod::class.java 22 | } 23 | 24 | override fun skipFun(state: DrawGraphProjectState, func: RMethod): Boolean { 25 | return func.isConstructor && func.arguments.isEmpty() 26 | } 27 | 28 | override fun toSign(func: RMethod): String? { 29 | PsiTreeUtil.getParentOfType(func, RClass::class.java)?.let { 30 | val qualifiedName = it.qualifiedName ?: return null 31 | return "${qualifiedName}#${func.name}" 32 | } 33 | return "#${func.name}" 34 | } 35 | 36 | override fun funMap(funMap: MutableMap, func: RMethod) { 37 | val v = RubyModifier.symbol(func) 38 | funMap["name"] = "$v ${func.name}" 39 | } 40 | 41 | override fun classMap(func: RMethod, relData: RelData): MutableMap? { 42 | val psiClass = PsiTreeUtil.getParentOfType(func, RClass::class.java) ?: return null 43 | val classMap = mutableMapOf() 44 | classMap["sign"] = psiClass.qualifiedName.toString() 45 | classMap["name"] = psiClass.name 46 | return classMap 47 | } 48 | 49 | override fun callList(func: RMethod, call: Boolean): List { 50 | return Call.find(func, call, RDotReference::class.java, RIdentifier::class.java) 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/clion/com/github/linwancen/plugin/graph/parser/c/CParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.c 2 | 3 | import com.github.linwancen.plugin.graph.parser.Call 4 | import com.github.linwancen.plugin.graph.parser.CommentUtils 5 | import com.github.linwancen.plugin.graph.parser.ParserLang 6 | import com.github.linwancen.plugin.graph.parser.RelData 7 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 8 | import com.intellij.psi.util.PsiTreeUtil 9 | import com.jetbrains.cidr.lang.OCLanguage 10 | import com.jetbrains.cidr.lang.psi.OCDeclarator 11 | import com.jetbrains.cidr.lang.psi.OCFunctionDefinition 12 | import com.jetbrains.cidr.lang.psi.OCReferenceElement 13 | 14 | class CParser : ParserLang() { 15 | 16 | override fun id(): String { 17 | return OCLanguage.getInstance().id 18 | } 19 | 20 | override fun funClass(): Class { 21 | return OCFunctionDefinition::class.java 22 | } 23 | 24 | override fun skipFun(state: DrawGraphProjectState, func: OCFunctionDefinition): Boolean { 25 | return false 26 | } 27 | 28 | override fun toSign(func: OCFunctionDefinition): String { 29 | return "${func.namespaceQualifier?.name ?: ""}#${func.name}" 30 | } 31 | 32 | override fun funMap(funMap: MutableMap, func: OCFunctionDefinition) { 33 | val v = CModifier.symbol(func) 34 | funMap["name"] = "$v ${func.name}" 35 | CommentUtils.childComment(func, funMap) 36 | } 37 | 38 | override fun classMap(func: OCFunctionDefinition, relData: RelData): MutableMap? { 39 | val psiClass = func.namespaceQualifier ?: return null 40 | val classMap = mutableMapOf() 41 | psiClass.name?.let { classMap["sign"] = it } 42 | psiClass.name?.let { classMap["name"] = it } 43 | return classMap 44 | } 45 | 46 | override fun callList(func: OCFunctionDefinition, call: Boolean): List { 47 | return if (call) Call.findRefs(PsiTreeUtil.findChildrenOfAnyType(func, OCReferenceElement::class.java)) 48 | .filterIsInstance() 49 | .map { it.parent } 50 | .filterIsInstance() 51 | else Call.find(func, false, funClass()) 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/common/ui/UiUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.common.ui 2 | 3 | import com.intellij.openapi.progress.ProgressIndicator 4 | import com.intellij.openapi.progress.Task 5 | import com.intellij.openapi.project.Project 6 | import java.awt.event.FocusAdapter 7 | import java.awt.event.FocusEvent 8 | import java.util.function.BiConsumer 9 | import java.util.function.Consumer 10 | import javax.swing.event.DocumentEvent 11 | import javax.swing.event.DocumentListener 12 | import javax.swing.text.JTextComponent 13 | 14 | object UiUtils { 15 | @JvmStatic 16 | fun lineCount(jTextComponent: JTextComponent): Int { 17 | val str: String = jTextComponent.text.trim() 18 | return str.length - str.replace("\n", "").length 19 | } 20 | 21 | @JvmStatic 22 | fun onChange(jTextComponent: JTextComponent, initValue: String, func: Consumer) { 23 | onChangeEvent(jTextComponent, initValue) { _, s -> func.accept(s) } 24 | } 25 | 26 | @JvmStatic 27 | fun onChangeEvent(jTextComponent: JTextComponent, initValue: String, onChange: BiConsumer) { 28 | jTextComponent.removeAll() 29 | jTextComponent.document.addDocumentListener(object : DocumentListener { 30 | override fun insertUpdate(e: DocumentEvent) { 31 | onChange.accept(e, jTextComponent.text) 32 | } 33 | 34 | override fun removeUpdate(e: DocumentEvent) { 35 | onChange.accept(e, jTextComponent.text) 36 | } 37 | 38 | override fun changedUpdate(e: DocumentEvent) { 39 | onChange.accept(e, jTextComponent.text) 40 | } 41 | }) 42 | jTextComponent.text = initValue 43 | } 44 | 45 | @JvmStatic 46 | fun onFocusLost(jTextComponent: JTextComponent, project: Project, onFocusLost: Consumer) { 47 | jTextComponent.removeAll() 48 | jTextComponent.addFocusListener(object : FocusAdapter() { 49 | override fun focusLost(e: FocusEvent) { 50 | object : Task.Backgroundable(project, "draw onFocusLost") { 51 | override fun run(indicator: ProgressIndicator) { 52 | onFocusLost.accept(e) 53 | } 54 | }.queue() 55 | } 56 | }) 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/printer/InstallMermaid.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.printer 2 | 3 | import com.github.linwancen.plugin.graph.settings.DrawGraphAppState 4 | import com.intellij.execution.CommandLineUtil 5 | import com.intellij.execution.configurations.GeneralCommandLine 6 | import com.intellij.execution.process.ScriptRunnerUtil 7 | import com.intellij.openapi.progress.ProgressIndicator 8 | import com.intellij.openapi.progress.Task 9 | import com.intellij.openapi.project.Project 10 | import org.apache.commons.lang3.SystemUtils 11 | import org.slf4j.LoggerFactory 12 | import java.io.File 13 | import java.nio.charset.StandardCharsets 14 | import java.nio.file.Files 15 | 16 | /** 17 | * on GraphWindowFactory 18 | */ 19 | object InstallMermaid { 20 | private val LOG = LoggerFactory.getLogger(this::class.java) 21 | 22 | @JvmStatic 23 | fun checkAndInstall() { 24 | val src = InstallMermaid.javaClass.getResourceAsStream("/jcef/mermaid.js") ?: return 25 | val file = File(DrawGraphAppState.of().tempPath, "mermaid.js") 26 | try { 27 | if (!file.exists()) { 28 | Files.copy(src, file.toPath()) 29 | } 30 | } catch (e: Exception) { 31 | DrawGraphAppState.of().online = true 32 | LOG.warn( 33 | "can not copy mermaid.js to {} {} {}", 34 | SystemUtils.OS_NAME, SystemUtils.OS_VERSION, file.absolutePath, e 35 | ) 36 | } 37 | } 38 | 39 | @JvmStatic 40 | fun openDir(project: Project) { 41 | object : Task.Backgroundable(project, "draw open dir") { 42 | override fun run(indicator: ProgressIndicator) { 43 | try { 44 | val tempPath = DrawGraphAppState.of().tempPath 45 | val parameters = arrayListOf("/c", "start", tempPath) 46 | val commandLine = CommandLineUtil.toCommandLine("cmd", parameters) 47 | val generalCommandLine = GeneralCommandLine(commandLine) 48 | generalCommandLine.charset = StandardCharsets.UTF_8 49 | generalCommandLine.setWorkDirectory(tempPath) 50 | ScriptRunnerUtil.getProcessOutput(generalCommandLine) 51 | return 52 | } catch (_: Exception) { 53 | // ignore 54 | } 55 | } 56 | }.queue() 57 | } 58 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html 2 | 3 | pluginGroup = com.github.linwancen.drawgraph 4 | pluginName = draw-graph 5 | pluginRepositoryUrl = https://github.com/LinWanCen/draw-graph 6 | # SemVer format -> https://semver.org 7 | pluginVersion = 1.30 8 | 9 | # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html 10 | # 2020.2 use JCEF, 2020.1 is JavaFx, Should Set JCEF JBR 11 | pluginSinceBuild = 201 12 | pluginUntilBuild = 13 | 14 | # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension 15 | #platformType = IC 16 | platformType = IU 17 | #platformType = CL 18 | #platformVersion = 2020.1 19 | platformVersion = 2020.2 20 | 21 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html 22 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 23 | platformPlugins = java, org.jetbrains.kotlin, org.intellij.groovy \ 24 | , JavaScript \ 25 | , Pythonid:202.6397.98 \ 26 | , org.intellij.scala:2020.2.49 \ 27 | , com.jetbrains.php:202.6397.115 \ 28 | , org.jetbrains.plugins.go:202.6397.94 \ 29 | , org.jetbrains.plugins.ruby:202.6397.59 \ 30 | , org.rust.lang:0.3.140.3644-202 \ 31 | , org.toml.lang:0.2.140.3644-202 \ 32 | , PsiViewer:202-SNAPSHOT.3 33 | 34 | platformPlugins_2020_1 = java, org.jetbrains.kotlin, org.intellij.groovy \ 35 | , JavaScript \ 36 | , Pythonid:201.6668.121 \ 37 | , org.intellij.scala:2020.1.43 \ 38 | , com.jetbrains.php:201.6668.153 \ 39 | , org.jetbrains.plugins.go:201.6668.60.126 \ 40 | , org.jetbrains.plugins.ruby:201.6668.113 \ 41 | , org.rust.lang:0.3.131.3366-201 \ 42 | , org.toml.lang:0.2.131.3366-201 \ 43 | , PsiViewer:201.6251.22-EAP-SNAPSHOT.3 44 | 45 | # Gradle Releases -> https://github.com/gradle/gradle/releases 46 | gradleVersion = 7.6 47 | 48 | # Opt-out flag for bundling Kotlin standard library -> https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library 49 | # suppress inspection "UnusedProperty" 50 | kotlin.stdlib.default.dependency = false 51 | 52 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html 53 | # suppress inspection "UnusedProperty" 54 | # Enabling it will cause the *.java with *.form constructor NPE 55 | #org.gradle.unsafe.configuration-cache = true 56 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # draw-graph Changelog 4 | 5 | ## [1.0.0] 6 | 7 | - 1.30 Read db table info generated by SQL List plugin 8 | - 1.29 Effect UI and Add Impl Anno Value 9 | - 1.28 Support overloading method 10 | - 1.27 effect.txt 11 | - 1.26 Anno Doc 12 | - 1.25 json format and base64, timestamp, jsonStr, multi line 13 | - 1.24 doc from super method 14 | - 1.23 dir call graph and save sqlite 15 | - 1.22 ★ method usage graph 16 | - 1.21 PlantUML support RegExp, JSON, yaml, HTML injected 17 | - 1.20 PlantUML support RegExp, JSON, yaml file 18 | - 1.19 linux path use ~ and setting DrawGraphSetting.properties 19 | - 1.18 Support 2020.1 set JBR with JCEF 20 | - 1.17 Preview HTML File 21 | - 1.16 ★ method call graph 22 | - 1.15 Support .puml and navigate to class_method 23 | - 1.14 Maven dependencies graph include/exclude 24 | - 1.13 Rel Controller|Service|Mapper 25 | - 1.12 ★ click to navigate 26 | - 1.11 doc setting (len=20) 27 | - 1.10 support Python, Go, Rust, C/C++/OC, PHP, JS/TS etc. 28 | - 1.09 direction setting 29 | - 1.08 skip get/set/is(FieldName) 30 | - 1.07 Add Symbol: + public - private # protected ~ package 31 | - S static O Override A abstract C Constructor o open 32 | - 1.06 Async for 2023.3 33 | - 1.05 Support Kotlin call graph 34 | - 1.04 PlantUML and Graphviz 35 | - 1.03 file button and reset button 36 | - 1.02 include/exclude and skip get set is 37 | - 1.01 Maven dependencies graph with mermaid.js 38 | - 1.00 Method call graph with mermaid.js 39 | 40 | # 中文更新日志 41 | 42 | - 1.30 读取 SQL List 插件生成的表信息 43 | - 1.29 影响清单界面和支持添加实现类的注解值 44 | - 1.28 支持重载方法 45 | - 1.27 影响清单文件 46 | - 1.26 注解注释 47 | - 1.25 JSON 格式化与 base64, 时间戳, json 字符串, 多行文本 48 | - 1.24 从父方法获取注释 49 | - 1.23 目录调用图和保存到 SQLite 50 | - 1.22 ★ 方法被调用图 51 | - 1.21 PlantUML 支持 RegExp, JSON, yaml, HTML 注入 52 | - 1.20 PlantUML 支持 RegExp, JSON, yaml 文件 53 | - 1.19 linux 目录用 ~ 和设置 DrawGraphSetting.properties 54 | - 1.18 支持 2020.1 设置 JCEF 的 JBR 55 | - 1.17 预览 HTML 文件 56 | - 1.16 ★ 方法调用图 57 | - 1.15 支持 .puml 文件和跳转 class_method 58 | - 1.14 Maven 关系图 包含排除 59 | - 1.13 关联 Controller|Service|Mapper 60 | - 1.12 ★ 点击跳转 61 | - 1.11 注释设置(长度 20) 62 | - 1.10 支持 Python, Go, Rust, C/C++/OC, PHP, JS/TS 等 63 | - 1.09 方向设置 64 | - 1.08 跳过 get/set/is 在有同名字段的时候 65 | - 1.07 添加符号:+ 公有 - 私有 # 保护 ~ 包级 66 | - S 静态 O 重写 A 抽象 C 构造 o 可重写 67 | - 1.06 异步用于 2023.3 68 | - 1.05 支持 Kotlin 调用图 69 | - 1.04 PlantUML 与 Graphviz 实现 70 | - 1.03 文件按钮 与 重置按钮 71 | - 1.02 包含排除功能与跳过 get set is 72 | - 1.01 Maven 关系图 mermaid.js 版 73 | - 1.00 方法调用关系图 mermaid.js 版 -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/CommentUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser 2 | 3 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils 4 | import com.intellij.psi.PsiComment 5 | import com.intellij.psi.PsiElement 6 | import com.intellij.psi.util.PsiTreeUtil 7 | import java.util.regex.Pattern 8 | 9 | object CommentUtils { 10 | 11 | @JvmStatic 12 | fun byteToSrc(func: F): F { 13 | val navElement = func.navigationElement ?: return func 14 | try { 15 | @Suppress("UNCHECKED_CAST") 16 | return navElement as F 17 | } catch (e: Throwable) { 18 | // ignore e 19 | return func 20 | } 21 | } 22 | 23 | /** 24 | * use in C/C++/OC 25 | */ 26 | @Suppress("unused") 27 | fun childComment(element: PsiElement, map: MutableMap) { 28 | val psiComment = PsiTreeUtil.getChildOfType(element, PsiComment::class.java) ?: return 29 | val doc = doc(PsiUnSaveUtils.getText(psiComment)) 30 | map["@0"] = doc 31 | map["@1"] = doc 32 | } 33 | 34 | @JvmStatic 35 | private val DOC_PATTERN: Pattern = Pattern.compile( 36 | "(?m" + 37 | // ///// xx line start 38 | ")^ *//++ *+" + 39 | // /**** xx block start 40 | "|^ */\\*++ *+" + 41 | // ****/ xx block end, not only line start and must before **** 42 | "| *\\*++/.*" + 43 | // **** xx block body 44 | "|^ *\\*++ *+" + 45 | // {@link A} 46 | "|\\{@\\w++|}" + 47 | // #### xx python and shell start 48 | "|^ *#++ *+" + 49 | // -- xx SQL 50 | "|^ *--++ *+" 51 | ) 52 | 53 | @JvmStatic 54 | private val HTML_PATTERN: Pattern = Pattern.compile("<[^>]++>") 55 | 56 | @JvmStatic 57 | fun doc(s: String): String { 58 | return DOC_PATTERN.matcher(s).replaceAll(" ").trim { it <= ' ' } 59 | } 60 | 61 | @JvmStatic 62 | fun split(s: String, map: MutableMap) { 63 | val split = PsiUnSaveUtils.LINE_END_PATTERN.split(s) 64 | for ((index, line) in split.withIndex()) { 65 | map["@${index + 1}"] = line 66 | } 67 | } 68 | 69 | @JvmStatic 70 | fun html2Text(s: String): String { 71 | return HTML_PATTERN.matcher(s).replaceAll(" ").trim { it <= ' ' } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/java/JavaComment.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.java 2 | 3 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils 4 | import com.github.linwancen.plugin.common.text.DocText 5 | import com.intellij.psi.PsiElement 6 | import com.intellij.psi.PsiWhiteSpace 7 | import com.intellij.psi.javadoc.PsiDocComment 8 | 9 | open class JavaComment { 10 | companion object { 11 | private val INSTANCE = JavaComment() 12 | 13 | fun addDocParam(docComment: PsiDocComment?, map: MutableMap) { 14 | INSTANCE.addDocParam(docComment, map) 15 | } 16 | } 17 | 18 | open fun addDocParam(docComment: PsiDocComment?, map: MutableMap) { 19 | if (docComment == null) { 20 | return 21 | } 22 | addDescription(docComment.descriptionElements.asSequence(), map) 23 | addTag(docComment, map) 24 | } 25 | 26 | open fun addDescription(elements: Sequence, map: MutableMap) { 27 | val all = StringBuilder() 28 | val currLine = StringBuilder() 29 | var lineCount = 1 30 | for (element in elements) { 31 | if (appendElementText(element, all, currLine)) { 32 | map["@$lineCount"] = currLine.toString() 33 | lineCount++ 34 | currLine.clear() 35 | } 36 | } 37 | if (currLine.isNotEmpty()) { 38 | map["@$lineCount"] = currLine.toString() 39 | } 40 | if (all.isNotEmpty()) { 41 | map["@0"] = all.toString() 42 | } 43 | } 44 | 45 | open fun appendElementText(element: PsiElement, all: StringBuilder, currLine: StringBuilder): Boolean { 46 | if (element is PsiWhiteSpace && currLine.isNotEmpty()) { 47 | return true 48 | } 49 | val children = element.children 50 | if (children.isNotEmpty()) { 51 | if (children.size >= 3) { 52 | DocText.addHtmlText(PsiUnSaveUtils.getText(children[children.size - 2]), all, currLine) 53 | } 54 | return false 55 | } 56 | DocText.addHtmlText(PsiUnSaveUtils.getText(element), all, currLine) 57 | return false 58 | } 59 | 60 | open fun addTag(docComment: PsiDocComment, map: MutableMap) { 61 | for (tag in docComment.tags) { 62 | val name = tag.name 63 | val valueElement = tag.valueElement ?: tag.dataElements.firstOrNull() ?: continue 64 | val value = DocText.addHtmlText(PsiUnSaveUtils.getText(valueElement)) ?: continue 65 | map["@$name"] = value 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/rust/RustParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.rust 2 | 3 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils 4 | import com.github.linwancen.plugin.graph.parser.Call 5 | import com.github.linwancen.plugin.graph.parser.ParserLang 6 | import com.github.linwancen.plugin.graph.parser.RelData 7 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 8 | import com.intellij.psi.PsiComment 9 | import com.intellij.psi.util.PsiTreeUtil 10 | import org.rust.lang.RsLanguage 11 | import org.rust.lang.core.psi.* 12 | 13 | class RustParser : ParserLang() { 14 | 15 | override fun id(): String { 16 | return RsLanguage.id 17 | } 18 | 19 | override fun funClass(): Class { 20 | return RsFunction::class.java 21 | } 22 | 23 | override fun skipFun(state: DrawGraphProjectState, func: RsFunction): Boolean { 24 | return false 25 | } 26 | 27 | override fun toSign(func: RsFunction): String { 28 | val mod = PsiTreeUtil.getParentOfType(func, RsModItem::class.java)?.name 29 | val implItem = PsiTreeUtil.getParentOfType(func, RsImplItem::class.java) 30 | val struct = implItem?.firstChild?.let { PsiUnSaveUtils.getText(it) } 31 | var sign = "${func.name}" 32 | if (struct != null) { 33 | sign = "$struct.$sign" 34 | } 35 | if (mod != null) { 36 | sign = "$mod.$sign" 37 | } 38 | return sign 39 | } 40 | 41 | override fun funMap(funMap: MutableMap, func: RsFunction) { 42 | val v = RustModifier.symbol(func) 43 | funMap["name"] = "$v ${func.name}" 44 | } 45 | 46 | override fun classMap(func: RsFunction, relData: RelData): MutableMap? { 47 | val mod = PsiTreeUtil.getParentOfType(func, RsModItem::class.java)?.name 48 | val implItem = PsiTreeUtil.getParentOfType(func, RsImplItem::class.java) 49 | val struct = implItem?.firstChild?.let { PsiUnSaveUtils.getText(it) } 50 | val name = struct ?: mod ?: return null 51 | val classMap = mutableMapOf() 52 | classMap["name"] = name 53 | classMap["sign"] = when { 54 | struct != null && mod != null -> "$mod.$struct" 55 | else -> name 56 | } 57 | return classMap 58 | } 59 | 60 | override fun callList(func: RsFunction, call: Boolean): List { 61 | if (!call) { 62 | return Call.find(func, false, funClass()) 63 | } 64 | val refs = PsiTreeUtil 65 | .findChildrenOfAnyType(func, RsPath::class.java, RsMethodCall::class.java) 66 | .filter { PsiTreeUtil.getNonStrictParentOfType(it, PsiComment::class.java) == null } 67 | return Call.findRefs(refs).filterIsInstance() 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/kotlin/KotlinParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.kotlin 2 | 3 | import com.github.linwancen.plugin.graph.parser.Call 4 | import com.github.linwancen.plugin.graph.parser.ParserLang 5 | import com.github.linwancen.plugin.graph.parser.ParserUtils 6 | import com.github.linwancen.plugin.graph.parser.RelData 7 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 8 | import org.jetbrains.kotlin.idea.KotlinLanguage 9 | import org.jetbrains.kotlin.nj2k.postProcessing.type 10 | import org.jetbrains.kotlin.psi.KtClassOrObject 11 | import org.jetbrains.kotlin.psi.KtNameReferenceExpression 12 | import org.jetbrains.kotlin.psi.KtNamedFunction 13 | import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject 14 | 15 | class KotlinParser : ParserLang() { 16 | 17 | override fun id(): String { 18 | return KotlinLanguage.INSTANCE.id 19 | } 20 | 21 | override fun funClass(): Class { 22 | return KtNamedFunction::class.java 23 | } 24 | 25 | override fun skipFun(state: DrawGraphProjectState, func: KtNamedFunction): Boolean { 26 | return false 27 | } 28 | 29 | override fun toSign(func: KtNamedFunction): String? { 30 | val classOrObject = func.containingClassOrObject ?: return "#${func.name}" 31 | val params = params(classOrObject, func) 32 | return "${classOrObject.fqName ?: return null}#${func.name}$params" 33 | } 34 | 35 | fun params(classOrObject: KtClassOrObject, func: KtNamedFunction): String { 36 | val its = classOrObject.declarations 37 | .filterIsInstance() 38 | .filter { it.name == func.name } 39 | if (its.size == 1) { 40 | return "" 41 | } 42 | return func.valueParameters.joinToString(prefix = "(", separator = ",", postfix = ")") { 43 | it.type().toString() 44 | } 45 | } 46 | 47 | override fun funMap(funMap: MutableMap, func: KtNamedFunction) { 48 | val v = KotlinModifier.symbol(func) 49 | funMap["name"] = "$v ${func.name}${ParserUtils.signParams(funMap)}" 50 | KotlinComment.addDocParam(func.docComment, funMap) 51 | } 52 | 53 | override fun classMap(func: KtNamedFunction, relData: RelData): MutableMap? { 54 | val psiClass = func.containingClassOrObject ?: return null 55 | val classMap = mutableMapOf() 56 | psiClass.name?.let { classMap["name"] = it } 57 | psiClass.fqName?.let { classMap["sign"] = it.asString() } 58 | KotlinComment.addDocParam(psiClass.docComment, classMap) 59 | return classMap 60 | } 61 | 62 | override fun callList(func: KtNamedFunction, call: Boolean): List { 63 | return Call.find(func, call, KtNameReferenceExpression::class.java) 64 | } 65 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/printer/Printer.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.printer 2 | 3 | import com.github.linwancen.plugin.graph.parser.RelData 4 | import com.github.linwancen.plugin.graph.settings.DrawGraphAppState 5 | 6 | abstract class Printer { 7 | protected open fun beforeGroup(groupMap: MutableMap) { 8 | // impl it 9 | } 10 | 11 | protected open fun afterGroup(groupMap: MutableMap) { 12 | // impl it 13 | } 14 | 15 | protected open fun item(itemMap: MutableMap) { 16 | // impl it 17 | } 18 | 19 | protected open fun call(usageSign: String, callSign: String) { 20 | // impl it 21 | } 22 | 23 | protected open fun sign(input: String): String { 24 | return canNotUseSymbol.replace(input, "_") 25 | } 26 | 27 | protected open fun addLine(s: String?, sb: StringBuilder, newLineEscape: Boolean = false) { 28 | if (!DrawGraphAppState.of().doc) { 29 | return 30 | } 31 | var docLine = s?.replace("\"", "") ?: return 32 | if (docLine.length > 20) { 33 | docLine = docLine.substring(0, 20) 34 | } 35 | docLine = "${docLine.trim()}\n " 36 | if (newLineEscape) { 37 | docLine = docLine.replace("\n", "\\n") 38 | } 39 | sb.append(docLine) 40 | } 41 | 42 | abstract fun toSrc(relData: RelData): Pair 43 | 44 | protected fun printerData(relData: RelData) { 45 | // TODO 统一 filter 46 | relData.parentChildMap 47 | .filter { !relData.childSet.contains(it.key) } 48 | .forEach { printerChildren(relData, it.key, it.value) } 49 | // PlantUML cannot re def 50 | relData.itemMap 51 | .filter { !relData.parentChildMap.containsKey(it.key) } 52 | .filter { !relData.childSet.contains(it.key) } 53 | .forEach { item(it.value) } 54 | // PlantUML must def before 55 | relData.callSet.forEach { call(it.first, it.second) } 56 | } 57 | 58 | private fun printerChildren(relData: RelData, parent: String, children: Set) { 59 | val parentMap = relData.itemMap[parent] ?: return 60 | beforeGroup(parentMap) 61 | for (child in children) { 62 | val strings = relData.parentChildMap[child] 63 | if (strings == null) { 64 | item(relData.itemMap[child] ?: continue) 65 | } else { 66 | printerChildren(relData, child, strings) 67 | } 68 | } 69 | afterGroup(parentMap) 70 | } 71 | 72 | companion object { 73 | 74 | /** 75 | * [not support english symbol #4138](https://github.com/mermaid-js/mermaid/issues/4138) 76 | * PlantUML cannot use "-#$,"` 77 | * "-" should in first 78 | */ 79 | @JvmStatic 80 | val canNotUseSymbol = Regex("[-#$,。?!,、;:“”‘’`()《》【】~@()|'\"<>{}\\[\\]\\\\/ ]") 81 | } 82 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/ui/PlantUmlFileController.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui 2 | 3 | import com.fasterxml.jackson.databind.MapperFeature 4 | import com.fasterxml.jackson.databind.ObjectMapper 5 | import com.fasterxml.jackson.databind.SerializationFeature 6 | import com.github.linwancen.plugin.common.psi.LangUtils 7 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils 8 | import com.github.linwancen.plugin.common.text.ArrayNewLinePrinter 9 | import com.github.linwancen.plugin.common.text.JsonValueParser 10 | import com.github.linwancen.plugin.graph.printer.PrinterData 11 | import com.github.linwancen.plugin.graph.printer.PrinterPlantuml 12 | import com.intellij.openapi.application.runInEdt 13 | import com.intellij.openapi.project.DumbService 14 | import com.intellij.openapi.project.Project 15 | import com.intellij.openapi.vfs.VirtualFile 16 | import com.intellij.psi.PsiElement 17 | import com.intellij.psi.PsiManager 18 | 19 | object PlantUmlFileController { 20 | @JvmStatic 21 | val languages = arrayOf("RegExp", "JSON", "yaml") 22 | 23 | @JvmStatic 24 | fun forPlantUMLFiles(project: Project, window: GraphWindow, files: Array) { 25 | if (files.size != 1) return 26 | val virtualFile = files[0] 27 | val name = virtualFile.name 28 | DumbService.getInstance(project).runReadActionInSmartMode { 29 | val src = PsiUnSaveUtils.fileText(project, files[0]) ?: return@runReadActionInSmartMode 30 | if (name.endsWith(".puml") || name.endsWith(".plantuml")) { 31 | forPlantUMLSrc(project, window, src) 32 | } else { 33 | val psiFile = PsiManager.getInstance(project).findFile(files[0]) ?: return@runReadActionInSmartMode 34 | forPlantUMLSupportFile(psiFile, project, window, src) 35 | } 36 | } 37 | } 38 | 39 | @JvmStatic 40 | fun forPlantUMLSupportFile(psiFile: PsiElement, project: Project, window: GraphWindow, src: String?) { 41 | val languageId = LangUtils.matchBaseLanguageId(psiFile, *languages) ?: return 42 | val code = if (!languageId.contains("JSON")) { 43 | src 44 | } else { 45 | val mapper = ObjectMapper() 46 | .enable(SerializationFeature.INDENT_OUTPUT) 47 | .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) 48 | .setDefaultPrettyPrinter(ArrayNewLinePrinter()) 49 | val jsonNode = mapper.readTree(src) 50 | val convert = JsonValueParser.convert(mapper, jsonNode) 51 | mapper.writeValueAsString(convert) 52 | } 53 | forPlantUMLSrc(project, window, "@start$languageId\n$code\n@end$languageId") 54 | } 55 | 56 | @JvmStatic 57 | fun forPlantUMLSrc(project: Project, window: GraphWindow, plantumlSrc: String?) { 58 | runInEdt { 59 | window.toolWindow.activate(null) 60 | window.plantumlSrc.text = plantumlSrc 61 | PrinterPlantuml.build(PrinterData(plantumlSrc, null, project)) { 62 | runInEdt { 63 | window.plantumlHtml.text = it 64 | if (window.plantumlBrowser != null) window.plantumlBrowser?.load(it) 65 | } 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/ui/RelDataController.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui 2 | 3 | import com.github.linwancen.plugin.graph.parser.RelData 4 | import com.github.linwancen.plugin.graph.printer.* 5 | import com.github.linwancen.plugin.graph.settings.DrawGraphAppState 6 | import com.github.linwancen.plugin.graph.ui.webview.Browser 7 | import com.intellij.openapi.application.runInEdt 8 | import com.intellij.openapi.project.Project 9 | import javax.swing.JTextArea 10 | 11 | object RelDataController { 12 | 13 | @JvmStatic 14 | fun dataToWindow( 15 | project: Project, 16 | window: GraphWindow, 17 | relData: RelData, 18 | isCall: Boolean, 19 | ) { 20 | RelData2Effect().save(project, relData, isCall) { set, s -> 21 | runInEdt { 22 | window.toolWindow.activate(null) 23 | window.effect.text = s 24 | } 25 | } 26 | RelData2SQLite().save(project, relData) 27 | 28 | val (plantumlSrc, plantumlJs) = PrinterPlantuml().toSrc(relData) 29 | val (mermaidSrc, _) = PrinterMermaid().toSrc(relData) 30 | val (graphvizSrc, graphvizJs) = PrinterGraphviz().toSrc(relData) 31 | val limit = DrawGraphAppState.of().limit 32 | if (relData.itemMap.size > limit) { 33 | val it = "itemMap.size: ${relData.itemMap.size} > limit: $limit\n
" + 34 | "callSet.size: ${relData.callSet.size}\n
" + 35 | "parentChildMap.size: ${relData.parentChildMap.size}\n
" + 36 | "childSet.size: ${relData.childSet.size}" 37 | runInEdt { 38 | window.toolWindow.activate(null) 39 | window.mermaidSrc.text = it 40 | window.plantumlSrc.text = it 41 | window.graphvizSrc.text = it 42 | } 43 | update(it, window.plantumlHtml, window.plantumlBrowser) 44 | update(it, window.mermaidHtml, window.mermaidBrowser) 45 | update(it, window.graphvizHtml, window.graphvizBrowser) 46 | PrinterPlantuml.build(PrinterData(plantumlSrc, plantumlJs, project), null) 47 | PrinterMermaid.build(PrinterData(mermaidSrc, null, project), null) 48 | PrinterGraphviz.build(PrinterData(graphvizSrc, graphvizJs, project), null) 49 | return 50 | } 51 | runInEdt { 52 | window.toolWindow.activate(null) 53 | // don't stop when src does not change, update file when open multi project 54 | window.mermaidSrc.text = mermaidSrc 55 | window.plantumlSrc.text = plantumlSrc 56 | window.graphvizSrc.text = graphvizSrc 57 | } 58 | PrinterPlantuml.build(PrinterData(plantumlSrc, plantumlJs, project)) { 59 | update(it, window.plantumlHtml, window.plantumlBrowser) 60 | } 61 | PrinterMermaid.build(PrinterData(mermaidSrc, null, project)) { 62 | update(it, window.mermaidHtml, window.mermaidBrowser) 63 | } 64 | PrinterGraphviz.build(PrinterData(graphvizSrc, graphvizJs, project)) { 65 | update(it, window.graphvizHtml, window.graphvizBrowser) 66 | } 67 | } 68 | 69 | private fun update(src: String, jTextArea: JTextArea, browser: Browser?) { 70 | runInEdt { 71 | if (jTextArea.text != src) { 72 | jTextArea.text = src 73 | browser?.load(src) 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/java/JavaParserUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.java 2 | 3 | import com.github.linwancen.plugin.graph.parser.ParserUtils 4 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 5 | import com.intellij.psi.PsiClass 6 | import com.intellij.psi.PsiElement 7 | import com.intellij.psi.PsiMethod 8 | import com.intellij.psi.javadoc.PsiDocComment 9 | 10 | /** 11 | * java & scala 12 | */ 13 | object JavaParserUtils { 14 | @JvmStatic 15 | fun skipFun(state: DrawGraphProjectState, func: PsiMethod): Boolean { 16 | return (func.isConstructor && !func.hasParameters()) 17 | || state.skipGetSetIs && GetSetIs.isGetSetIs(func, func.containingClass ?: return false) 18 | } 19 | 20 | @JvmStatic 21 | fun funMap(func: PsiMethod, funMap: MutableMap) { 22 | funMapWithoutDoc(func, funMap) 23 | val docComment = supperMethodDoc(func) 24 | JavaComment.addDocParam(docComment, funMap) 25 | } 26 | 27 | @JvmStatic 28 | fun supperMethodDoc(psiMethod: PsiMethod): PsiDocComment? { 29 | val docComment = psiMethod.docComment 30 | if (docComment != null) { 31 | return docComment 32 | } 33 | val superMethods: Array 34 | try { 35 | superMethods = psiMethod.findSuperMethods() 36 | } catch (e: Exception) { 37 | return null 38 | } 39 | for (superMethod in superMethods) { 40 | // .class 41 | val navElement: PsiElement 42 | try { 43 | navElement = superMethod.navigationElement 44 | } catch (e: Exception) { 45 | continue 46 | } 47 | if (navElement is PsiMethod) { 48 | val superDoc = navElement.docComment 49 | if (superDoc != null) { 50 | return superDoc 51 | } 52 | } 53 | } 54 | return null 55 | } 56 | 57 | @JvmStatic 58 | fun sign(func: PsiMethod): String? { 59 | val clazz = func.containingClass ?: return "#${func.name}" 60 | return "${clazz.qualifiedName ?: return null}#${func.name}${params(clazz, func)}" 61 | } 62 | 63 | @JvmStatic 64 | fun params(clazz: PsiClass, func: PsiMethod): String { 65 | val its = clazz.findMethodsByName(func.name, false) 66 | if (its.size == 1) { 67 | return "" 68 | } 69 | return func.parameterList.parameters.joinToString(prefix = "(", separator = ",", postfix = ")") { 70 | it.type.toString().substringAfterLast(':') 71 | } 72 | } 73 | 74 | @JvmStatic 75 | fun funMapWithoutDoc(func: PsiMethod, funMap: MutableMap) { 76 | val v = JavaModifier.symbol(func) 77 | funMap["name"] = "$v ${func.name}${ParserUtils.signParams(funMap)}" 78 | JavaAnno.addAnno(func, funMap) 79 | } 80 | 81 | @JvmStatic 82 | fun classMap(func: PsiMethod): MutableMap? { 83 | val (psiClass, classMap) = classMapWithoutDoc(func) ?: return null 84 | JavaComment.addDocParam(psiClass.docComment, classMap) 85 | return classMap 86 | } 87 | 88 | @JvmStatic 89 | fun classMapWithoutDoc(func: PsiMethod): Pair>? { 90 | val psiClass = func.containingClass ?: return null 91 | val classMap = mutableMapOf() 92 | psiClass.qualifiedName?.let { classMap["sign"] = it } 93 | psiClass.name?.let { classMap["name"] = it } 94 | JavaAnno.addAnno(psiClass, classMap) 95 | return Pair(psiClass, classMap) 96 | } 97 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions Workflow created for handling the release process based on the draft release prepared with the Build workflow. 2 | # Running the publishPlugin task requires all following secrets to be provided: PUBLISH_TOKEN, PRIVATE_KEY, PRIVATE_KEY_PASSWORD, CERTIFICATE_CHAIN. 3 | # See https://plugins.jetbrains.com/docs/intellij/plugin-signing.html for more information. 4 | 5 | name: Release 6 | on: 7 | release: 8 | types: [prereleased, released] 9 | 10 | jobs: 11 | 12 | # Prepare and publish the plugin to the Marketplace repository 13 | release: 14 | name: Publish Plugin 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | pull-requests: write 19 | steps: 20 | 21 | # Check out current repository 22 | - name: Fetch Sources 23 | uses: actions/checkout@v3 24 | with: 25 | ref: ${{ github.event.release.tag_name }} 26 | 27 | # Setup Java 11 environment for the next steps 28 | - name: Setup Java 29 | uses: actions/setup-java@v3 30 | with: 31 | distribution: zulu 32 | java-version: 11 33 | 34 | # Set environment variables 35 | - name: Export Properties 36 | id: properties 37 | shell: bash 38 | run: | 39 | CHANGELOG="$(cat << 'EOM' | sed -e 's/^[[:space:]]*$//g' -e '/./,$!d' 40 | ${{ github.event.release.body }} 41 | EOM 42 | )" 43 | 44 | CHANGELOG="${CHANGELOG//'%'/'%25'}" 45 | CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" 46 | CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" 47 | 48 | echo "changelog=$CHANGELOG" >> $GITHUB_OUTPUT 49 | 50 | # Update Unreleased section with the current release note 51 | - name: Patch Changelog 52 | if: ${{ steps.properties.outputs.changelog != '' }} 53 | env: 54 | CHANGELOG: ${{ steps.properties.outputs.changelog }} 55 | run: | 56 | ./gradlew patchChangelog --release-note="$CHANGELOG" 57 | 58 | # Publish the plugin to the Marketplace 59 | - name: Publish Plugin 60 | env: 61 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} 62 | CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} 63 | PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} 64 | PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} 65 | run: ./gradlew publishPlugin 66 | 67 | # Upload artifact as a release asset 68 | - name: Upload Release Asset 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | run: gh release upload ${{ github.event.release.tag_name }} ./build/distributions/* 72 | 73 | # Create pull request 74 | - name: Create Pull Request 75 | if: ${{ steps.properties.outputs.changelog != '' }} 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | run: | 79 | VERSION="${{ github.event.release.tag_name }}" 80 | BRANCH="changelog-update-$VERSION" 81 | LABEL="release changelog" 82 | 83 | git config user.email "action@github.com" 84 | git config user.name "GitHub Action" 85 | 86 | git checkout -b $BRANCH 87 | git commit -am "Changelog update - $VERSION" 88 | git push --set-upstream origin $BRANCH 89 | 90 | gh label create "$LABEL" \ 91 | --description "Pull requests with release changelog update" \ 92 | || true 93 | 94 | gh pr create \ 95 | --title "Changelog update - \`$VERSION\`" \ 96 | --body "Current pull request contains patched \`CHANGELOG.md\` file for the \`$VERSION\` version." \ 97 | --label "$LABEL" \ 98 | --head $BRANCH 99 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/xml/RelServicePom.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.xml 2 | 3 | import com.github.linwancen.plugin.common.text.Skip 4 | import com.github.linwancen.plugin.graph.parser.RelData 5 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 6 | import com.intellij.psi.xml.XmlDocument 7 | import com.intellij.psi.xml.XmlFile 8 | import com.intellij.psi.xml.XmlTag 9 | 10 | /** 11 | * for pom.xml 12 | */ 13 | object RelServicePom { 14 | 15 | fun parsePom( 16 | psiFile: XmlFile, 17 | callSetMap: MutableMap>, 18 | relData: RelData, 19 | ) { 20 | for (it in psiFile.children) { 21 | if (it !is XmlDocument) { 22 | continue 23 | } 24 | val root = it.rootTag ?: continue 25 | val rootInfo = info(root) ?: continue 26 | val sign = rootInfo["sign"] ?: continue 27 | val callSet = mutableSetOf() 28 | callSetMap[sign] = callSet 29 | 30 | module(root, relData, rootInfo) 31 | 32 | dependencies(root, callSet) 33 | } 34 | } 35 | 36 | private fun module( 37 | root: XmlTag, 38 | relData: RelData, 39 | rootInfo: MutableMap, 40 | ) { 41 | val modules = root.findFirstSubTag("modules") 42 | if (modules == null) { 43 | relData.regParentChild(rootInfo) 44 | } else { 45 | val items = modules.findSubTags("module") 46 | if (items.isEmpty()) { 47 | relData.regParentChild(rootInfo) 48 | } else { 49 | for (item in items) { 50 | val info = mutableMapOf() 51 | info["sign"] = item.value.text 52 | relData.regParentChild(rootInfo, info) 53 | } 54 | } 55 | } 56 | } 57 | 58 | private fun dependencies(root: XmlTag, callList: MutableSet) { 59 | root.findFirstSubTag("dependencies")?.let { tag -> 60 | val items = tag.findSubTags("dependency") 61 | if (items.isNotEmpty()) { 62 | for (item in items) { 63 | val itemInfo = info(item) ?: continue 64 | callList.add(itemInfo["sign"] ?: continue) 65 | } 66 | } 67 | } 68 | } 69 | 70 | @JvmStatic 71 | var regex = Regex("\\$\\{project.artifactId} ? \\|? ?") 72 | 73 | private fun info(xmlTag: XmlTag): MutableMap? { 74 | val artifactId = xmlTag.findFirstSubTag("artifactId")?.value?.text ?: return null 75 | val state = DrawGraphProjectState.of(xmlTag.project) 76 | if (Skip.skip(artifactId, state.otherIncludePattern, state.otherExcludePattern)) { 77 | return null 78 | } 79 | val info = mutableMapOf() 80 | info["sign"] = artifactId 81 | info["name"] = artifactId 82 | info["link"] = xmlTag.containingFile.virtualFile.path 83 | 84 | val description = xmlTag.findFirstSubTag("description")?.value?.text?.trim() ?: "" 85 | if (description.isNotEmpty()) { 86 | info["@0"] = description 87 | info["@1"] = description 88 | return info 89 | } 90 | var name = xmlTag.findFirstSubTag("name")?.value?.text ?: "" 91 | if (name.isNotEmpty()) { 92 | name = regex.replace(name, "").trim() 93 | } 94 | if (name.isNotEmpty()) { 95 | info["@0"] = name 96 | info["@1"] = name 97 | } 98 | return info 99 | } 100 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # draw-graph 2 | 3 | ![Build](https://github.com/LinWanCen/draw-graph/workflows/Build/badge.svg) 4 | [![Version](https://img.shields.io/jetbrains/plugin/v/21242-draw-graph.svg)](https://plugins.jetbrains.com/plugin/21242-draw-graph) 5 | [![Downloads](https://img.shields.io/jetbrains/plugin/d/21242-draw-graph.svg)](https://plugins.jetbrains.com/plugin/21242-draw-graph) 6 | 7 | ## Plugin description 插件介绍 8 | 9 | 10 | Method call usage graph and maven dependency graph, click to navigate 11 | 12 | 生成 方法调用图 和 Maven 依赖图,点击跳转 13 | 14 | - Java, Kotlin, Groovy, Scala 15 | - C/C++/OC, Python, Go, Rust, Ruby 16 | - JS/TS, PHP, Regexp, JSON, Yaml 17 | 18 | #### English Desc 19 | 20 | ##### How to Use 21 | 22 | One file call graph or all pom.xml dep graph: 23 | 1. Open Graph ToolWindow at Right 24 | 2. Open pom.xml/.java/.py... file 25 | 26 | Multi file call graph or partial pom.xml dep graph: 27 | 1. Select multi files in the same language 28 | 2. Open Right Click Menu 29 | 3. Select Method Call/Usage Graph 30 | 31 | RegExp string graph: 32 | 1. Open Right Click Menu at RegExp string 33 | 2. Select Regexp, JSON, yaml Graph or HTML 34 | 35 | Install [Graphviz] and set xx/bin to env Path for PlantUML and Graphviz. 36 | 37 | ##### About 38 | 39 | 2020.2+ is jcef(chrome) to support outline mermaid.js, 40 | 2020.1- is JavaFx WebView, is need set jcef-jbr and use online to see Mermaid. 41 | 42 | ##### My Plugin 43 | - Show doc comment in the Project view Tree, line End, json etc.: [Show Comment] 44 | - show line count for file / method, show children count for dir in project view:[Line Num] 45 | - Method call usage graph and maven dependency graph: [Draw Graph] 46 | - Find author/comment of multiple files or lines and export Find: [Find Author] 47 | - Auto sync coverage and capture coverage during debug: [Sync Coverage] 48 | 49 | --- 50 | 51 | #### 中文描述 52 | 53 | ##### 用法 54 | 55 | 单个文件调用图 或 所有 pom.xml 依赖图: 56 | 1. 打开右边的工具栏 57 | 2. 打开 pom.xml/.java/.py 等文件 58 | 59 | 多个文件调用图 或 部分 pom.xml 依赖图: 60 | 1. 在文件树选择多个同语言的文件打开右键菜单 61 | 2. 选择方法(被)调用图 62 | 63 | 字符串正则表达式图 64 | 1. 在正则表达式文本上打开右键菜单 65 | 2. 选择正则, JSON, yaml 图或 HTML 66 | 67 | 安装 [Graphviz] 并设置 bin 目录为环境变量以便使用 PlantUML 和 Graphviz。 68 | 69 | ##### 关于 70 | 71 | 2020.2 默认 jcef(chrome) 且支持离线 mermaid.js, 72 | 2020.1 默认 JavaFx WebView,需更换设置 jcef-jbr 且用在线模式才能看到 Mermaid 图。 73 | 74 | ##### 我的项目 75 | - 在文件树、行末、JSON 显示注释:[Show Comment] 76 | - 在文件树显示行数、文件数:[Line Num] 77 | - 生成 方法调用图 和 Maven 依赖图:[Draw Graph] 78 | - 查找多个文件或行的作者 与 导出搜索:[Find Author] 79 | - 自动同步覆盖率 和 调试中抓取覆盖率:[Sync Coverage] 80 | 81 | --- 82 | 83 | #### 支持 84 | 85 | 如果对你有所帮助,可以通过群或文章等形式分享给大家,在插件市场好评, 86 | 或者给本项目 [本项目 GitHub 主页][Draw Graph GitHub] 一个 Star,您的支持是项目前进的动力。 87 | 88 | [Graphviz]: https://graphviz.org/download/ 89 | [Show Comment]: https://plugins.jetbrains.com/plugin/18553-show-comment 90 | [Line Num]: https://plugins.jetbrains.com/plugin/23300-line-num 91 | [Draw Graph]: https://plugins.jetbrains.com/plugin/21242-draw-graph 92 | [Find Author]: https://plugins.jetbrains.com/plugin/20557-find-author 93 | [Sync Coverage]: https://plugins.jetbrains.com/plugin/20780-sync-coverage 94 | [Draw Graph GitHub]: https://github.com/LinWanCen/draw-graph 95 | 96 | 97 | 98 | ## Installation 99 | 100 | - Using IDE built-in plugin system: 101 | 102 | Settings/Preferences > Plugins > Marketplace > Search for "draw-graph" > 103 | Install Plugin 104 | 105 | - Manually: 106 | 107 | Download the [latest release](https://github.com/LinWanCen/draw-graph/releases/latest) and install it manually using 108 | Settings/Preferences > Plugins > ⚙️ > Install plugin from disk... 109 | 110 | [Changelog 更新说明](CHANGELOG.md) 111 | 112 | --- 113 | Plugin based on the [IntelliJ Platform Plugin Template][template]. 114 | 115 | [template]: https://github.com/JetBrains/intellij-platform-plugin-template -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/common/text/JsonValueParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.common.text 2 | 3 | import com.fasterxml.jackson.databind.JsonNode 4 | import com.fasterxml.jackson.databind.ObjectMapper 5 | import com.fasterxml.jackson.databind.node.JsonNodeFactory 6 | import com.fasterxml.jackson.databind.node.TextNode 7 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils 8 | import java.text.SimpleDateFormat 9 | import java.util.* 10 | import java.util.regex.Pattern 11 | 12 | object JsonValueParser { 13 | 14 | @JvmStatic 15 | fun convert(mapper: ObjectMapper, root: JsonNode): JsonNode { 16 | if (root.isObject) { 17 | val newObject = JsonNodeFactory.instance.objectNode() 18 | root.fields().forEach { (k, v) -> newObject.set(k, convert(mapper, v)) } 19 | return newObject 20 | } 21 | if (root.isArray) { 22 | val newObject = JsonNodeFactory.instance.objectNode() 23 | root.forEachIndexed { i, v -> newObject.set(i.toString(), convert(mapper, v)) } 24 | return newObject 25 | } 26 | if (root.isNumber && root.isIntegralNumber) { 27 | var timestamp = root.asLong() 28 | val length = timestamp.toString().length 29 | if (length != 10 && length != 13) { 30 | return root 31 | } 32 | if (length == 10) { 33 | timestamp *= 1000 34 | } 35 | val date = Date(timestamp) 36 | val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") 37 | val dateStr = sdf.format(date) 38 | return TextNode(dateStr) 39 | } 40 | if (root.isTextual) { 41 | val s = root.asText() 42 | // json 43 | if (s.startsWith('{') || s.startsWith("[")) { 44 | try { 45 | val subJson = mapper.readTree(s) 46 | val convert = convert(mapper, subJson) 47 | return convert 48 | } catch (ignored: Exception) { 49 | } 50 | } 51 | val s64 = PsiUnSaveUtils.LINE_END_PATTERN.matcher(s).replaceAll("") 52 | if (isBase64(s64)) { 53 | try { 54 | val decode = Base64.getDecoder().decode(s64) 55 | val string = String(decode, Charsets.UTF_8) 56 | return multiLineToObject(string) 57 | } catch (ignored: Exception) { 58 | } 59 | } 60 | if (s64.length != s.length) { 61 | return multiLineToObject(s) 62 | } 63 | } 64 | return root 65 | } 66 | 67 | /** 68 | * 0-F maybe 16 decimal number 69 | */ 70 | @JvmStatic 71 | private val A_PATTERN = Pattern.compile("[G-ZG-z]") 72 | 73 | @JvmStatic 74 | private val N_PATTERN = Pattern.compile("[0-9]") 75 | 76 | @JvmStatic 77 | private val NOT_BASE64_PATTERN = Pattern.compile("[^A-Za-z0-9+/]") 78 | 79 | @JvmStatic 80 | private fun isBase64(s: String): Boolean { 81 | if (s.length < 4) return false 82 | if (s.length % 4 != 0) return false 83 | val likeBase64 = s.endsWith('=') || A_PATTERN.matcher(s).find() && N_PATTERN.matcher(s).find() 84 | if (!likeBase64) return false 85 | val i = s.indexOf('=') 86 | val pre = if (i > 0) { 87 | if (i < s.length - 2) return false 88 | s.substring(0, i) 89 | } else { 90 | s 91 | } 92 | return !NOT_BASE64_PATTERN.matcher(pre).find() 93 | } 94 | 95 | @JvmStatic 96 | private fun multiLineToObject(s: String): JsonNode { 97 | val split = PsiUnSaveUtils.LINE_END_PATTERN.split(s) 98 | if (split.size == 1) { 99 | return TextNode(s) 100 | } 101 | val node = JsonNodeFactory.instance.objectNode() 102 | for ((index, str) in split.withIndex()) { 103 | node.put(index.toString(), str) 104 | } 105 | return node 106 | } 107 | } -------------------------------------------------------------------------------- /src/main/idea/com/github/linwancen/plugin/graph/parser/java/JavaParser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser.java 2 | 3 | import com.github.linwancen.plugin.graph.parser.Call 4 | import com.github.linwancen.plugin.graph.parser.ParserLang 5 | import com.github.linwancen.plugin.graph.parser.RelData 6 | import com.github.linwancen.plugin.graph.settings.DrawGraphAppState 7 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 8 | import com.intellij.lang.java.JavaLanguage 9 | import com.intellij.openapi.project.Project 10 | import com.intellij.psi.* 11 | import com.intellij.psi.search.GlobalSearchScope 12 | import com.intellij.psi.search.PsiShortNamesCache 13 | import com.intellij.psi.search.searches.OverridingMethodsSearch 14 | import com.intellij.psi.util.PsiTreeUtil 15 | 16 | open class JavaParser : ParserLang() { 17 | 18 | override fun id(): String { 19 | return JavaLanguage.INSTANCE.id 20 | } 21 | 22 | override fun nameToElementImpl(project: Project, name: String): PsiElement? { 23 | val scope = GlobalSearchScope.allScope(project) 24 | if (name.contains('.')) { 25 | return JavaPsiFacade.getInstance(project).findClass(name, scope) 26 | } 27 | val classes = PsiShortNamesCache.getInstance(project).getClassesByName(name, scope) 28 | if (classes.isNotEmpty()) { 29 | return classes[0] 30 | } 31 | return null 32 | } 33 | 34 | override fun funClass(): Class { 35 | return PsiMethod::class.java 36 | } 37 | 38 | override fun skipFun(state: DrawGraphProjectState, func: PsiMethod): Boolean { 39 | return JavaParserUtils.skipFun(state, func) 40 | } 41 | 42 | override fun toSign(func: PsiMethod): String? { 43 | return JavaParserUtils.sign(func) 44 | } 45 | 46 | override fun funMap(funMap: MutableMap, func: PsiMethod) { 47 | JavaParserUtils.funMap(func, funMap) 48 | } 49 | 50 | override fun classMap(func: PsiMethod, relData: RelData): MutableMap? { 51 | return JavaParserUtils.classMap(func) 52 | } 53 | 54 | override fun callList(func: PsiMethod, call: Boolean): List { 55 | val find = Call.find(func, call, PsiJavaCodeReferenceElement::class.java) 56 | val path = func.containingFile?.virtualFile?.path ?: return find 57 | if (path.contains('!')) { 58 | // not project fun 59 | return find 60 | } 61 | val scope = GlobalSearchScope.projectScope(func.project) 62 | val override = if (call) { 63 | OverridingMethodsSearch.search(func, scope, true).filterNotNull() 64 | } else { 65 | func.findSuperMethods().toList() 66 | } 67 | if (override.isEmpty()) { 68 | return find 69 | } 70 | val list = mutableListOf() 71 | list.addAll(find) 72 | list.addAll(override) 73 | return list 74 | } 75 | 76 | override fun fileCall( 77 | callSetMap: MutableMap>, 78 | usageSetMap: MutableMap>, 79 | psiFile: PsiFile, 80 | ) { 81 | if (!DrawGraphAppState.of().impl) { 82 | return 83 | } 84 | val psiClasses = PsiTreeUtil.findChildrenOfType(psiFile, PsiClass::class.java) 85 | for (psiClass in psiClasses) { 86 | val interfaces = psiClass.interfaces 87 | if (interfaces.isNotEmpty()) { 88 | val map = mutableMapOf() 89 | for (method in psiClass.methods) { 90 | map[method.name] = toSign(method) ?: continue 91 | } 92 | for (clazz in interfaces) { 93 | for (method in clazz.methods) { 94 | val implSign = map[method.name] 95 | if (implSign != null) { 96 | val sign = toSign(method) ?: continue 97 | val list = usageSetMap.computeIfAbsent(implSign) { mutableSetOf() } 98 | list.add(sign) 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/settings/AbstractDrawGraphState.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.settings 2 | 3 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils 4 | import java.util.* 5 | import java.util.regex.Pattern 6 | 7 | open class AbstractDrawGraphState { 8 | 9 | var skipGetSetIs = true 10 | var skipLib = true 11 | var lr = true 12 | var doc = "en" != Locale.getDefault().language 13 | var impl = true 14 | var mvc = true 15 | 16 | @Transient 17 | var includePattern = Pattern.compile("")!! 18 | private set 19 | 20 | @Transient 21 | var excludePattern = Pattern.compile("^(java)\\.")!! 22 | private set 23 | 24 | @Transient 25 | var otherIncludePattern = Pattern.compile("")!! 26 | private set 27 | 28 | @Transient 29 | var otherExcludePattern = Pattern.compile("")!! 30 | private set 31 | 32 | @Transient 33 | var effectIncludePattern = Pattern.compile("")!! 34 | private set 35 | 36 | @Transient 37 | var effectExcludePattern = Pattern.compile("Test")!! 38 | private set 39 | 40 | @Transient 41 | var annoDocArr = listOf( 42 | "io.swagger.annotations.Api#value", 43 | "io.swagger.annotations.Api#tags", 44 | "io.swagger.annotations.ApiOperation#value", 45 | "io.swagger.v3.oas.annotations.Operation#summary", 46 | "io.swagger.v3.oas.annotations.tags.Tag#name", 47 | "io.swagger.v3.oas.annotations.tags.Tag#description", 48 | Setting.message("anno_doc"), 49 | ) 50 | private set 51 | 52 | @Transient 53 | var effectAnnoArr = listOf( 54 | "org.springframework.web.bind.annotation.RequestMapping#value", 55 | "org.springframework.web.bind.annotation.GetMapping#value", 56 | "org.springframework.web.bind.annotation.PostMapping#value", 57 | "org.springframework.web.bind.annotation.PutMapping#value", 58 | "org.springframework.web.bind.annotation.DeleteMapping#value", 59 | "org.springframework.web.bind.annotation.PatchMapping#value", 60 | Setting.message("effect_anno"), 61 | ) 62 | private set 63 | 64 | fun getInclude(): String { 65 | return includePattern.pattern() 66 | } 67 | 68 | fun setInclude(s: String) { 69 | this.includePattern = Pattern.compile(s) 70 | } 71 | 72 | fun getExclude(): String { 73 | return excludePattern.pattern() 74 | } 75 | 76 | fun setExclude(s: String) { 77 | this.excludePattern = Pattern.compile(s) 78 | } 79 | 80 | fun getOtherInclude(): String { 81 | return otherIncludePattern.pattern() 82 | } 83 | 84 | fun setOtherInclude(s: String) { 85 | this.otherIncludePattern = Pattern.compile(s) 86 | } 87 | 88 | fun getOtherExclude(): String { 89 | return otherExcludePattern.pattern() 90 | } 91 | 92 | fun setOtherExclude(s: String) { 93 | this.otherExcludePattern = Pattern.compile(s) 94 | } 95 | 96 | 97 | fun getEffectInclude(): String { 98 | return effectIncludePattern.pattern() 99 | } 100 | 101 | fun setEffectInclude(s: String) { 102 | this.effectIncludePattern = Pattern.compile(s) 103 | } 104 | 105 | fun getEffectExclude(): String { 106 | return effectExcludePattern.pattern() 107 | } 108 | 109 | fun setEffectExclude(s: String) { 110 | this.effectExcludePattern = Pattern.compile(s) 111 | } 112 | 113 | fun getAnnoDoc(): String { 114 | return annoDocArr.joinToString("\n") 115 | } 116 | 117 | fun setAnnoDoc(s: String) { 118 | this.annoDocArr = s.split(PsiUnSaveUtils.LINE_END_PATTERN) 119 | } 120 | 121 | fun getEffectAnno(): String { 122 | return effectAnnoArr.joinToString("\n") 123 | } 124 | 125 | fun setEffectAnno(s: String) { 126 | this.effectAnnoArr = s.split(PsiUnSaveUtils.LINE_END_PATTERN) 127 | } 128 | 129 | fun resetAbstract(default: AbstractDrawGraphState) { 130 | setInclude(default.getInclude()) 131 | setExclude(default.getExclude()) 132 | setOtherInclude(default.getOtherInclude()) 133 | setOtherExclude(default.getOtherExclude()) 134 | setEffectInclude(default.getEffectInclude()) 135 | setEffectExclude(default.getEffectExclude()) 136 | setAnnoDoc(default.getAnnoDoc()) 137 | setEffectAnno(default.getEffectAnno()) 138 | } 139 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/printer/RelData2Effect.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.printer 2 | 3 | import com.github.linwancen.plugin.common.text.Skip 4 | import com.github.linwancen.plugin.common.text.TsvUtils 5 | import com.github.linwancen.plugin.graph.parser.RelData 6 | import com.github.linwancen.plugin.graph.settings.DrawGraphAppState 7 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 8 | import com.intellij.openapi.progress.ProgressIndicator 9 | import com.intellij.openapi.progress.Task 10 | import com.intellij.openapi.project.Project 11 | import org.slf4j.LoggerFactory 12 | import java.io.File 13 | import java.nio.charset.StandardCharsets 14 | import java.nio.file.Files 15 | import java.nio.file.Path 16 | import java.util.function.BiConsumer 17 | 18 | class RelData2Effect { 19 | private val log = LoggerFactory.getLogger(this::class.java) 20 | 21 | fun save(project: Project, relData: RelData, isCall: Boolean, func: BiConsumer, String>) { 22 | object : Task.Backgroundable(project, "draw effect") { 23 | override fun run(indicator: ProgressIndicator) { 24 | val path = DrawGraphAppState.of().tempPath 25 | val appState = DrawGraphAppState.of() 26 | val projectState = DrawGraphProjectState.of(project) 27 | try { 28 | File(path).mkdirs() 29 | val haveCall = relData.callSet.map { if (isCall) it.first else it.second }.toSet() 30 | val noCall = 31 | relData.callSet.map { if (isCall) it.second else it.first }.filter { it !in haveCall }.toSet() 32 | val notCallImplMap = mutableMapOf>() 33 | for (it in relData.callSet) { 34 | if (noCall.contains(if (isCall) it.second else it.first)) { 35 | notCallImplMap.computeIfAbsent(if (isCall) it.second else it.first) { mutableListOf() } 36 | .add(if (isCall) it.first else it.second) 37 | } 38 | } 39 | val sb = StringBuilder() 40 | val mapperTableMap = TsvUtils.load(project, ".mapperTable.tsv") 41 | val dtoTableMap = TsvUtils.load(project, ".dtoTable.tsv") 42 | noCall.forEach { 43 | if (Skip.skip(it, projectState.effectIncludePattern, projectState.effectExcludePattern)) { 44 | return@forEach 45 | } 46 | val map = relData.itemMap[it] 47 | sb.append(it).append('\t').append(map?.get("@1") ?: "") 48 | // effect anno value 49 | appendAnnoValue(map, appState, sb) 50 | // effect impl anno value 51 | notCallImplMap[it]?.forEach { 52 | val implMap = relData.itemMap[it] 53 | appendAnnoValue(implMap, appState, sb) 54 | } 55 | appendAnnoValue(map, appState, sb) 56 | // table and comment 57 | if (isCall) { 58 | val s = mapperTableMap[it.replaceFirst('#', '.')] 59 | if (s != null) { 60 | sb.append('\t').append(s) 61 | } 62 | val s2 = dtoTableMap[it.split('#').first()] 63 | if (s2 != null) { 64 | sb.append('\t').append(s2) 65 | } 66 | } 67 | sb.append('\n') 68 | } 69 | val s = sb.toString() 70 | func.accept(noCall, s) 71 | val dotPath = "$path/effect.txt" 72 | Files.write(Path.of(dotPath), s.toByteArray(StandardCharsets.UTF_8)) 73 | } catch (e: Throwable) { 74 | log.info("RelData2Effect fail", e) 75 | } 76 | } 77 | }.queue() 78 | } 79 | 80 | private fun appendAnnoValue( 81 | map: MutableMap?, 82 | appState: DrawGraphAppState, 83 | sb: StringBuilder, 84 | ) { 85 | if (map == null) return 86 | for (anno in appState.effectAnnoArr) { 87 | if (anno.isBlank()) { 88 | continue 89 | } 90 | val effectAnno = map[anno] 91 | if (effectAnno != null) { 92 | sb.append('\t').append(effectAnno) 93 | break 94 | } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.github.linwancen.drawgraph 4 | Draw Graph 5 | 林万程 6 | 7 | com.intellij.modules.platform 8 | com.intellij.modules.java 9 | org.jetbrains.kotlin 10 | org.intellij.groovy 11 | 12 | org.intellij.scala 13 | com.intellij.modules.xml 14 | JavaScript 15 | com.jetbrains.php 16 | com.intellij.modules.python 17 | org.jetbrains.plugins.go 18 | 19 | org.rust.lang 20 | 21 | com.jetbrains.rust 22 | com.intellij.modules.ruby 23 | 24 | com.intellij.modules.clion 25 | 26 | com.intellij.javafx 27 | 28 | org.jetbrains.plugins.javaFX 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 60 | 61 | 62 | 67 | 68 | 69 | 74 | 75 | 76 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/printer/RelData2SQLite.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.printer 2 | 3 | import com.github.linwancen.plugin.common.TaskTool 4 | import com.github.linwancen.plugin.graph.parser.RelData 5 | import com.github.linwancen.plugin.graph.settings.DrawGraphAppState 6 | import com.google.gson.Gson 7 | import com.intellij.openapi.progress.ProgressIndicator 8 | import com.intellij.openapi.progress.Task 9 | import com.intellij.openapi.project.Project 10 | import org.slf4j.LoggerFactory 11 | import java.io.File 12 | import java.sql.DriverManager 13 | 14 | class RelData2SQLite { 15 | private val log = LoggerFactory.getLogger(this::class.java) 16 | 17 | fun save(project: Project, relData: RelData) { 18 | object : Task.Backgroundable(project, "draw save sqlite") { 19 | override fun run(indicator: ProgressIndicator) { 20 | val path = DrawGraphAppState.of().tempPath 21 | try { 22 | Class.forName("org.sqlite.JDBC") 23 | } catch (e: Throwable) { 24 | log.warn("org.sqlite.JDBC not found") 25 | return 26 | } 27 | try { 28 | File(path).mkdirs() 29 | DriverManager.getConnection("jdbc:sqlite:$path/draw_graph.sqlite").use { connection -> 30 | connection.createStatement().use { s -> 31 | s.execute("PRAGMA synchronous=OFF") 32 | s.executeUpdate("drop table if exists itemMap") 33 | s.executeUpdate("create table itemMap(sign TEXT, info TEXT)") 34 | s.executeUpdate("drop table if exists parentChildMap") 35 | s.executeUpdate("create table parentChildMap(parentSign TEXT, childSign TEXT)") 36 | s.executeUpdate("drop table if exists callSet") 37 | s.executeUpdate("create table callSet(usageSign TEXT, callSign TEXT)") 38 | } 39 | val parentSize = relData.parentChildMap.values.sumBy { it.size } 40 | val taskTool = TaskTool(indicator, relData.itemMap.size + parentSize + relData.callSet.size) 41 | var i = 0 42 | val gson = Gson() 43 | connection.autoCommit = false 44 | connection.prepareStatement("insert into itemMap values(?, ?)").use { p -> 45 | relData.itemMap.forEach { 46 | p.setString(1, it.key) 47 | p.setString(2, gson.toJson(it.value)) 48 | p.addBatch() 49 | if (i++ % 1000 == 0) { 50 | taskTool.beforeNext(i, "1. insert into itemMap") 51 | p.executeBatch() 52 | connection.commit() 53 | } 54 | } 55 | p.executeBatch() 56 | connection.commit() 57 | } 58 | connection.prepareStatement("insert into parentChildMap values(?, ?)").use { p -> 59 | relData.parentChildMap.forEach { 60 | p.setString(1, it.key) 61 | it.value.forEach { child -> 62 | p.setString(2, child) 63 | p.addBatch() 64 | if (i++ % 1000 == 0) { 65 | taskTool.beforeNext(i, "2. insert into parentChildMap)") 66 | p.executeBatch() 67 | connection.commit() 68 | } 69 | } 70 | } 71 | p.executeBatch() 72 | connection.commit() 73 | } 74 | connection.prepareStatement("insert into callSet values(?, ?)").use { p -> 75 | relData.callSet.forEach { 76 | p.setString(1, it.first) 77 | p.setString(2, it.second) 78 | p.addBatch() 79 | if (i++ % 1000 == 0) { 80 | taskTool.beforeNext(i, "3. insert into callSet)") 81 | p.executeBatch() 82 | connection.commit() 83 | } 84 | } 85 | p.executeBatch() 86 | connection.commit() 87 | } 88 | } 89 | } catch (e: Throwable) { 90 | log.info("RelData2SQLite fail", e) 91 | } 92 | } 93 | }.queue() 94 | } 95 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/Parser.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser 2 | 3 | import com.intellij.lang.Language 4 | import com.intellij.openapi.extensions.ExtensionPointName 5 | import com.intellij.openapi.progress.ProgressIndicator 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.vfs.VirtualFile 8 | import com.intellij.psi.PsiElement 9 | import com.intellij.psi.PsiManager 10 | import org.slf4j.LoggerFactory 11 | import kotlin.reflect.full.createInstance 12 | 13 | abstract class Parser { 14 | 15 | protected abstract fun id(): String 16 | 17 | protected abstract fun srcImpl( 18 | project: Project, 19 | relData: RelData, 20 | files: List, 21 | indicator: ProgressIndicator? 22 | ) 23 | 24 | open fun callImpl( 25 | project: Project, 26 | relData: RelData, 27 | psiElement: PsiElement, 28 | isCall: Boolean, 29 | indicator: ProgressIndicator? 30 | ) {} 31 | 32 | open fun nameToElementImpl(project: Project, name: String): PsiElement? { 33 | return null 34 | } 35 | 36 | companion object { 37 | private val log = LoggerFactory.getLogger(this::class.java) 38 | 39 | fun parserMap(): MutableMap { 40 | val epn: ExtensionPointName = ExtensionPointName.create("com.github.linwancen.drawgraph.parser") 41 | val parserList = epn.extensionList 42 | log.info("load graph parser epn {}", parserList) 43 | val parserMap = mutableMapOf() 44 | parserList.forEach { 45 | log.info("load graph parser {} -> {}", it.id(), it) 46 | parserMap[it.id()] = it 47 | } 48 | // for 2024.2 49 | if (parserMap["kotlin"] == null) { 50 | try { 51 | val kotlin = Class.forName("com.github.linwancen.plugin.graph.parser.kotlin.KotlinParser").kotlin 52 | val parser = kotlin.createInstance() as Parser 53 | parserMap["kotlin"] = parser 54 | log.info("add load graph parser kotlin -> {}", parser) 55 | } catch (_: Exception) { 56 | } 57 | } 58 | return parserMap 59 | } 60 | 61 | @JvmStatic 62 | fun findParser(element: PsiElement, map: Map): Parser? { 63 | var language: Language = element.language 64 | while (true) { 65 | val impl: Parser? = map[language.id] 66 | if (impl != null) { 67 | return impl 68 | } 69 | language = language.baseLanguage ?: return null 70 | } 71 | } 72 | 73 | /** 74 | * need DumbService.getInstance(project).runReadActionInSmartMo 75 | */ 76 | @JvmStatic 77 | fun src(project: Project, relData: RelData, files: List, indicator: ProgressIndicator?) { 78 | val parserMap = parserMap() 79 | for (file in files) { 80 | val psiFile = PsiManager.getInstance(project).findFile(file) ?: continue 81 | val usageService = findParser(psiFile, parserMap) ?: continue 82 | // not one by one because pom.xml find all files 83 | usageService.srcImpl(project, relData, files, indicator) 84 | // only one lang srcImpl all and return 85 | return 86 | } 87 | } 88 | 89 | /** 90 | * need DumbService.getInstance(project).runReadActionInSmartMo 91 | */ 92 | @JvmStatic 93 | fun call( 94 | project: Project, 95 | relData: RelData, 96 | psiElement: PsiElement, 97 | call: Boolean, 98 | indicator: ProgressIndicator 99 | ) { 100 | val parserMap = parserMap() 101 | var language = psiElement.language 102 | language.baseLanguage?.let { language = it } 103 | val usageService = parserMap[language.id] ?: return 104 | usageService.callImpl(project, relData, psiElement, call, indicator) 105 | } 106 | 107 | /** 108 | * need DumbService.getInstance(project).runReadActionInSmartMo 109 | */ 110 | @JvmStatic 111 | fun nameToElement(project: Project, name: String): PsiElement? { 112 | val epn: ExtensionPointName = ExtensionPointName.create("com.github.linwancen.drawgraph.parser") 113 | val parserList = epn.extensionList 114 | for (it in parserList) { 115 | val element = it.nameToElementImpl(project, name) 116 | if (element != null) { 117 | return element 118 | } 119 | } 120 | return null 121 | } 122 | } 123 | 124 | fun regCall( 125 | callSetMap: Map>, 126 | relData: RelData, 127 | all: Boolean = false, 128 | ) { 129 | for ((usage, callList) in callSetMap) { 130 | for (call in callList) { 131 | if (all || callSetMap.containsKey(call)) { 132 | relData.regCall(usage, call) 133 | } 134 | } 135 | } 136 | } 137 | 138 | fun regUsage( 139 | callSetMap: Map>, 140 | usageSetMap: Map>, 141 | relData: RelData, 142 | all: Boolean = false, 143 | ) { 144 | for ((call, usageList) in usageSetMap) { 145 | for (usage in usageList) { 146 | if (all || callSetMap.containsKey(usage)) { 147 | relData.regCall(usage, call) 148 | } 149 | } 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/printer/PrinterMermaid.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.printer 2 | 3 | import com.github.linwancen.plugin.graph.parser.RelData 4 | import com.github.linwancen.plugin.graph.settings.DrawGraphAppState 5 | import com.github.linwancen.plugin.graph.ui.DrawGraphBundle 6 | import com.intellij.openapi.progress.ProgressIndicator 7 | import com.intellij.openapi.progress.Task 8 | import com.intellij.util.ExceptionUtil 9 | import org.apache.commons.lang3.StringUtils 10 | import java.io.File 11 | import java.nio.charset.StandardCharsets 12 | import java.nio.file.Files 13 | import java.nio.file.Path 14 | import java.util.function.Consumer 15 | 16 | class PrinterMermaid : Printer() { 17 | 18 | val sb = StringBuilder("graph ${if (DrawGraphAppState.of().lr) "LR" else "TB"}\n\n") 19 | 20 | override fun beforeGroup(groupMap: MutableMap) { 21 | sb.append("\nsubgraph ") 22 | label(groupMap, false) 23 | sb.append(" direction ${if (DrawGraphAppState.of().lr) "LR" else "TB"}\n") 24 | } 25 | 26 | override fun afterGroup(groupMap: MutableMap) { 27 | sb.append("end\n") 28 | } 29 | 30 | override fun item(itemMap: MutableMap) { 31 | label(itemMap, true) 32 | } 33 | 34 | private fun label(itemMap: MutableMap, isItem: Boolean) { 35 | sb.append(" '${sign(itemMap["sign"] ?: return)}'") 36 | if (itemMap["name"] != null) { 37 | sb.append(if (isItem) "(" else "[") 38 | sb.append("\"") 39 | if (!isItem) { 40 | sb.append("${itemMap["name"]}\n") 41 | } 42 | addLine(itemMap["@1"], sb) 43 | addLine(itemMap["@2"], sb) 44 | addLine(itemMap["@3"], sb) 45 | if (isItem) { 46 | sb.append("${itemMap["name"]}") 47 | } 48 | sb.append("\"") 49 | sb.append(if (isItem) ")" else "]") 50 | } 51 | sb.append("\n") 52 | val link = itemMap["link"]?.replace(")", "") ?: "" 53 | sb.append(" click '${sign(itemMap["sign"] ?: return)}' call navigate(\"$link\")") 54 | sb.append("\n\n") 55 | } 56 | 57 | override fun call(usageSign: String, callSign: String) { 58 | sb.append("\n'${sign(usageSign)}' --> '${sign(callSign)}'") 59 | } 60 | 61 | override fun sign(input: String): String { 62 | val deleteKeyword = keyword.replace(input, "$1_") 63 | return canNotUseSymbol.replace(deleteKeyword, "_") 64 | } 65 | 66 | override fun toSrc(relData: RelData): Pair { 67 | printerData(relData) 68 | return Pair(sb.toString(), "") 69 | } 70 | 71 | companion object { 72 | /** 73 | * [parse error with word graph #4079](https://github.com/mermaid-js/mermaid/issues/4079) 74 | */ 75 | @JvmStatic 76 | val keyword = Regex("\\b(graph|end|parent)\\b") 77 | 78 | @JvmStatic 79 | fun build(data: PrinterData, func: Consumer?) { 80 | object : Task.Backgroundable(data.project, "draw Mermaid") { 81 | override fun run(indicator: ProgressIndicator) { 82 | val src = data.src ?: return 83 | if (StringUtils.isBlank(src)) { 84 | return 85 | } 86 | // language="html" 87 | val appState = DrawGraphAppState.of() 88 | val offline = temp( 89 | src, """ 90 | 91 | 92 | 93 | """ 94 | ) 95 | // language="html" 96 | val online = temp( 97 | src, """ 98 | 99 | """ 100 | ) 101 | val path = appState.tempPath 102 | try { 103 | if (appState.online) { 104 | func?.accept(online) 105 | } else { 106 | func?.accept(offline) 107 | } 108 | 109 | File(path).mkdirs() 110 | val offlinePath = "$path/mermaid-offline.html" 111 | val onlinePath = "$path/mermaid-online.html" 112 | Files.write(Path.of(offlinePath), offline.toByteArray(StandardCharsets.UTF_8)) 113 | Files.write(Path.of(onlinePath), online.toByteArray(StandardCharsets.UTF_8)) 114 | } catch (e: Exception) { 115 | func?.accept("${offline}\n${ExceptionUtil.getThrowableText(e)}") 116 | } 117 | } 118 | }.queue() 119 | } 120 | 121 | private fun temp(src: String?, mermaidLink: String?): String { 122 | // language="html" 123 | val online = """
124 | 
125 | $src
126 | 
127 | $mermaidLink 128 | 154 | 155 |
156 | ${DrawGraphBundle.message("mermaid.msg")} 157 | """ 158 | return online 159 | } 160 | } 161 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/printer/PrinterPlantuml.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.printer 2 | 3 | import com.github.linwancen.plugin.graph.parser.RelData 4 | import com.github.linwancen.plugin.graph.settings.DrawGraphAppState 5 | import com.intellij.openapi.progress.ProgressIndicator 6 | import com.intellij.openapi.progress.Task 7 | import com.intellij.util.ExceptionUtil 8 | import net.sourceforge.plantuml.FileFormat 9 | import net.sourceforge.plantuml.FileFormatOption 10 | import net.sourceforge.plantuml.SourceStringReader 11 | import org.apache.commons.lang3.StringUtils 12 | import java.io.File 13 | import java.io.FileOutputStream 14 | import java.nio.charset.StandardCharsets 15 | import java.nio.file.Files 16 | import java.nio.file.Path 17 | import java.util.function.Consumer 18 | 19 | 20 | class PrinterPlantuml : Printer() { 21 | 22 | val sb = StringBuilder( 23 | """@startuml 24 | hide empty circle 25 | hide empty members 26 | ${if (DrawGraphAppState.of().lr) "left to right direction" else ""} 27 | skinparam shadowing false 28 | skinparam componentStyle rectangle 29 | skinparam defaultTextAlignment center 30 | 31 | """ 32 | ) 33 | val js = StringBuilder() 34 | 35 | override fun beforeGroup(groupMap: MutableMap) { 36 | label(groupMap, false) 37 | sb.append(" {\n") 38 | } 39 | 40 | override fun afterGroup(groupMap: MutableMap) { 41 | sb.append("}\n\n") 42 | } 43 | 44 | override fun item(itemMap: MutableMap) { 45 | label(itemMap, true) 46 | sb.append("\n") 47 | } 48 | 49 | private fun label(map: MutableMap, isItem: Boolean) { 50 | val sign = sign(map["sign"] ?: return) 51 | if (isItem) sb.append(" ") 52 | sb.append("component $sign") 53 | if (map["name"] != null) { 54 | sb.append(" as \" ") 55 | addLine(map["@1"], sb, true) 56 | addLine(map["@2"], sb, true) 57 | addLine(map["@3"], sb, true) 58 | sb.append("${map["name"]}\"") 59 | } 60 | val id = "${if (isItem) "elem" else "cluster"}_$sign" 61 | js.append(" document.getElementById(\"$id\").onclick = function(){ navigate(\"${map["link"]}\") }\n") 62 | } 63 | 64 | override fun call(usageSign: String, callSign: String) { 65 | sb.append("\n${sign(usageSign)} --> ${sign(callSign)}") 66 | } 67 | 68 | override fun toSrc(relData: RelData): Pair { 69 | printerData(relData) 70 | sb.append("\n@enduml") 71 | return Pair(sb.toString(), js.toString()) 72 | } 73 | 74 | companion object { 75 | @JvmStatic 76 | fun build(data: PrinterData, func: Consumer?) { 77 | object : Task.Backgroundable(data.project, "draw PlantUML") { 78 | override fun run(indicator: ProgressIndicator) { 79 | val src = data.src ?: return 80 | if (StringUtils.isBlank(src)) { 81 | return 82 | } 83 | val path = DrawGraphAppState.of().tempPath 84 | try { 85 | File(path).mkdirs() 86 | 87 | val plantumlPath = "$path/plantuml.puml" 88 | Files.write(Path.of(plantumlPath), src.toByteArray(StandardCharsets.UTF_8)) 89 | if (func == null) { 90 | return 91 | } 92 | val svgFile = "$path/plantuml.svg" 93 | val svgOut = FileOutputStream(svgFile) 94 | val reader = SourceStringReader(src) 95 | val svgDesc = reader.outputImage(svgOut, FileFormatOption(FileFormat.SVG)) 96 | val svg = Files.readString(Path.of(svgFile), StandardCharsets.UTF_8) 97 | func.accept( 98 | // language="html" 99 | """ 100 | 101 | $svg 102 |
103 | $svgDesc 104 |
105 | 145 | 146 |
147 | """ 148 | ) 149 | val pngFile = "$path/plantuml.png" 150 | val pngOut = FileOutputStream(pngFile) 151 | reader.outputImage(pngOut, FileFormatOption(FileFormat.PNG)) 152 | } catch (e: Exception) { 153 | func?.accept( 154 | // language="html" 155 | """ 156 | 157 |
158 | ${ExceptionUtil.getThrowableText(e)} 159 | """ 160 | ) 161 | } 162 | } 163 | }.queue() 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/printer/PrinterGraphviz.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.printer 2 | 3 | import com.github.linwancen.plugin.graph.parser.RelData 4 | import com.github.linwancen.plugin.graph.settings.DrawGraphAppState 5 | import com.github.linwancen.plugin.graph.ui.DrawGraphBundle 6 | import com.intellij.execution.CommandLineUtil 7 | import com.intellij.execution.configurations.GeneralCommandLine 8 | import com.intellij.execution.process.ScriptRunnerUtil 9 | import com.intellij.openapi.progress.ProgressIndicator 10 | import com.intellij.openapi.progress.Task 11 | import com.intellij.util.ExceptionUtil 12 | import org.apache.commons.lang3.StringUtils 13 | import java.io.File 14 | import java.nio.charset.StandardCharsets 15 | import java.nio.file.Files 16 | import java.nio.file.Path 17 | import java.util.function.Consumer 18 | 19 | 20 | class PrinterGraphviz : Printer() { 21 | 22 | val sb = StringBuilder( 23 | """digraph{ 24 | ${if (DrawGraphAppState.of().lr) "rankdir=LR" else ""} 25 | fontname = "Microsoft YaHei,Consolas" 26 | node [shape = "record", style="rounded,filled", fillcolor = "#F1F1F1", fontname = "Microsoft YaHei,Consolas"] 27 | edge [arrowhead = "empty", fontname = "Microsoft YaHei,Consolas"] 28 | graph [compound=true] 29 | 30 | """ 31 | ) 32 | val js = StringBuilder() 33 | var nodeId = 1 34 | var clusterId = 1 35 | 36 | override fun beforeGroup(groupMap: MutableMap) { 37 | sb.append( 38 | "subgraph \"cluster_${sign(groupMap["sign"] ?: return)}\" {\n" + 39 | " edge [\"dir\"=\"none\"]\n" 40 | + " graph [style=\"rounded\"]\n" 41 | ) 42 | label(groupMap, false) 43 | sb.append("\n") 44 | } 45 | 46 | override fun afterGroup(groupMap: MutableMap) { 47 | sb.append("}\n\n") 48 | } 49 | 50 | override fun item(itemMap: MutableMap) { 51 | sb.append(" \"cluster_${sign(itemMap["sign"] ?: return)}\"") 52 | label(itemMap, true) 53 | sb.append("\n") 54 | } 55 | 56 | private fun label(map: Map, isItem: Boolean) { 57 | map["name"] ?: return 58 | if (isItem) { 59 | sb.append('[') 60 | } else { 61 | sb.append(" ") 62 | } 63 | sb.append("label =\" ") 64 | addLine(map["@1"], sb, true) 65 | addLine(map["@2"], sb, true) 66 | addLine(map["@3"], sb, true) 67 | sb.append("${map["name"]}\"") 68 | if (isItem) { 69 | sb.append(']') 70 | } 71 | val id = if (isItem) "node${nodeId++}" else "clust${clusterId++}" 72 | js.append(" document.getElementById(\"$id\").onclick = function(){ navigate(\"${map["link"]}\") }\n") 73 | } 74 | 75 | override fun call(usageSign: String, callSign: String) { 76 | sb.append("\n\"cluster_${sign(usageSign)}\" -> \"cluster_${sign(callSign)}\"") 77 | } 78 | 79 | override fun toSrc(relData: RelData): Pair { 80 | printerData(relData) 81 | sb.append("\n}") 82 | return Pair(sb.toString(), js.toString()) 83 | } 84 | 85 | companion object { 86 | 87 | @JvmStatic 88 | fun build(data: PrinterData, func: Consumer?) { 89 | object : Task.Backgroundable(data.project, "draw Graphviz") { 90 | override fun run(indicator: ProgressIndicator) { 91 | val src = data.src ?: return 92 | if (StringUtils.isBlank(src)) { 93 | return 94 | } 95 | val path = DrawGraphAppState.of().tempPath 96 | try { 97 | File(path).mkdirs() 98 | val dotPath = "$path/graphviz.dot" 99 | Files.write(Path.of(dotPath), src.toByteArray(StandardCharsets.UTF_8)) 100 | if (func == null) { 101 | return 102 | } 103 | val commandLine = 104 | CommandLineUtil.toCommandLine("dot", arrayListOf("-Tsvg", "-Tpng", "-O", dotPath)) 105 | val generalCommandLine = GeneralCommandLine(commandLine) 106 | generalCommandLine.charset = StandardCharsets.UTF_8 107 | generalCommandLine.setWorkDirectory(path) 108 | val commandLineOutputStr = ScriptRunnerUtil.getProcessOutput(generalCommandLine) 109 | val svgFile = "$path/graphviz.dot.svg" 110 | val svg = Files.readString(Path.of(svgFile), StandardCharsets.UTF_8) 111 | func.accept( 112 | // language="html" 113 | """ 114 | 115 | $svg 116 |
117 | 140 | 141 |
142 | ${DrawGraphBundle.message("graphviz.msg")} 143 |
144 | $commandLineOutputStr 145 | """ 146 | ) 147 | return 148 | } catch (e: Exception) { 149 | func?.accept( 150 | // language="html" 151 | """ 152 | 153 |
154 | ${DrawGraphBundle.message("graphviz.msg")} 155 |
156 | ${ExceptionUtil.getThrowableText(e)} 157 | """ 158 | ) 159 | } 160 | } 161 | }.queue() 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions Workflow is created for testing and preparing the plugin release in the following steps: 2 | # - validate Gradle Wrapper, 3 | # - run 'test' and 'verifyPlugin' tasks, 4 | # - run Qodana inspections, 5 | # - run 'buildPlugin' task and prepare artifact for the further tests, 6 | # - run 'runPluginVerifier' task, 7 | # - create a draft release. 8 | # 9 | # Workflow is triggered on push and pull_request events. 10 | # 11 | # GitHub Actions reference: https://help.github.com/en/actions 12 | # 13 | ## JBIJPPTPL 14 | 15 | name: Build 16 | on: 17 | # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g. for dependabot pull requests) 18 | push: 19 | branches: [main] 20 | # Trigger the workflow on any pull request 21 | pull_request: 22 | 23 | jobs: 24 | 25 | # Run Gradle Wrapper Validation Action to verify the wrapper's checksum 26 | # Run verifyPlugin, IntelliJ Plugin Verifier, and test Gradle tasks 27 | # Build plugin and provide the artifact for the next workflow jobs 28 | build: 29 | name: Build 30 | runs-on: ubuntu-latest 31 | outputs: 32 | version: ${{ steps.properties.outputs.version }} 33 | changelog: ${{ steps.properties.outputs.changelog }} 34 | steps: 35 | 36 | # Free GitHub Actions Environment Disk Space 37 | - name: Maximize Build Space 38 | run: | 39 | sudo rm -rf /usr/share/dotnet 40 | sudo rm -rf /usr/local/lib/android 41 | sudo rm -rf /opt/ghc 42 | 43 | # Check out current repository 44 | - name: Fetch Sources 45 | uses: actions/checkout@v3 46 | 47 | # Validate wrapper 48 | - name: Gradle Wrapper Validation 49 | uses: gradle/wrapper-validation-action@v1.0.5 50 | 51 | # Setup Java 11 environment for the next steps 52 | - name: Setup Java 53 | uses: actions/setup-java@v3 54 | with: 55 | distribution: zulu 56 | java-version: 11 57 | 58 | # Set environment variables 59 | - name: Export Properties 60 | id: properties 61 | shell: bash 62 | run: | 63 | PROPERTIES="$(./gradlew properties --console=plain -q)" 64 | VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')" 65 | NAME="$(echo "$PROPERTIES" | grep "^pluginName:" | cut -f2- -d ' ')" 66 | CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)" 67 | 68 | echo "version=$VERSION" >> $GITHUB_OUTPUT 69 | echo "name=$NAME" >> $GITHUB_OUTPUT 70 | echo "pluginVerifierHomeDir=~/.pluginVerifier" >> $GITHUB_OUTPUT 71 | 72 | echo "changelog<> $GITHUB_OUTPUT 73 | echo "$CHANGELOG" >> $GITHUB_OUTPUT 74 | echo "EOF" >> $GITHUB_OUTPUT 75 | 76 | ./gradlew listProductsReleases # prepare list of IDEs for Plugin Verifier 77 | 78 | # Run tests 79 | - name: Run Tests 80 | run: ./gradlew check 81 | 82 | # Collect Tests Result of failed tests 83 | - name: Collect Tests Result 84 | if: ${{ failure() }} 85 | uses: actions/upload-artifact@v3 86 | with: 87 | name: tests-result 88 | path: ${{ github.workspace }}/build/reports/tests 89 | 90 | # Upload Kover report to CodeCov 91 | - name: Upload Code Coverage Report 92 | uses: codecov/codecov-action@v3 93 | with: 94 | files: ${{ github.workspace }}/build/reports/kover/xml/report.xml 95 | 96 | # Cache Plugin Verifier IDEs 97 | - name: Setup Plugin Verifier IDEs Cache 98 | uses: actions/cache@v3 99 | with: 100 | path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides 101 | key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }} 102 | 103 | # Run Verify Plugin task and IntelliJ Plugin Verifier tool 104 | - name: Run Plugin Verification tasks 105 | run: ./gradlew runPluginVerifier -Pplugin.verifier.home.dir=${{ steps.properties.outputs.pluginVerifierHomeDir }} 106 | 107 | # Collect Plugin Verifier Result 108 | - name: Collect Plugin Verifier Result 109 | if: ${{ always() }} 110 | uses: actions/upload-artifact@v3 111 | with: 112 | name: pluginVerifier-result 113 | path: ${{ github.workspace }}/build/reports/pluginVerifier 114 | 115 | # Run Qodana inspections 116 | - name: Qodana - Code Inspection 117 | uses: JetBrains/qodana-action@v2022.3.0 118 | 119 | # Prepare plugin archive content for creating artifact 120 | - name: Prepare Plugin Artifact 121 | id: artifact 122 | shell: bash 123 | run: | 124 | cd ${{ github.workspace }}/build/distributions 125 | FILENAME=`ls *.zip` 126 | unzip "$FILENAME" -d content 127 | 128 | echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT 129 | 130 | # Store already-built plugin as an artifact for downloading 131 | - name: Upload artifact 132 | uses: actions/upload-artifact@v3 133 | with: 134 | name: ${{ steps.artifact.outputs.filename }} 135 | path: ./build/distributions/content/*/* 136 | 137 | # Prepare a draft release for GitHub Releases page for the manual verification 138 | # If accepted and published, release workflow would be triggered 139 | releaseDraft: 140 | name: Release Draft 141 | if: github.event_name != 'pull_request' 142 | needs: build 143 | runs-on: ubuntu-latest 144 | permissions: 145 | contents: write 146 | steps: 147 | 148 | # Check out current repository 149 | - name: Fetch Sources 150 | uses: actions/checkout@v3 151 | 152 | # Remove old release drafts by using the curl request for the available releases with a draft flag 153 | - name: Remove Old Release Drafts 154 | env: 155 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 156 | run: | 157 | gh api repos/{owner}/{repo}/releases \ 158 | --jq '.[] | select(.draft == true) | .id' \ 159 | | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{} 160 | 161 | # Create a new release draft which is not publicly visible and requires manual acceptance 162 | - name: Create Release Draft 163 | env: 164 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 165 | run: | 166 | gh release create v${{ needs.build.outputs.version }} \ 167 | --draft \ 168 | --title "v${{ needs.build.outputs.version }}" \ 169 | --notes "$(cat << 'EOM' 170 | ${{ needs.build.outputs.changelog }} 171 | EOM 172 | )" 173 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/linwancen/plugin/graph/parser/ParserLang.kt: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.parser 2 | 3 | import com.github.linwancen.plugin.common.TaskTool 4 | import com.github.linwancen.plugin.common.text.Skip 5 | import com.github.linwancen.plugin.graph.parser.relfile.RelFile 6 | import com.github.linwancen.plugin.graph.settings.DrawGraphProjectState 7 | import com.intellij.openapi.progress.ProgressIndicator 8 | import com.intellij.openapi.project.Project 9 | import com.intellij.openapi.vfs.VirtualFile 10 | import com.intellij.psi.PsiElement 11 | import com.intellij.psi.PsiFile 12 | import com.intellij.psi.PsiManager 13 | import com.intellij.psi.util.PsiTreeUtil 14 | 15 | abstract class ParserLang : Parser() { 16 | 17 | protected abstract fun funClass(): Class 18 | 19 | /** 20 | * Constructor or get/set 21 | */ 22 | protected abstract fun skipFun(state: DrawGraphProjectState, func: F): Boolean 23 | 24 | protected abstract fun toSign(func: F): String? 25 | 26 | protected abstract fun funMap(funMap: MutableMap, func: F) 27 | 28 | protected abstract fun classMap(func: F, relData: RelData): MutableMap? 29 | 30 | protected abstract fun callList(func: F, call: Boolean): List 31 | 32 | /** interface impl */ 33 | protected open fun fileCall( 34 | callSetMap: MutableMap>, 35 | usageSetMap: MutableMap>, 36 | psiFile: PsiFile, 37 | ) { 38 | } 39 | 40 | override fun srcImpl(project: Project, relData: RelData, files: List, indicator: ProgressIndicator?) { 41 | val state = DrawGraphProjectState.of(project) 42 | val callSetMap = mutableMapOf>() 43 | val psiManager = PsiManager.getInstance(project) 44 | val relFiles = if (files.size != 1) files else RelFile.relFileOf(project, files) 45 | val parserMap = parserMap() 46 | val taskTool = if (indicator == null) null else TaskTool(indicator, relFiles.size) 47 | for ((index, file) in relFiles.withIndex()) { 48 | if (taskTool != null) { 49 | taskTool.beforeNext(index, file.path) ?: return 50 | } 51 | val psiFile = psiManager.findFile(file) ?: continue 52 | val usageService = findParser(psiFile, parserMap) ?: continue 53 | if (usageService is ParserLang<*>) { 54 | usageService.fileImpl(psiFile, state, file, relData, callSetMap) 55 | } 56 | } 57 | regCall(callSetMap, relData) 58 | } 59 | 60 | fun fileImpl( 61 | psiFile: PsiFile, state: DrawGraphProjectState, file: VirtualFile, 62 | relData: RelData, callSetMap: MutableMap> 63 | ) { 64 | val funcs = PsiTreeUtil.findChildrenOfType(psiFile, funClass()) 65 | for (func in funcs) { 66 | val sign = funcSign(state, func, file, relData) ?: continue 67 | // override fun 68 | val callSet = callSetMap.computeIfAbsent(sign) { mutableSetOf() } 69 | callSet.addAll( 70 | callList(func, true) 71 | .filter { !skipFun(state, it) } 72 | .mapNotNull { toSign(it) } 73 | .filter { !Skip.skip(it, state.includePattern, state.excludePattern) }) 74 | } 75 | } 76 | 77 | private fun funcSign(state: DrawGraphProjectState, func: F, file: VirtualFile?, relData: RelData): String? { 78 | if (skipFun(state, func)) { 79 | return null 80 | } 81 | var path = file?.path 82 | val inJar = path == null || path.contains('!') 83 | if (inJar && state.skipLib && !state.autoLoad) { 84 | return null 85 | } 86 | val sign = toSign(func) ?: return null 87 | if (Skip.skip(sign, state.includePattern, state.excludePattern)) { 88 | return null 89 | } 90 | val funMap = mutableMapOf() 91 | funMap["sign"] = sign 92 | val srcFun = CommentUtils.byteToSrc(func) 93 | funMap(funMap, srcFun) 94 | val name = funMap["name"] 95 | val classMap = classMap(srcFun, relData) 96 | if (inJar) { 97 | classMap?.let { path = it["sign"] } 98 | } 99 | path?.let { funMap["link"] = "$path#${name?.substring(name.lastIndexOf(' ') + 1) ?: ""}" } 100 | if (classMap == null) { 101 | relData.regParentChild(funMap) 102 | } else { 103 | path?.let { classMap["link"] = "$path#" } 104 | relData.regParentChild(classMap, funMap) 105 | } 106 | return sign 107 | } 108 | 109 | override fun callImpl( 110 | project: Project, 111 | relData: RelData, 112 | psiElement: PsiElement, 113 | isCall: Boolean, 114 | indicator: ProgressIndicator? 115 | ) { 116 | val state = DrawGraphProjectState.of(project) 117 | val basePath = project.basePath 118 | val callSetMap = mutableMapOf>() 119 | val func = PsiTreeUtil.getParentOfType(psiElement, funClass(), false) ?: return 120 | funcSign(state, func, func.containingFile?.virtualFile, relData) ?: return 121 | recursiveCall(1, func, isCall, mutableSetOf(), indicator) { _, usage, call -> 122 | val callFile = call.containingFile?.virtualFile 123 | if (state.skipLib && callFile?.path?.startsWith(basePath ?: "") == false) { 124 | return@recursiveCall 125 | } 126 | val usageSign = toSign(usage) ?: return@recursiveCall 127 | val callSign = funcSign(state, call, callFile, relData) ?: return@recursiveCall 128 | val callSet = callSetMap.computeIfAbsent(if (isCall) usageSign else callSign) { mutableSetOf() } 129 | callSet.add(if (isCall) callSign else usageSign) 130 | } 131 | regCall(callSetMap, relData, true) 132 | } 133 | 134 | open fun recursiveCall( 135 | level: Int, 136 | usage: F, 137 | isCall: Boolean, 138 | set: MutableSet, 139 | indicator: ProgressIndicator?, 140 | callBack: (level: Int, usage: F, call: F) -> Unit, 141 | ) { 142 | if (!set.add(usage)) { 143 | return 144 | } 145 | val callList = callList(usage, isCall) 146 | val taskTool = if (indicator == null) null else TaskTool(indicator, callList.size) 147 | for ((index, call) in callList.withIndex()) { 148 | if (taskTool != null) { 149 | taskTool.beforeNext(index, "$level $call") ?: return 150 | } 151 | callBack.invoke(level, usage, call) 152 | recursiveCall(level + 1, call, isCall, set, indicator, callBack) 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /src/main/java/com/github/linwancen/plugin/graph/ui/webview/JcefNavigateHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.linwancen.plugin.graph.ui.webview; 2 | 3 | import com.github.linwancen.plugin.common.psi.PsiUnSaveUtils; 4 | import com.github.linwancen.plugin.graph.parser.Parser; 5 | import com.intellij.openapi.application.ApplicationManager; 6 | import com.intellij.openapi.project.DumbService; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import com.intellij.openapi.vfs.VirtualFileManager; 10 | import com.intellij.psi.NavigatablePsiElement; 11 | import com.intellij.psi.PsiElement; 12 | import com.intellij.psi.PsiFile; 13 | import com.intellij.psi.PsiManager; 14 | import com.intellij.psi.util.PsiTreeUtil; 15 | import com.intellij.ui.jcef.JBCefBrowser; 16 | import com.intellij.util.concurrency.EdtExecutorService; 17 | import org.cef.browser.CefBrowser; 18 | import org.cef.browser.CefFrame; 19 | import org.cef.callback.CefQueryCallback; 20 | import org.cef.handler.CefMessageRouterHandlerAdapter; 21 | import org.jetbrains.annotations.NotNull; 22 | import org.jetbrains.annotations.Nullable; 23 | 24 | import java.util.Arrays; 25 | import java.util.LinkedList; 26 | import java.util.List; 27 | import java.util.Queue; 28 | import java.util.regex.Pattern; 29 | 30 | public class JcefNavigateHandler extends CefMessageRouterHandlerAdapter { 31 | protected Project project; 32 | protected JBCefBrowser jbCefBrowser; 33 | 34 | public JcefNavigateHandler(Project project, JBCefBrowser jbCefBrowser) { 35 | this.project = project; 36 | this.jbCefBrowser = jbCefBrowser; 37 | } 38 | 39 | @Override 40 | public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, @NotNull String request, 41 | boolean persistent, @NotNull CefQueryCallback callback) { 42 | ApplicationManager.getApplication().executeOnPooledThread(() -> { 43 | try { 44 | open(request, callback); 45 | } catch (Throwable e) { 46 | callback.failure(50, e.toString()); 47 | } 48 | }); 49 | return true; 50 | } 51 | 52 | private void open(@NotNull String request, @NotNull CefQueryCallback callback) { 53 | if (request.equals("openDevtools")) { 54 | jbCefBrowser.openDevtools(); 55 | callback.success("openDevtools"); 56 | return; 57 | } 58 | if (!request.startsWith("navigate:")) { 59 | callback.failure(40, "not support: " + request); 60 | return; 61 | } 62 | DumbService.getInstance(project).runReadActionInSmartMode(() -> { 63 | String link = request.substring("navigate:".length()); 64 | @NotNull String filePath = link; 65 | @NotNull String childName = ""; 66 | int i = link.indexOf("#"); 67 | if (i > 0) { 68 | filePath = link.substring(0, i); 69 | childName = link.substring(i + 1); 70 | } 71 | // className to Element 72 | if (!filePath.contains("/")) { 73 | PsiElement psiElement = Parser.nameToElement(project, filePath); 74 | if (psiElement instanceof NavigatablePsiElement) { 75 | navigateSelect(callback, (NavigatablePsiElement) psiElement, childName); 76 | return; 77 | } 78 | } 79 | // findFileByNioPath() not support 2020.1 80 | @Nullable VirtualFile file = VirtualFileManager.getInstance().findFileByUrl("file:///" + filePath); 81 | if (file == null) { 82 | callback.failure(41, "file not found: " + filePath); 83 | return; 84 | } 85 | @Nullable PsiFile psiFile = PsiManager.getInstance(project).findFile(file); 86 | if (psiFile == null) { 87 | callback.failure(42, "psiFile not found: " + filePath); 88 | return; 89 | } 90 | navigateSelect(callback, psiFile, childName); 91 | }); 92 | } 93 | 94 | private static void navigateSelect(@NotNull CefQueryCallback callback, 95 | @NotNull NavigatablePsiElement element, @NotNull String childName) { 96 | if (childName.isBlank()) { 97 | navigate(callback, element); 98 | } 99 | if (navigateChild(callback, element, childName)) { 100 | return; 101 | } 102 | navigate(callback, element); 103 | } 104 | 105 | public static final Pattern PARAM_PATTERN = Pattern.compile("[#_(),]++"); 106 | 107 | private static boolean navigateChild(@NotNull CefQueryCallback callback, 108 | @NotNull NavigatablePsiElement element, @NotNull String childName) { 109 | String[] params = null; 110 | if (PARAM_PATTERN.matcher(childName).find()) { 111 | String[] split = PARAM_PATTERN.split(childName); 112 | childName = split[0]; 113 | params = Arrays.copyOfRange(split, 1, split.length); 114 | } 115 | Queue queue = new LinkedList<>(); 116 | queue.add(element); 117 | while (!queue.isEmpty()) { 118 | NavigatablePsiElement current = queue.poll(); 119 | List children = PsiTreeUtil.getChildrenOfTypeAsList(current, NavigatablePsiElement.class); 120 | for (NavigatablePsiElement child : children) { 121 | if (childName.equals(child.getName())) { 122 | if (params != null && !paramMatch(childName, child, params)) { 123 | continue; 124 | } 125 | navigate(callback, child); 126 | return true; 127 | } 128 | } 129 | queue.addAll(children); 130 | } 131 | return false; 132 | } 133 | 134 | private static boolean paramMatch(@NotNull String childName, @NotNull PsiElement child, @NotNull String[] params) { 135 | String text = PsiUnSaveUtils.getText(child); 136 | if (params.length == 0) { 137 | return text.indexOf(childName + "()") > 0; 138 | } 139 | int i = text.indexOf(childName + "("); 140 | text = text.substring(i + childName.length() + 1); 141 | i = text.indexOf(')'); 142 | text = text.substring(0, i); 143 | for (String param : params) { 144 | i = text.indexOf(param); 145 | if (i < 0) { 146 | return false; 147 | } 148 | text = text.substring(i + param.length()); 149 | } 150 | return true; 151 | } 152 | 153 | private static void navigate(@NotNull CefQueryCallback callback, @NotNull NavigatablePsiElement element) { 154 | EdtExecutorService.getInstance().submit( 155 | () -> ApplicationManager.getApplication().invokeLater( 156 | () -> { 157 | element.navigate(true); 158 | callback.success("navigate: " + element.getName()); 159 | } 160 | ) 161 | ); 162 | } 163 | } 164 | --------------------------------------------------------------------------------