├── .gitignore
├── CHANGELOG.md
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
└── main
├── java
└── com
│ └── crzsc
│ └── plugin
│ ├── actions
│ ├── GenerateAction.kt
│ └── GenerateDirAction.kt
│ ├── listener
│ ├── MyProjectManagerListener.kt
│ └── PsiTreeListener.kt
│ ├── provider
│ └── AssetsLineMarkerProvider.kt
│ ├── setting
│ ├── AppSettingsComponent.kt
│ ├── AppSettingsConfigurable.kt
│ └── PluginSetting.kt
│ └── utils
│ ├── Constants.kt
│ ├── FileGenerator.kt
│ ├── FileHelperNew.kt
│ ├── Messages.kt
│ ├── PluginUtils.kt
│ └── StringExt.kt
└── resources
├── META-INF
├── plugin.xml
└── pluginIcon.svg
└── messages
├── MessagesBun_zh.properties
└── MessagesBundle.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | .idea/
3 | build/
4 | .gradle/
5 | /local.properties
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [//]: # (# FlutterAssetsGenerator Changelog)
4 | ## [2.5.0]
5 | ### Fixed
6 | - Bug fix.
7 | ### Added
8 | - add enable leading with package name option
9 | ## [2.4.2]
10 | ### Added
11 | - `Flutter: Configuring Paths` operation deletes paths that don't exist.
12 | ## [2.4.1]
13 | ### Fixed
14 | - Bug fix.
15 | ## [2.4.0]
16 | ### Added
17 | - Global configuration.
18 | - Register assets to pubspec with one click.
19 | ## [2.3.0]
20 | ### Added
21 | - Configuring ignore paths.
22 | ## [2.2.0]
23 | ### Fixed
24 | - Svg preview not displaying.
25 | ### Added
26 | - Multi module flutter project support.
27 | ## [2.1.0]
28 | ### Fixed
29 | - Bug fix.
30 | ### Added
31 | - Filename split supports config.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | It's a plugin that generates an asset index which we can easily find.It can be used on Android Studio or Idea.
2 |
3 | ## How to use
4 |
5 | ### 1.Configuring paths in pubspec.yaml
6 |
7 | Plugin now supports automatic configuration:
8 | - Right-click on folder or file, then click
9 |
`Flutter: Configuring Paths`.
10 |

11 | ### 2.Generate file
12 |
13 | You can generate file by these ways:
14 |
15 | - `Build` => `Generate Flutter Assets`
16 |

17 | - Press `Option`(mac)/`Alt`(win) + `G`,It will generate assets.dart on lib/generated.
18 |
19 | Simply use it like:
20 |
21 | ```dart
22 | Image.asset(
23 | Assets.imageLoading,
24 | width: 24,
25 | height: 24,
26 | fit: BoxFit.contain,
27 | )
28 | ```
29 |
30 | ### 3.Extras
31 |
32 | - You can locate file quickly by click line-marker.
33 |

34 | - Plugin will observe your changes on assets path and update file.
35 |
36 |
37 | ## Settings
38 | ### Global
39 | `Preferences` => `Tools` => `FlutterAssetsGenerator`
40 | ### Module based
41 | You can change default settings by add following contents in your `pubspec.yaml`.
42 |
43 | ```yaml
44 | flutter_assets_generator:
45 | # Optional. Sets the directory of generated localization files. Provided value should be a valid path on lib dir. Default: generated
46 | output_dir: generated
47 | # Optional. Sets whether utomatic monitoring of file changes. Default: true
48 | auto_detection: true
49 | # Optional. Sets file name conversion rules. Default: true
50 | named_with_parent: true
51 | # Optional. Sets the name for the generated localization file. Default: assets
52 | output_filename: assets
53 | # Optional. Sets the name for the generated localization class. Default: Assets
54 | class_name: Assets
55 | # Optional. Sets the filename split pattern for filename split. Default: [-_]
56 | filename_split_pattern: "[-_]"
57 | # Optional. Configuring ignore paths. Default: [],e.g: ["assets/fonts", "assets/images/dark", ...]
58 | path_ignore: []
59 | ```
60 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | import org.jetbrains.changelog.ExtensionsKt
2 |
3 | plugins {
4 | id 'java'
5 | id "org.jetbrains.intellij" version "1.1.6"
6 | id 'org.jetbrains.kotlin.jvm' version '1.7.20'
7 | // Gradle Changelog Plugin
8 | id 'org.jetbrains.changelog' version "1.3.1"
9 | }
10 |
11 | apply plugin: 'org.jetbrains.changelog'
12 |
13 | String properties(String key) {
14 | return project.findProperty(key).toString()
15 | }
16 |
17 |
18 | group = properties("pluginGroup")
19 | version = properties("pluginVersion")
20 |
21 | compileKotlin {
22 | kotlinOptions.jvmTarget = "1.8"
23 | }
24 |
25 | compileTestKotlin {
26 | kotlinOptions.jvmTarget = "1.8"
27 | }
28 |
29 | repositories {
30 | mavenCentral()
31 | }
32 |
33 | dependencies {
34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
35 | }
36 |
37 | // See https://github.com/JetBrains/gradle-intellij-plugin/
38 | intellij {
39 | pluginName = properties("pluginName")
40 |
41 | version = properties("platformVersion")
42 | type = properties("platformType")
43 | downloadSources = properties("platformDownloadSources").toBoolean()
44 | updateSinceUntilBuild = true
45 |
46 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file.
47 |
48 | plugins = properties("platformPlugins").split(',').toList()
49 | }
50 |
51 | // Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin
52 | changelog {
53 | version = properties("pluginVersion")
54 | path = "${project.projectDir}/CHANGELOG.md"
55 | // header = "[${-> version.get()}] - ${new SimpleDateFormat("yyyy-MM-dd").format(new Date())}"
56 | // headerParserRegex = ~/(\d+\.\d+)/
57 | // itemPrefix = "-"
58 | keepUnreleasedSection = true
59 | unreleasedTerm = "[Unreleased]"
60 | groups = ["Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"]
61 | }
62 |
63 | runIde {
64 | // Absolute path to installed target 3.5 Android Studio to use as
65 | // IDE Development Instance (the "Contents" directory is macOS specific):
66 | // ideDir.set(file("/Applications/Android Studio.app/Contents"))
67 | }
68 |
69 | patchPluginXml {
70 | version = properties("pluginVersion")
71 | sinceBuild = properties("pluginSinceBuild")
72 | untilBuild = properties("pluginUntilBuild")
73 | pluginDescription = ExtensionsKt.markdownToHTML(new File(rootDir, "README.md").text)
74 | // Get the latest available change notes from the changelog file
75 | changeNotes = ExtensionsKt.markdownToHTML(new File(rootDir, "CHANGELOG.md").text)
76 | }
77 |
78 | runPluginVerifier {
79 | ideVersions = properties("pluginVerifierIdeVersions").split(',').toList()
80 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # IntelliJ Platform Artifacts Repositories
2 | # -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html
3 | org.gradle.caching=true
4 | pluginGroup = com.crzsc
5 | pluginName = FlutterAssetsGenerator
6 | pluginVersion = 2.5.0
7 |
8 | # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
9 | # for insight into build numbers and IntelliJ Platform versions.
10 | pluginSinceBuild = 203
11 | pluginUntilBuild =
12 |
13 | # Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl
14 | # See https://jb.gg/intellij-platform-builds-list for available build versions.
15 | pluginVerifierIdeVersions = 2020.3.4,2021.1.3,2021.2.1,2021.3.1
16 |
17 | platformType = IC
18 | platformVersion = 2021.3.2
19 | platformDownloadSources = true
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 | #fit 2021.3.2
24 | platformPlugins = yaml,java,Dart:213.7433,io.flutter:70.2.3,Kotlin
25 | #platformPlugins = yaml, java, Dart:212.5080.8, io.flutter:61.2.4, Kotlin
26 |
27 | # Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3
28 | javaVersion = 11
29 |
30 | gradleVersion = 7.2
31 |
32 | #changeNotes = 1.Fix bugs. \n2.Filename split supports config.
33 |
34 | # Opt-out flag for bundling Kotlin standard library.
35 | # See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details.
36 | # suppress inspection "UnusedProperty"
37 | kotlin.stdlib.default.dependency = false
38 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cr1992/FlutterAssetsGenerator/63466940e10fab11478fcab4987d5a27218fcbfa/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/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 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto init
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto init
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :init
68 | @rem Get command-line arguments, handling Windows variants
69 |
70 | if not "%OS%" == "Windows_NT" goto win9xME_args
71 |
72 | :win9xME_args
73 | @rem Slurp the command line arguments.
74 | set CMD_LINE_ARGS=
75 | set _SKIP=2
76 |
77 | :win9xME_args_slurp
78 | if "x%~1" == "x" goto execute
79 |
80 | set CMD_LINE_ARGS=%*
81 |
82 | :execute
83 | @rem Setup the command line
84 |
85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
86 |
87 |
88 | @rem Execute Gradle
89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
90 |
91 | :end
92 | @rem End local scope for the variables with windows NT shell
93 | if "%ERRORLEVEL%"=="0" goto mainEnd
94 |
95 | :fail
96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
97 | rem the _cmd.exe /c_ return code!
98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
99 | exit /b 1
100 |
101 | :mainEnd
102 | if "%OS%"=="Windows_NT" endlocal
103 |
104 | :omega
105 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'Flutter_assets_generator'
2 |
3 |
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/actions/GenerateAction.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.actions
2 |
3 | import com.crzsc.plugin.utils.FileGenerator
4 | import com.crzsc.plugin.utils.FileHelperNew.shouldActivateFor
5 | import com.crzsc.plugin.utils.PluginUtils.showNotify
6 | import com.crzsc.plugin.utils.message
7 | import com.intellij.openapi.actionSystem.AnAction
8 | import com.intellij.openapi.actionSystem.AnActionEvent
9 | import com.intellij.openapi.actionSystem.PlatformDataKeys
10 |
11 | class GenerateAction : AnAction() {
12 |
13 | override fun actionPerformed(e: AnActionEvent) {
14 | val project = e.getData(PlatformDataKeys.PROJECT)
15 | if (shouldActivateFor(project!!)) {
16 | FileGenerator(project).generateAll()
17 | } else {
18 | showNotify(message("notFlutterProject"))
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/actions/GenerateDirAction.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.actions
2 |
3 | import com.crzsc.plugin.utils.FileGenerator
4 | import com.crzsc.plugin.utils.FileHelperNew
5 | import com.crzsc.plugin.utils.PluginUtils.showNotify
6 | import com.crzsc.plugin.utils.message
7 | import com.intellij.openapi.actionSystem.AnAction
8 | import com.intellij.openapi.actionSystem.AnActionEvent
9 | import com.intellij.openapi.actionSystem.PlatformDataKeys
10 |
11 | /**
12 | * 同步文件 文件夹到yaml
13 | */
14 | class GenerateDirAction : AnAction() {
15 |
16 | override fun actionPerformed(e: AnActionEvent) {
17 | val project = e.getData(PlatformDataKeys.PROJECT)
18 | if (FileHelperNew.shouldActivateFor(project!!)) {
19 | val file = e.getData(PlatformDataKeys.VIRTUAL_FILE)
20 | if (file != null) {
21 | FileGenerator(project).buildYaml(file)
22 | }
23 | } else {
24 | showNotify(message("notFlutterProject"))
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/listener/MyProjectManagerListener.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.listener
2 |
3 | import com.intellij.openapi.project.Project
4 | import com.intellij.openapi.project.ProjectManagerListener
5 | import com.intellij.psi.PsiManager
6 |
7 | class MyProjectManagerListener : ProjectManagerListener {
8 | private val eventsMap = mutableMapOf()
9 | override fun projectClosed(project: Project) {
10 | super.projectClosed(project)
11 | }
12 |
13 | override fun projectOpened(project: Project) {
14 | super.projectOpened(project)
15 | val treeListener = PsiTreeListener(project)
16 | eventsMap[project] = treeListener
17 | PsiManager.getInstance(project).addPsiTreeChangeListener(
18 | treeListener)
19 | }
20 |
21 | override fun projectClosing(project: Project) {
22 | super.projectClosing(project)
23 | eventsMap.remove(project)?.let {
24 | PsiManager.getInstance(project).removePsiTreeChangeListener(it)
25 | }
26 | }
27 |
28 | override fun projectClosingBeforeSave(project: Project) {
29 | super.projectClosingBeforeSave(project)
30 | }
31 | }
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/listener/PsiTreeListener.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.listener
2 |
3 | import com.crzsc.plugin.utils.FileGenerator
4 | import com.crzsc.plugin.utils.FileHelperNew
5 | import com.intellij.openapi.project.Project
6 | import com.intellij.psi.PsiDirectory
7 | import com.intellij.psi.PsiTreeChangeEvent
8 | import com.intellij.psi.PsiTreeChangeListener
9 | import com.intellij.util.castSafelyTo
10 | import java.util.*
11 | import kotlin.concurrent.timerTask
12 |
13 | class PsiTreeListener(private val project: Project) : PsiTreeChangeListener {
14 | private val fileGenerator = FileGenerator(project)
15 |
16 | override fun beforePropertyChange(event: PsiTreeChangeEvent) {
17 | }
18 |
19 | override fun childReplaced(event: PsiTreeChangeEvent) {
20 | handleEvent(event)
21 | }
22 |
23 | override fun childrenChanged(event: PsiTreeChangeEvent) {
24 | handleEvent(event)
25 | }
26 |
27 | override fun beforeChildAddition(event: PsiTreeChangeEvent) {
28 | }
29 |
30 | override fun beforeChildReplacement(event: PsiTreeChangeEvent) {
31 | }
32 |
33 | override fun propertyChanged(event: PsiTreeChangeEvent) {
34 | handleEvent(event)
35 | }
36 |
37 | override fun beforeChildrenChange(event: PsiTreeChangeEvent) {
38 | }
39 |
40 | override fun childMoved(event: PsiTreeChangeEvent) {
41 | handleEvent(event)
42 | }
43 |
44 | override fun childRemoved(event: PsiTreeChangeEvent) {
45 | handleEvent(event)
46 | }
47 |
48 | override fun beforeChildMovement(event: PsiTreeChangeEvent) {
49 | }
50 |
51 | override fun childAdded(event: PsiTreeChangeEvent) {
52 | handleEvent(event)
53 | }
54 |
55 | override fun beforeChildRemoval(event: PsiTreeChangeEvent) {
56 | }
57 |
58 | private fun handleEvent(event: PsiTreeChangeEvent) {
59 | val assets = FileHelperNew.getAssets(project)
60 | for (config in assets) {
61 | if (FileHelperNew.isAutoDetectionEnable(config)) {
62 | // 该Module开启了自动检测
63 | event.child?.let { changedFile ->
64 | changedFile.parent.castSafelyTo()?.let { dir ->
65 | //assets目录发生改变 这里延迟生成避免报错
66 | for (file in config.assetVFiles) {
67 | if (dir.virtualFile.path.startsWith(file.path)) {
68 | Timer().schedule(timerTask {
69 | fileGenerator.generateOne(config)
70 | }, 300)
71 | break
72 | }
73 | }
74 | }
75 | }
76 | }
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/provider/AssetsLineMarkerProvider.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.provider
2 |
3 | import com.crzsc.plugin.utils.FileHelperNew
4 | import com.crzsc.plugin.utils.PluginUtils.openFile
5 | import com.crzsc.plugin.utils.isSvgExtension
6 | import com.intellij.codeInsight.daemon.LineMarkerInfo
7 | import com.intellij.codeInsight.daemon.LineMarkerProvider
8 | import com.intellij.openapi.editor.markup.GutterIconRenderer
9 | import com.intellij.openapi.util.Iconable
10 | import com.intellij.openapi.vfs.LocalFileSystem
11 | import com.intellij.openapi.vfs.VirtualFile
12 | import com.intellij.psi.PsiElement
13 | import com.intellij.psi.impl.source.tree.LeafPsiElement
14 | import com.intellij.psi.util.PsiTreeUtil
15 | import com.intellij.ui.scale.ScaleContext
16 | import com.intellij.util.IconUtil
17 | import com.intellij.util.SVGLoader
18 | import io.flutter.utils.FlutterModuleUtils
19 | import org.jetbrains.kotlin.idea.util.module
20 | import javax.swing.Icon
21 | import javax.swing.ImageIcon
22 |
23 |
24 | /**
25 | * Svgs.dart显示图标在路径左侧
26 | */
27 | class AssetsLineMarkerProvider : LineMarkerProvider {
28 |
29 | override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? {
30 | val module = element.module
31 | if (!FlutterModuleUtils.isFlutterModule(module)) return null
32 | val elementType = (element as? LeafPsiElement?)?.elementType?.toString()
33 | if (elementType == "REGULAR_STRING_PART"
34 | ) {
35 | // 这里会被多次调用 尽量减少调用次数
36 | var assetName: String? = null
37 | var leadingWithPackageName: String? = null;
38 | if (module != null) {
39 | FileHelperNew.getPubSpecConfig(module)?.let {
40 | assetName = FileHelperNew.getGeneratedFileName(it)
41 | leadingWithPackageName = it.getLeadingWithPackageNameIfChecked()
42 | }
43 | }
44 | val filenameCorrect = element.containingFile.name.equals(
45 | assetName, true
46 | ) || element.containingFile.name.equals(
47 | "assets.dart", true
48 | )
49 | if (filenameCorrect) {
50 | // println("filenameCorrect showMakeByType : $element")
51 | return showMakeByType(element, leadingWithPackageName)
52 | }
53 | }
54 | return null
55 | }
56 |
57 | private fun showMakeByType(element: PsiElement, leadingWithPackageName: String?): LineMarkerInfo<*>? {
58 | val assetsPath = element.text
59 | val anchor = PsiTreeUtil.getDeepestFirst(element)
60 | // 先用默认的path找文件
61 | var filePath = element.project.basePath + "/" + element.text
62 | if (!leadingWithPackageName.isNullOrEmpty()) {
63 | filePath = filePath.replace(leadingWithPackageName, "")
64 | }
65 | var vFile = LocalFileSystem.getInstance().findFileByPath(filePath)
66 | if (vFile == null) {
67 | // 如果没找到,尝试根据当前文件向上查找
68 | var file = element.containingFile.viewProvider.virtualFile
69 | while (file.name != "lib" && file.parent != null) {
70 | file = file.parent
71 | }
72 | filePath = file.parent.path + "/" + element.text
73 | if (!leadingWithPackageName.isNullOrEmpty()) {
74 | filePath = filePath.replace(leadingWithPackageName, "")
75 | }
76 | vFile = LocalFileSystem.getInstance().findFileByPath(filePath)
77 | }
78 | // println("showMakeByType assetsPath $assetsPath")
79 | if (vFile != null) {
80 | return when {
81 | assetsPath.isSvgExtension -> showSvgMark(element, anchor, vFile)
82 | else -> showIconMark(element, anchor, vFile)
83 | }
84 | }
85 | return null
86 | }
87 |
88 |
89 | private fun showSvgMark(
90 | element: PsiElement, anchor: PsiElement,
91 | vFile: VirtualFile
92 | ): LineMarkerInfo<*> {
93 | val icon: Icon =
94 | ImageIcon(
95 | SVGLoader.load(
96 | null,
97 | vFile.inputStream,
98 | ScaleContext.createIdentity(),
99 | 16.0,
100 | 16.0
101 | )
102 | )
103 | return LineMarkerInfo(anchor, anchor.textRange, icon, {
104 | //悬停,会多次调用
105 | return@LineMarkerInfo ""
106 | }, { _, _ -> element.openFile(vFile) }, GutterIconRenderer.Alignment.LEFT)
107 |
108 | }
109 |
110 |
111 | private fun showIconMark(
112 | element: PsiElement, anchor: PsiElement,
113 | vFile: VirtualFile
114 | ): LineMarkerInfo<*> {
115 | val icon = IconUtil.getIcon(vFile, Iconable.ICON_FLAG_VISIBILITY, element.project)
116 | //其他文件展示文件格式
117 | return LineMarkerInfo(
118 | anchor, anchor.textRange,
119 | icon, {
120 | //悬停,会多次调用
121 | return@LineMarkerInfo ""
122 | }, { _, _ -> element.openFile(vFile) }, GutterIconRenderer.Alignment.LEFT
123 | )
124 |
125 | }
126 |
127 | }
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/setting/AppSettingsComponent.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.setting
2 |
3 | import com.crzsc.plugin.utils.message
4 | import com.intellij.ui.components.JBCheckBox
5 | import com.intellij.ui.components.JBLabel
6 | import com.intellij.ui.components.JBTextField
7 | import com.intellij.util.ui.FormBuilder
8 | import javax.swing.JComponent
9 | import javax.swing.JPanel
10 |
11 | class AppSettingsComponent {
12 | val panel: JPanel
13 | private val filePath = JBTextField()
14 | private val fileName = JBTextField()
15 | private val className = JBTextField()
16 | private val filenameSplitPattern = JBTextField()
17 | private val autoDetection = JBCheckBox(message("settingsAutoDetection"))
18 | private val namedWithParent = JBCheckBox(message("settingsNamed"))
19 | private val leadingWithPackageName = JBCheckBox(message("settingsLeadingWithPackageName"))
20 |
21 | init {
22 | panel = FormBuilder.createFormBuilder()
23 | .addLabeledComponent(JBLabel(message("settingsFilePath")), filePath, 1, false)
24 | .addLabeledComponent(JBLabel(message("settingsFileName")), fileName, 1, false)
25 | .addLabeledComponent(JBLabel(message("settingsClassName")), className, 1, false)
26 | .addLabeledComponent(JBLabel(message("settingsSplitPattern")), filenameSplitPattern, 1, false)
27 | .addComponent(autoDetection, 1)
28 | .addComponent(namedWithParent, 1)
29 | .addComponent(leadingWithPackageName, 1)
30 | .addComponentFillVertically(JPanel(), 0)
31 | .panel
32 | }
33 |
34 | val preferredFocusedComponent: JComponent
35 | get() = filePath
36 |
37 | fun getFilePath(): String {
38 | return filePath.text
39 | }
40 |
41 | fun setFilePath(text: String?) {
42 | filePath.text = text
43 | }
44 |
45 | fun getFileName(): String {
46 | return fileName.text
47 | }
48 |
49 |
50 | fun setFileName(text: String?) {
51 | fileName.text = text
52 | }
53 |
54 | fun getClassName(): String {
55 | return className.text
56 | }
57 |
58 | fun setClassName(text: String?) {
59 | className.text = text
60 | }
61 |
62 | fun getFilenameSplitPattern(): String {
63 | return filenameSplitPattern.text
64 | }
65 |
66 | fun setFilenameSplitPattern(text: String?) {
67 | filenameSplitPattern.text = text
68 | }
69 |
70 | fun getAutoDetection(): Boolean {
71 | return autoDetection.isSelected
72 | }
73 |
74 | fun setAutoDetection(newStatus: Boolean) {
75 | autoDetection.isSelected = newStatus
76 | }
77 |
78 | fun getNamedWithParent(): Boolean {
79 | return namedWithParent.isSelected
80 | }
81 |
82 | fun setNamedWithParent(newStatus: Boolean) {
83 | namedWithParent.isSelected = newStatus
84 | }
85 |
86 | fun getLeadingWithPackageName(): Boolean {
87 | return leadingWithPackageName.isSelected
88 | }
89 |
90 | fun setLeadingWithPackageName(newStatus: Boolean) {
91 | leadingWithPackageName.isSelected = newStatus
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/setting/AppSettingsConfigurable.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.setting
2 |
3 | import com.intellij.openapi.options.Configurable
4 | import javax.swing.JComponent
5 |
6 | class AppSettingsConfigurable : Configurable {
7 | private var mySettingsComponent: AppSettingsComponent? = null
8 |
9 | // A default constructor with no arguments is required because this implementation
10 | // is registered as an applicationConfigurable EP
11 | override fun getDisplayName(): String {
12 | return "FlutterAssetsGenerator"
13 | }
14 |
15 | override fun getPreferredFocusedComponent(): JComponent {
16 | return mySettingsComponent!!.preferredFocusedComponent
17 | }
18 |
19 | override fun createComponent(): JComponent {
20 | mySettingsComponent = AppSettingsComponent()
21 | return mySettingsComponent!!.panel
22 | }
23 |
24 | override fun isModified(): Boolean {
25 | val settings = PluginSetting.instance
26 | var modified = mySettingsComponent!!.getAutoDetection() != settings.autoDetection
27 | modified = modified or (mySettingsComponent!!.getFileName() != settings.fileName)
28 | modified = modified or (mySettingsComponent!!.getClassName() != settings.className)
29 | modified = modified or (mySettingsComponent!!.getFilenameSplitPattern() != settings.filenameSplitPattern)
30 | modified = modified or (mySettingsComponent!!.getFilePath() != settings.filePath)
31 | modified = modified or (mySettingsComponent!!.getNamedWithParent() != settings.namedWithParent)
32 | modified = modified or (mySettingsComponent!!.getLeadingWithPackageName() != settings.leadingWithPackageName)
33 | return modified
34 | }
35 |
36 | override fun apply() {
37 | val settings = PluginSetting.instance
38 | settings.autoDetection = mySettingsComponent!!.getAutoDetection()
39 | settings.fileName = mySettingsComponent!!.getFileName()
40 | settings.className = mySettingsComponent!!.getClassName()
41 | settings.filePath = mySettingsComponent!!.getFilePath()
42 | settings.filenameSplitPattern = mySettingsComponent!!.getFilenameSplitPattern()
43 | settings.namedWithParent = mySettingsComponent!!.getNamedWithParent()
44 | settings.leadingWithPackageName = mySettingsComponent!!.getLeadingWithPackageName()
45 | }
46 |
47 | override fun reset() {
48 | val settings = PluginSetting.instance
49 | mySettingsComponent!!.setAutoDetection(settings.autoDetection)
50 | mySettingsComponent!!.setFileName(settings.fileName)
51 | mySettingsComponent!!.setClassName(settings.className)
52 | mySettingsComponent!!.setFilePath(settings.filePath)
53 | mySettingsComponent!!.setFilenameSplitPattern(settings.filenameSplitPattern)
54 | mySettingsComponent!!.setNamedWithParent(settings.namedWithParent)
55 | mySettingsComponent!!.setLeadingWithPackageName(settings.leadingWithPackageName)
56 | }
57 |
58 | override fun disposeUIResources() {
59 | mySettingsComponent = null
60 | }
61 | }
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/setting/PluginSetting.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.setting
2 |
3 | import com.crzsc.plugin.utils.Constants
4 | import com.intellij.openapi.application.ApplicationManager
5 | import com.intellij.openapi.components.*
6 | import com.intellij.util.xmlb.XmlSerializerUtil
7 |
8 | /**
9 | * Supports storing the application settings in a persistent way.
10 | * The [State] and [Storage] annotations define the name of the data and the file name where
11 | * these persistent application settings are stored.
12 | */
13 | @State(name = "com.crzsc.plugin.setting.PluginSetting", storages = [Storage("FlutterAssetsGenerator.xml")])
14 | class PluginSetting : PersistentStateComponent {
15 | var className: String? = Constants.DEFAULT_CLASS_NAME
16 | var fileName: String? = Constants.DEFAULT_CLASS_NAME.lowercase()
17 | var filePath: String? = Constants.DEFAULT_OUTPUT_DIR
18 | var filenameSplitPattern: String? = Constants.DEFAULT_FILENAME_SPLIT_PATTERN
19 | var namedWithParent = true
20 | var autoDetection = true
21 | var leadingWithPackageName = true;
22 | override fun getState(): PluginSetting {
23 | return this
24 | }
25 |
26 | override fun loadState(state: PluginSetting) {
27 | XmlSerializerUtil.copyBean(state, this)
28 | }
29 |
30 | companion object {
31 | val instance: PluginSetting
32 | get() = ApplicationManager.getApplication().getService(PluginSetting::class.java)
33 | }
34 | }
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/utils/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.utils
2 |
3 | object Constants {
4 | /**
5 | * 配置map的key
6 | */
7 | const val KEY_CONFIGURATION_MAP = "flutter_assets_generator"
8 | /**
9 | * 输出目录的key
10 | */
11 | const val KEY_OUTPUT_DIR = "output_dir"
12 |
13 | /**
14 | * 输出文件的类名
15 | */
16 | const val KEY_CLASS_NAME = "class_name"
17 |
18 | /**
19 | * 是否自动检测
20 | */
21 | const val KEY_AUTO_DETECTION = "auto_detection"
22 |
23 | /**
24 | * 命名是否根据上级目录决定
25 | */
26 | const val KEY_NAMED_WITH_PARENT = "named_with_parent"
27 |
28 | const val KEY_LEADING_WITH_PACKAGE_NAME = "leading_with_package_name"
29 |
30 | /**
31 | * 输出的文件名
32 | */
33 | const val KEY_OUTPUT_FILENAME = "output_filename"
34 |
35 | /**
36 | * 分割文件的正则
37 | */
38 | const val FILENAME_SPLIT_PATTERN = "filename_split_pattern"
39 |
40 | /**
41 | * 忽略的目录
42 | */
43 | const val PATH_IGNORE = "path_ignore"
44 |
45 | /**
46 | * 默认目录
47 | */
48 | const val DEFAULT_OUTPUT_DIR = "generated"
49 | const val DEFAULT_CLASS_NAME = "Assets"
50 |
51 | const val DEFAULT_FILENAME_SPLIT_PATTERN = "[-_]"
52 | }
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/utils/FileGenerator.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.utils
2 |
3 | import com.crzsc.plugin.utils.PluginUtils.showNotify
4 | import com.crzsc.plugin.utils.PluginUtils.toLowCamelCase
5 | import com.crzsc.plugin.utils.PluginUtils.upperCaseFirst
6 | import com.intellij.openapi.application.ApplicationManager
7 | import com.intellij.openapi.command.WriteCommandAction
8 | import com.intellij.openapi.project.Project
9 | import com.intellij.openapi.project.guessModuleDir
10 | import com.intellij.openapi.vfs.VirtualFile
11 | import com.intellij.psi.PsiDocumentManager
12 | import com.intellij.psi.PsiManager
13 | import com.intellij.psi.util.PsiTreeUtil
14 | import io.flutter.utils.FlutterModuleUtils
15 | import org.jetbrains.kotlin.idea.core.util.toPsiFile
16 | import org.jetbrains.kotlin.idea.util.findModule
17 | import org.jetbrains.yaml.YAMLElementGenerator
18 | import org.jetbrains.yaml.psi.YAMLFile
19 | import org.jetbrains.yaml.psi.YAMLMapping
20 | import org.jetbrains.yaml.psi.YAMLSequence
21 | import java.io.File
22 |
23 | class FileGenerator(private val project: Project) {
24 | private val ignoreDir = listOf("2.0x", "3.0x", "Mx", "Nx")
25 |
26 | /**
27 | * 为所有模块重新生成
28 | */
29 | fun generateAll() {
30 | WriteCommandAction.runWriteCommandAction(project) {
31 | val assets = FileHelperNew.getAssets(project) as MutableList
32 | assets.removeAll {
33 | println("module : ${it.module} assets ${it.assetVFiles}")
34 | it.assetVFiles.isEmpty()
35 | }
36 | if (assets.isEmpty()) {
37 | showNotify("Please configure your assets path in pubspec.yaml")
38 | return@runWriteCommandAction
39 | }
40 | for (config in assets) {
41 | generateWithConfig(config)
42 | }
43 | }
44 | }
45 |
46 |
47 | /**
48 | * 生成单个模块文件
49 | */
50 | fun generateOne(config: ModulePubSpecConfig) {
51 | WriteCommandAction.runWriteCommandAction(project) {
52 | generateWithConfig(config)
53 | }
54 | }
55 |
56 | private fun generateWithConfig(config: ModulePubSpecConfig) {
57 | println(config)
58 | val module = config.module
59 | val map = mutableMapOf()
60 | val ignorePath = FileHelperNew.getPathIgnore(config)
61 | // 这里可能有多个目录或者文件
62 | for (file in config.assetVFiles) {
63 | println("generate file map in $file")
64 | generateFileMap(
65 | file,
66 | config,
67 | map,
68 | ignorePath,
69 | )
70 | }
71 | if (map.isEmpty()) {
72 | // showNotify("assets path is empty")
73 | println("${config.module} assets map is empty, skip")
74 | return
75 | }
76 | val content = StringBuilder()
77 | content.append("///This file is automatically generated. DO NOT EDIT, all your changes would be lost.\n")
78 | val className = FileHelperNew.getGeneratedClassName(config)
79 | content.append("class $className {\n $className._();\n\n")
80 | map.toSortedMap().forEach {
81 | content.append(" static const String ${it.key} = '${config.getLeadingWithPackageNameIfChecked()}${it.value}';\n")
82 | }
83 | content.append("\n}\n")
84 | val psiManager = PsiManager.getInstance(project)
85 | val psiDocumentManager = PsiDocumentManager.getInstance(project)
86 | FileHelperNew.getGeneratedFile(config).let { generated ->
87 | psiManager.findFile(generated)?.let { dartFile ->
88 | psiDocumentManager.getDocument(dartFile)?.let { document ->
89 | if (document.text != content.toString()) {
90 | document.setText(content)
91 | psiDocumentManager.commitDocument(document)
92 | showNotify("$module : assets generate succeed")
93 | } else {
94 | showNotify("$module : nothing changed")
95 | }
96 | }
97 | }
98 | }
99 | }
100 |
101 | private fun generateFileMap(
102 | root: VirtualFile,
103 | config: ModulePubSpecConfig,
104 | map: MutableMap,
105 | ignorePath: List,
106 | ) {
107 | val namedWithParent = FileHelperNew.isNamedWithParent(config)
108 | val pattern = FileHelperNew.getFilenameSplitPattern(config)
109 | val basePath = config.module.guessModuleDir()?.path
110 | val regex = Regex(pattern)
111 | if (root.isDirectory) {
112 | root.children.filter {
113 | var pathIgnore = false
114 | if (ignorePath.isNotEmpty()) {
115 | for (name in ignorePath) {
116 | if (it.path.contains(name, ignoreCase = true)) {
117 | pathIgnore = true
118 | println("${it.path} pathIgnore : $pathIgnore")
119 | break
120 | }
121 | }
122 | }
123 | !it.name.startsWith('.') && checkName(it.name) && !pathIgnore
124 | }.forEach {
125 | if (it.isDirectory) {
126 | generateFileMap(it, config, map, ignorePath)
127 | } else {
128 | config(it, regex, basePath, namedWithParent, map)
129 | }
130 | }
131 | } else {
132 | config(root, regex, basePath, namedWithParent, map)
133 | }
134 | }
135 |
136 | private fun config(
137 | it: VirtualFile,
138 | regex: Regex,
139 | basePath: String?,
140 | namedWithParent: Boolean,
141 | map: MutableMap
142 | ) {
143 | var key = it.nameWithoutExtension.replace(".", "_").toLowCamelCase(regex)///fileName style
144 | val value = it.path.removePrefix("$basePath/")
145 | if (namedWithParent) {
146 | it.parent?.let { parent ->
147 | key = "${parent.name.toLowCamelCase(regex)}${key.upperCaseFirst()}"
148 | if (map.containsKey(key)) {
149 | key = "${parent.parent.name.toLowCamelCase(regex)}${key.upperCaseFirst()}"
150 | }
151 | map[key] = value
152 | }
153 | } else {
154 | map[key] = value
155 | }
156 | }
157 |
158 | /**
159 | * 将所选择目录及子目录添加到yaml配置
160 | */
161 | fun buildYaml(file: VirtualFile) {
162 | saveChanges()
163 | val modules = FileHelperNew.getAssets(project)
164 | var module: ModulePubSpecConfig? = null
165 | for (m in modules) {
166 | // println("file.path ${file.path} pubRoot path ${m.pubRoot.path}")
167 | if (file.path.startsWith(m.pubRoot.path)) {
168 | module = m
169 | break
170 | }
171 | }
172 | if (module != null && FlutterModuleUtils.isFlutterModule(module.module)) {
173 | // println("current module is ${module.module}")
174 | val paths = mutableListOf()
175 | val rootPath = "${module.pubRoot.path}/"
176 | if (file.isDirectory) {
177 | traversalDir(file, rootPath, paths)
178 | } else {
179 | paths.add(file.path.removePrefix(rootPath))
180 | }
181 | // println(paths)
182 | val moduleAssets = FileHelperNew.tryGetAssetsList(module.map)
183 | if (moduleAssets != null) {
184 | val moduleDir = file.findModule(project)?.guessModuleDir()
185 | println(moduleDir?.path)
186 | moduleAssets.removeIf {
187 | var parentPath = moduleDir?.path
188 | var path = it as String
189 | path = path.removeSuffix(File.separator)
190 | if (path.contains(File.separator)) {
191 | val subIndex = path.lastIndexOf(File.separator)
192 | parentPath = "$parentPath${File.separator}${path.substring(0, subIndex + 1)}"
193 | path = path.substring(subIndex + 1, path.length)
194 | // println("parentPath:$parentPath path:$path")
195 | }
196 | val asset = File(parentPath, path)
197 | println("${asset.absolutePath} file exists : ${asset.exists()}")
198 | !asset.exists()
199 | }
200 | paths.removeIf {
201 | moduleAssets.contains(it)
202 | }
203 | }
204 | val yamlFile = module.pubRoot.pubspec.toPsiFile(project) as? YAMLFile
205 | yamlFile?.let {
206 | val psiElement =
207 | yamlFile.node.getChildren(null)
208 | .firstOrNull()?.psi?.children?.firstOrNull()?.children?.firstOrNull { it.text.startsWith("flutter:") }
209 | if (psiElement != null) {
210 | val yamlMapping = psiElement.children.first() as YAMLMapping
211 | WriteCommandAction.runWriteCommandAction(project) {
212 | var assetsValue = yamlMapping.keyValues.firstOrNull { it.keyText == "assets" }
213 | val stringBuilder = StringBuilder()
214 | moduleAssets?.forEach {
215 | stringBuilder.append(" - $it\n")
216 | }
217 | paths.forEach {
218 | stringBuilder.append(" - $it\n")
219 | }
220 | stringBuilder.removeSuffix("\n")
221 | if (assetsValue == null) {
222 | assetsValue = YAMLElementGenerator.getInstance(project)
223 | .createYamlKeyValue("assets", stringBuilder.toString())
224 | yamlMapping.putKeyValue(assetsValue)
225 | } else {
226 | val yamlValue = PsiTreeUtil.collectElementsOfType(
227 | YAMLElementGenerator.getInstance(project)
228 | .createDummyYamlWithText(stringBuilder.toString()), YAMLSequence::class.java
229 | ).iterator().next()
230 | assetsValue.setValue(yamlValue)
231 | }
232 | }
233 | }
234 | }
235 | saveChanges()
236 | showNotify("Flutter: Configuration complete.")
237 | } else {
238 | showNotify("This module is not flutter module")
239 | }
240 | }
241 |
242 | private fun saveChanges() {
243 | ApplicationManager.getApplication().saveAll()
244 | PsiDocumentManager.getInstance(project).commitAllDocumentsUnderProgress()
245 | }
246 |
247 | private fun traversalDir(file: VirtualFile, rootPath: String, list: MutableList) {
248 | if (file.isDirectory) {
249 | list.add("${file.path.removePrefix(rootPath)}/")
250 | file.children.forEach {
251 | if (it.isDirectory) {
252 | traversalDir(it, rootPath, list)
253 | }
254 | }
255 | }
256 | }
257 |
258 | private fun checkName(name: String): Boolean {
259 | return !ignoreDir.contains(name)
260 | }
261 |
262 | }
263 |
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/utils/FileHelperNew.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.utils
2 |
3 | import com.crzsc.plugin.setting.PluginSetting
4 | import com.intellij.openapi.module.Module
5 | import com.intellij.openapi.project.Project
6 | import com.intellij.openapi.project.guessModuleDir
7 | import com.intellij.openapi.vfs.VirtualFile
8 | import io.flutter.pub.PubRoot
9 | import io.flutter.utils.FlutterModuleUtils
10 | import org.jetbrains.kotlin.idea.util.projectStructure.allModules
11 | import org.jetbrains.kotlin.konan.file.File
12 | import org.yaml.snakeyaml.Yaml
13 | import java.io.FileInputStream
14 | import java.util.*
15 | import java.util.regex.Pattern
16 |
17 | /**
18 | * 基于Module来处理Assets
19 | */
20 | object FileHelperNew {
21 |
22 | /**
23 | * 获取所有可用的Flutter Module的Asset配置
24 | */
25 | @JvmStatic
26 | fun getAssets(project: Project): List {
27 | val modules = project.allModules()
28 | val folders = mutableListOf()
29 | for (module in modules) {
30 | if (FlutterModuleUtils.isFlutterModule(module)) {
31 | val moduleDir = module.guessModuleDir()
32 | if (moduleDir != null) {
33 | getPubSpecConfig(module)?.let {
34 | folders.add(it)
35 | }
36 | }
37 | }
38 | }
39 | return folders
40 | }
41 |
42 | @JvmStatic
43 | fun shouldActivateFor(project: Project): Boolean {
44 | return FlutterModuleUtils.hasFlutterModule(project)
45 | }
46 |
47 | fun tryGetAssetsList(map: Map<*, *>): MutableList<*>? {
48 | (map["flutter"] as? Map<*, *>)?.let {
49 | return it["assets"] as? MutableList<*>
50 | }
51 | return null
52 | }
53 |
54 | @JvmStatic
55 | fun getPubSpecConfig(module: Module): ModulePubSpecConfig? {
56 | try {
57 | val moduleDir = module.guessModuleDir()
58 | val pubRoot = PubRoot.forDirectory(moduleDir)
59 | if (moduleDir != null && pubRoot != null) {
60 | val fis = FileInputStream(pubRoot.pubspec.path)
61 | val pubConfigMap = Yaml().load(fis) as? Map
62 | if (pubConfigMap != null) {
63 | val assetVFiles = mutableListOf()
64 | (pubConfigMap["flutter"] as? Map<*, *>)?.let { configureMap ->
65 | (configureMap["assets"] as? ArrayList<*>)?.let { list ->
66 | for (path in list) {
67 | moduleDir.findFileByRelativePath(path as String)?.let {
68 | if (it.isDirectory) {
69 | val index = path.indexOf("/")
70 | val assetsPath = if (index == -1) {
71 | path
72 | } else {
73 | path.substring(0, index)
74 | }
75 | val assetVFile = moduleDir.findChild(assetsPath)
76 | ?: moduleDir.createChildDirectory(this, assetsPath)
77 | if (!assetVFiles.contains(assetVFile)) {
78 | assetVFiles.add(assetVFile)
79 | }
80 | } else {
81 | if (!assetVFiles.contains(it)) {
82 | assetVFiles.add(it)
83 | }
84 | }
85 | }
86 | }
87 | }
88 | }
89 | return ModulePubSpecConfig(
90 | module,
91 | pubRoot,
92 | assetVFiles,
93 | pubConfigMap,
94 | )
95 | }
96 | }
97 | } catch (e: Exception) {
98 | e.printStackTrace()
99 | return null
100 | }
101 | return null
102 | }
103 |
104 | /**
105 | * 读取配置
106 | */
107 | private fun readSetting(config: ModulePubSpecConfig, key: String): Any? {
108 | (config.map[Constants.KEY_CONFIGURATION_MAP] as? Map<*, *>)?.let { configureMap ->
109 | return configureMap[key]
110 | }
111 | return null
112 | }
113 |
114 | /**
115 | * 是否开启了自动检测
116 | */
117 | fun isAutoDetectionEnable(config: ModulePubSpecConfig): Boolean {
118 | return readSetting(config, Constants.KEY_AUTO_DETECTION) as Boolean? ?: PluginSetting.instance.autoDetection
119 | }
120 |
121 | /**
122 | * 是否根据父文件夹命名 默认true
123 | */
124 | fun isNamedWithParent(config: ModulePubSpecConfig): Boolean {
125 | return readSetting(config, Constants.KEY_NAMED_WITH_PARENT) as Boolean?
126 | ?: PluginSetting.instance.namedWithParent
127 | }
128 |
129 | fun isWithLeadingWithPackageName(config: ModulePubSpecConfig): Boolean {
130 | return readSetting(config, Constants.KEY_LEADING_WITH_PACKAGE_NAME) as Boolean?
131 | ?: PluginSetting.instance.leadingWithPackageName
132 | }
133 |
134 | /**
135 | * 读取生成的类名配置
136 | */
137 | fun getGeneratedClassName(config: ModulePubSpecConfig): String {
138 | return readSetting(config, Constants.KEY_CLASS_NAME) as String? ?: PluginSetting.instance.className
139 | ?: Constants.DEFAULT_CLASS_NAME
140 | }
141 |
142 | /**
143 | * 读取文件分割配置
144 | */
145 | fun getFilenameSplitPattern(config: ModulePubSpecConfig): String {
146 | return try {
147 | val pattern =
148 | readSetting(config, Constants.FILENAME_SPLIT_PATTERN) as String?
149 | ?: PluginSetting.instance.filenameSplitPattern ?: Constants.DEFAULT_FILENAME_SPLIT_PATTERN
150 | Pattern.compile(pattern)
151 | pattern
152 | } catch (e: Exception) {
153 | e.printStackTrace()
154 | Constants.DEFAULT_FILENAME_SPLIT_PATTERN
155 | }
156 | }
157 |
158 | /**
159 | * 读取忽略文件目录
160 | */
161 | fun getPathIgnore(config: ModulePubSpecConfig): List {
162 | return try {
163 | val paths =
164 | readSetting(config, Constants.PATH_IGNORE) as List?
165 | ?: emptyList()
166 | paths
167 | } catch (e: Exception) {
168 | e.printStackTrace()
169 | emptyList()
170 | }
171 | }
172 |
173 | /**
174 | * 获取generated自动生成目录
175 | * 从yaml中读取
176 | */
177 | private fun getGeneratedFilePath(config: ModulePubSpecConfig): VirtualFile {
178 | return config.pubRoot.lib?.let { lib ->
179 | // 没有配置则返回默认path
180 | val filePath: String = readSetting(config, Constants.KEY_OUTPUT_DIR) as String?
181 | ?: PluginSetting.instance.filePath ?: Constants.DEFAULT_OUTPUT_DIR
182 | println("getGeneratedFilePath $filePath")
183 | if (!filePath.contains(File.separator)) {
184 | return@let lib.findOrCreateChildDir(lib, filePath)
185 | } else {
186 | var file = lib
187 | filePath.split(File.separator).forEach { dir ->
188 | if (dir.isNotEmpty()) {
189 | file = file.findOrCreateChildDir(file, dir)
190 | }
191 | }
192 | return@let file
193 | }
194 | }!!
195 | }
196 |
197 | private fun VirtualFile.findOrCreateChildDir(requestor: Any, name: String): VirtualFile {
198 | val child = findChild(name)
199 | return child ?: createChildDirectory(requestor, name)
200 | }
201 |
202 | /**
203 | * 获取需要生成的文件 如果没有则会创建文件
204 | */
205 | fun getGeneratedFile(config: ModulePubSpecConfig): VirtualFile {
206 | return getGeneratedFilePath(config).let {
207 | val configName = getGeneratedFileName(config)
208 | return@let it.findOrCreateChildData(
209 | it,
210 | "$configName.dart"
211 | )
212 | }
213 | }
214 |
215 | fun getGeneratedFileName(config: ModulePubSpecConfig): String =
216 | readSetting(config, Constants.KEY_OUTPUT_FILENAME) as? String ?: PluginSetting.instance.fileName
217 | ?: Constants.DEFAULT_CLASS_NAME.lowercase()
218 |
219 | }
220 |
221 | /**
222 | * 模块Flutter配置信息
223 | */
224 | data class ModulePubSpecConfig(
225 | val module: Module,
226 | val pubRoot: PubRoot,
227 | val assetVFiles: List,
228 | val map: Map,
229 | val isFlutterModule: Boolean = FlutterModuleUtils.isFlutterModule(module)
230 | ) {
231 | fun getLeadingWithPackageNameIfChecked(): String {
232 | if (FileHelperNew.isWithLeadingWithPackageName(this)) {
233 | return "packages/${map["name"]}/"
234 | }
235 | return "";
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/utils/Messages.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.utils
2 |
3 | import java.util.*
4 |
5 |
6 | fun message(key: String): String {
7 | val l = if (Locale.getDefault().language.equals("us") or Locale.getDefault().language.equals("en")) "" else "_" + Locale.getDefault().language
8 | return ResourceBundle.getBundle("messages.MessagesBundle$l").getString(key)
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/utils/PluginUtils.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.utils
2 |
3 | import com.intellij.notification.NotificationDisplayType
4 | import com.intellij.notification.NotificationGroup
5 | import com.intellij.notification.NotificationType
6 | import com.intellij.notification.Notifications
7 | import com.intellij.openapi.application.ApplicationManager
8 | import com.intellij.openapi.fileEditor.FileEditorManager
9 | import com.intellij.openapi.fileEditor.OpenFileDescriptor
10 | import com.intellij.openapi.vfs.VirtualFile
11 | import com.intellij.psi.PsiElement
12 |
13 | object PluginUtils {
14 |
15 | @JvmStatic
16 | fun showNotify(message: String?) {
17 | val notificationGroup = NotificationGroup("FlutterAssetsGenerator", NotificationDisplayType.BALLOON, true)
18 | ApplicationManager.getApplication().invokeLater {
19 | val notification = notificationGroup.createNotification(message!!, NotificationType.INFORMATION)
20 | Notifications.Bus.notify(notification)
21 | }
22 | }
23 |
24 | @JvmStatic
25 | fun showError(message: String?) {
26 | val notificationGroup = NotificationGroup("FlutterAssetsGenerator", NotificationDisplayType.BALLOON, true)
27 | ApplicationManager.getApplication().invokeLater {
28 | val notification = notificationGroup.createNotification(message!!, NotificationType.ERROR)
29 | Notifications.Bus.notify(notification)
30 | }
31 | }
32 |
33 | /**
34 | * 转换小写驼峰式
35 | */
36 | fun String.toLowCamelCase(regex: Regex): String {
37 | return if (this.isEmpty()) {
38 | this
39 | } else {
40 | val newStr = this.replace(Regex("[@]"), "")
41 | val split = newStr.split(regex)
42 | val sb = StringBuilder()
43 | for (i in split.indices) {
44 | if (i == 0) {
45 | sb.append(split[i].lowerCaseFirst())
46 | } else {
47 | sb.append(split[i].upperCaseFirst())
48 | }
49 | }
50 | return sb.toString()
51 | }
52 | }
53 |
54 | /**
55 | * 新窗口打开文件
56 | */
57 | fun PsiElement.openFile(vFile: VirtualFile) {
58 | FileEditorManager.getInstance(project)
59 | .openTextEditor(OpenFileDescriptor(project, vFile), true)
60 | }
61 |
62 | fun String.lowerCaseFirst(): String {
63 | return if (this.isEmpty()) {
64 | this
65 | } else {
66 | "${this[0].lowercase()}${this.subSequence(1, this.length)}"
67 | }
68 | }
69 |
70 | /**
71 | * 首字母大写
72 | */
73 | fun String.upperCaseFirst(): String {
74 | return if (this.isEmpty()) {
75 | this
76 | } else {
77 | "${this[0].uppercase()}${this.subSequence(1, this.length)}"
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/src/main/java/com/crzsc/plugin/utils/StringExt.kt:
--------------------------------------------------------------------------------
1 | package com.crzsc.plugin.utils
2 |
3 | /**
4 | * 是svg拓展名
5 | */
6 | val String.isSvgExtension: Boolean
7 | get() = endsWith(".svg", true)
8 |
9 | /**
10 | * 是Image拓展名
11 | */
12 | val String.isImageExtension: Boolean
13 | get() = endsWith(".png", true)
14 | || endsWith(".jpg", true)
15 | || endsWith(".webp", true)
16 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | com.crzsc.FlutterAssetsGenerator
3 | FlutterAssetsGenerator
4 | Ray&Zsc
5 |
6 |
8 | com.intellij.modules.json
9 | com.intellij.modules.lang
10 |
11 | com.intellij.modules.all
12 | com.intellij.modules.platform
13 | Dart
14 | org.jetbrains.kotlin
15 | io.flutter
16 |
17 |
18 |
19 |
20 |
23 |
24 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
35 |
37 |
38 |
39 |
40 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/pluginIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
102 |
--------------------------------------------------------------------------------
/src/main/resources/messages/MessagesBun_zh.properties:
--------------------------------------------------------------------------------
1 | notFlutterProject=该项目不是flutter项目
2 | settingsAutoDetection=启用自动检测
3 | settingsNamed=继承命名
4 | settingsFilePath=输入生成的文件路径:
5 | settingsFileName=输入生成的文件名:
6 | settingsClassName=输入生成的类名:
7 | settingsLeadingWithPackageName=以包名开头
8 | settingsSplitPattern=输入文件名分割模式:
--------------------------------------------------------------------------------
/src/main/resources/messages/MessagesBundle.properties:
--------------------------------------------------------------------------------
1 | notFlutterProject=This project is not the flutter project
2 | settingsAutoDetection=Enable auto-detection
3 | settingsNamed=Named with parent
4 | settingsFilePath=Enter generated file path:
5 | settingsFileName=Enter generated file name:
6 | settingsClassName=Enter generated class name:
7 | settingsLeadingWithPackageName=Enable leading with package name
8 | settingsSplitPattern=Enter filename split pattern:
9 |
--------------------------------------------------------------------------------