├── .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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | true
18 | true
19 | false
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.run/Run Plugin Tests.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | true
22 | true
23 | false
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.run/Run Plugin.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | true
20 | true
21 | false
22 | false
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.run/Build Plugin.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | true
20 | true
21 | false
22 |
23 |
24 |
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 |
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 |
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 | 
4 | [](https://plugins.jetbrains.com/plugin/21242-draw-graph)
5 | [](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 |
--------------------------------------------------------------------------------