├── .github
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── .gitignore
├── .run
├── buildPlugin.run.xml
├── clean.run.xml
├── runIde.run.xml
└── runRider.run.xml
├── LICENSE
├── README.md
├── build.gradle.kts
├── clion
├── build.gradle.kts
└── src
│ └── main
│ ├── kotlin
│ └── com
│ │ └── nasller
│ │ └── codeglance
│ │ └── extensions
│ │ └── visitor
│ │ ├── MarkClionRdVisitor.kt
│ │ └── MarkClionVisitor.kt
│ └── resources
│ └── META-INF
│ ├── plugin-clion-rd.xml
│ └── plugin-clion.xml
├── core
├── build.gradle.kts
└── src
│ └── main
│ ├── kotlin
│ ├── MyRainbowVisitor.kt
│ └── com
│ │ └── nasller
│ │ └── codeglance
│ │ ├── config
│ │ ├── CodeGlanceColorsPage.kt
│ │ ├── CodeGlanceConfig.kt
│ │ ├── CodeGlanceConfigService.kt
│ │ ├── CodeGlanceConfigurable.kt
│ │ └── enums
│ │ │ ├── BaseEnum.kt
│ │ │ ├── ClickTypeEnum.kt
│ │ │ ├── EditorSizeEnum.kt
│ │ │ └── MouseJumpEnum.kt
│ │ ├── ui
│ │ ├── ColorButton.kt
│ │ └── DonationDialog.kt
│ │ └── util
│ │ ├── CodeGlanceBundle.kt
│ │ ├── CodeGlanceIcons.kt
│ │ ├── MySoftReference.kt
│ │ ├── MyVisualLinesIterator.kt
│ │ └── Util.kt
│ ├── python
│ ├── CharacterWeight.py
│ └── cour.ttf
│ └── resources
│ ├── colorSchemes
│ ├── color-default-darcula.xml
│ └── color-default.xml
│ ├── image
│ ├── ali_pay.png
│ ├── paypal.png
│ └── wechat_pay.png
│ └── messages
│ ├── CodeGlanceBundle.properties
│ └── CodeGlanceBundle_zh.properties
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── rider
├── build.gradle.kts
└── src
│ └── main
│ ├── kotlin
│ └── com
│ │ └── nasller
│ │ └── codeglance
│ │ └── extensions
│ │ └── visitor
│ │ └── MarkRiderVisitor.kt
│ └── resources
│ └── META-INF
│ └── plugin-rider.xml
├── settings.gradle.kts
└── src
└── main
├── java
└── com
│ └── nasller
│ └── codeglance
│ └── agent
│ ├── Interceptor.java
│ ├── Main.java
│ └── MyAppLifecycleListener.java
├── kotlin
└── com
│ └── nasller
│ └── codeglance
│ ├── EditorPanelInjector.kt
│ ├── actions
│ ├── DisableByDefaultAction.kt
│ └── ToggleVisibleAction.kt
│ ├── extensions
│ ├── GlanceVisibleActionProvider.kt
│ └── visitor
│ │ ├── MarkCommentVisitor.kt
│ │ ├── MarkDartVisitor.kt
│ │ ├── MarkJavaVisitor.kt
│ │ ├── MarkKotlinVisitor.kt
│ │ └── MarkScalaVisitor.kt
│ ├── listener
│ ├── GlanceListener.kt
│ ├── HideScrollBarListener.kt
│ └── MyVcsListener.kt
│ ├── panel
│ ├── GlancePanel.kt
│ ├── scroll
│ │ ├── CustomEditorFragmentRenderer.kt
│ │ ├── CustomScrollBarPopup.kt
│ │ └── ScrollBar.kt
│ └── vcs
│ │ └── MyVcsPanel.kt
│ └── render
│ ├── BaseMinimap.kt
│ ├── CharacterWeight.kt
│ ├── EmptyMinimap.kt
│ ├── FastMainMinimap.kt
│ ├── MainMinimap.kt
│ ├── MarkState.kt
│ └── ScrollState.kt
└── resources
├── META-INF
├── plugin-dart.xml
├── plugin-java.xml
├── plugin-kotlin.xml
├── plugin-scala.xml
├── plugin.xml
└── pluginIcon.svg
└── icons
├── glanceHide.svg
├── glanceHide_dark.svg
├── glanceShow.svg
└── glanceShow_dark.svg
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: Nasller
7 |
8 | ---
9 |
10 | 1. Which Glance version and IDEA version?
11 |
12 | 2. Can you show the complete config page picture and minimap picture?
13 |
14 | 3. What is the file type used and provide text or an example of the issue that occurred?
15 |
16 | 4. What steps will reproduce the issue?
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /.gradle
3 | /.kotlin
4 | /**/build
5 | /**/out
6 | /lib
7 | /.intellijPlatform
8 | *.iml
9 | *.ipr
10 | *.iws
11 | /bin
12 | idea-sandbox
13 | rider-sandbox
14 | clion-sandbox
15 | py-sandbox
16 | php-sandbox
--------------------------------------------------------------------------------
/.run/buildPlugin.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
16 |
21 |
22 |
23 | true
24 | true
25 | false
26 |
27 |
28 |
--------------------------------------------------------------------------------
/.run/clean.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | true
19 | true
20 | false
21 | false
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.run/runIde.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | true
19 | true
20 | false
21 | false
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.run/runRider.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | true
19 | true
20 | false
21 | false
22 |
23 |
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CodeGlance Pro
2 |
3 | [](https://plugins.jetbrains.com/plugin/18824-codeglance-pro)
4 | [](https://plugins.jetbrains.com/plugin/18824-codeglance-pro)
5 |
6 | #### Main differences compared to CodeGlance
7 | - Hide original scrollbar
8 | - Right click to quick config
9 | - Support markup highlights
10 | - Support error stripes highlights
11 | - Support Vcs line highlights
12 | - Support caret line highlights
13 | - Support language ColorScheme
14 | - Quick view code on Glance
15 | - Automatically calculate width in splitter mode
16 |
17 | Show/Hide or Enable/Disable Minimap
18 | ===================
19 | * **Ctrl-Shift-G** to toggle glance.
20 | * Settings > Other Settings > CodeGlance Pro
21 |
22 | ## EAP Version
23 | In case you are using an EAP version of any IDEA flavor,
24 | just add the EAP channel: `https://plugins.jetbrains.com/plugins/eap/18824` or `https://plugins.jetbrains.com/plugins/eap/list`.
25 | See JetBrains documentation for more details: https://www.jetbrains.com/help/idea/managing-plugins.html#repos
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
2 | import java.time.LocalDateTime
3 | import java.time.format.DateTimeFormatter
4 |
5 | val env: MutableMap = System.getenv()
6 | val dir: String = projectDir.parentFile.absolutePath
7 | fun properties(key: String) = providers.gradleProperty(key)
8 |
9 | plugins {
10 | id("java")
11 | alias(libs.plugins.kotlin)
12 | alias(libs.plugins.gradleIntelliJPlugin)
13 | }
14 |
15 | group = properties("pluginGroup").get()
16 | version = properties("pluginVersion").get() + if(env.getOrDefault("snapshots","") == "true") "-SNAPSHOT"
17 | else if(env.getOrDefault("PUBLISH_CHANNEL","") == "EAP") "-SNAPSHOT-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm")) else ""
18 |
19 | dependencies {
20 | implementation("net.bytebuddy:byte-buddy:1.15.10")
21 | implementation("net.bytebuddy:byte-buddy-agent:1.15.10")
22 | intellijPlatform {
23 | create(properties("platformType"), properties("platformVersion"))
24 | pluginModule(implementation(project(":core")))
25 | pluginModule(runtimeOnly(project(":rider")))
26 | pluginModule(runtimeOnly(project(":clion")))
27 |
28 | // Plugin Dependencies. Uses `platformBundledPlugins` property from the gradle.properties file for bundled IntelliJ Platform plugins.
29 | bundledPlugins(properties("platformBundledPlugins").map { it.split(',') })
30 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file for plugin from JetBrains Marketplace.
31 | plugins(properties("platformPlugins").map { it.split(',') })
32 |
33 | zipSigner()
34 | }
35 | }
36 |
37 | kotlin {
38 | jvmToolchain(properties("javaVersion").get().toInt())
39 | }
40 |
41 | repositories {
42 | mavenCentral()
43 | // IntelliJ Platform Gradle Plugin Repositories Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-repositories-extension.html
44 | intellijPlatform {
45 | defaultRepositories()
46 | }
47 | }
48 |
49 | intellijPlatform {
50 | pluginConfiguration {
51 | name = properties("pluginName").get()
52 | version = project.version.toString()
53 |
54 | ideaVersion {
55 | sinceBuild = properties("pluginSinceBuild")
56 | untilBuild = properties("pluginUntilBuild")
57 | }
58 | }
59 | sandboxContainer = layout.projectDirectory.dir(properties("sandboxDir").get())
60 |
61 | signing {
62 | certificateChainFile.set(File(env.getOrDefault("CERTIFICATE_CHAIN", "$dir/pluginCert/chain.crt")))
63 | privateKeyFile.set(File(env.getOrDefault("PRIVATE_KEY", "$dir/pluginCert/private.pem")))
64 | password.set(File(env.getOrDefault("PRIVATE_KEY_PASSWORD", "$dir/pluginCert/password.txt")).readText(Charsets.UTF_8))
65 | }
66 |
67 | publishing {
68 | token.set(env["PUBLISH_TOKEN"])
69 | channels.set(listOf(env["PUBLISH_CHANNEL"] ?: "default"))
70 | }
71 | }
72 |
73 | intellijPlatformTesting {
74 | runIde {
75 | register("runRider") {
76 | type = IntelliJPlatformType.Rider
77 | version = properties("riderPlatformVersion")
78 | sandboxDirectory = project.layout.projectDirectory.dir(properties("riderSandboxDir").get())
79 | plugins {
80 | plugins(properties("defaultPlugins").map { it.split(',') })
81 | }
82 | task {
83 | systemProperties["idea.is.internal"] = true
84 | jvmArgs(
85 | "-XX:+AllowEnhancedClassRedefinition",
86 | )
87 | }
88 | }
89 | register("runClion") {
90 | type = IntelliJPlatformType.CLion
91 | version = properties("clionPlatformVersion")
92 | sandboxDirectory = project.layout.projectDirectory.dir(properties("clionSandboxDir").get())
93 | plugins {
94 | plugins(properties("defaultPlugins").map { it.split(',') })
95 | }
96 | task {
97 | systemProperties["idea.is.internal"] = true
98 | jvmArgs(
99 | "-XX:+AllowEnhancedClassRedefinition",
100 | )
101 | }
102 | }
103 | }
104 | }
105 |
106 | tasks{
107 | runIde {
108 | systemProperties["idea.is.internal"] = true
109 | systemProperties["idea.kotlin.plugin.use.k2"] = true
110 | jvmArgs(
111 | "-XX:+AllowEnhancedClassRedefinition",
112 | )
113 | // Path to IDE distribution that will be used to run the IDE with the plugin.
114 | // ideDir.set(File("path to IDE-dependency"))
115 | }
116 |
117 | composedJar {
118 | manifest {
119 | attributes["Built-By"] = "Nasller"
120 | attributes["Premain-Class"] = "com.nasller.codeglance.agent.Main"
121 | attributes["Agent-Class"] = "com.nasller.codeglance.agent.Main"
122 | attributes["Can-Redefine-Classes"] = true
123 | attributes["Can-Retransform-Classes"] = true
124 | }
125 | }
126 |
127 | wrapper {
128 | gradleVersion = properties("gradleVersion").get()
129 | distributionType = Wrapper.DistributionType.ALL
130 | }
131 | }
--------------------------------------------------------------------------------
/clion/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.intellij.platform.module")
3 | alias(libs.plugins.kotlin)
4 | }
5 |
6 | repositories {
7 | mavenCentral()
8 |
9 | intellijPlatform {
10 | defaultRepositories()
11 | }
12 | }
13 |
14 | dependencies {
15 | intellijPlatform {
16 | clion(providers.gradleProperty("clionPlatformVersion"))
17 | pluginModule(implementation(project(":core")))
18 | bundledPlugins("com.intellij.cidr.lang", "org.jetbrains.plugins.clion.radler")
19 | }
20 | }
--------------------------------------------------------------------------------
/clion/src/main/kotlin/com/nasller/codeglance/extensions/visitor/MarkClionRdVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.extensions.visitor
2 |
3 | import MyRainbowVisitor
4 | import com.intellij.codeInsight.daemon.impl.HighlightVisitor
5 | import com.intellij.lang.Language
6 | import com.intellij.psi.PsiElement
7 | import com.intellij.psi.util.elementType
8 | import com.jetbrains.rider.cpp.fileType.CppLanguage
9 | import com.jetbrains.rider.cpp.fileType.lexer.CppTokenTypes
10 | import com.nasller.codeglance.util.Util
11 |
12 | class MarkClionRdVisitor : MyRainbowVisitor() {
13 | override fun visit(element: PsiElement) {
14 | if(element.elementType == CppTokenTypes.PRAGMA_DIRECTIVE){
15 | val lastChild = element.parent.lastChild
16 | if(lastChild.elementType == CppTokenTypes.IDENTIFIER){
17 | visitText(lastChild.text, lastChild.textRange, Util.MARK_CLION_REGION_ATTRIBUTES)
18 | }
19 | }
20 | }
21 |
22 | override fun suitableForFile(language: Language) = language is CppLanguage
23 |
24 | override fun clone(): HighlightVisitor = MarkClionRdVisitor()
25 | }
--------------------------------------------------------------------------------
/clion/src/main/kotlin/com/nasller/codeglance/extensions/visitor/MarkClionVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.extensions.visitor
2 |
3 | import MyRainbowVisitor
4 | import com.intellij.codeInsight.daemon.impl.HighlightVisitor
5 | import com.intellij.lang.Language
6 | import com.intellij.psi.PsiElement
7 | import com.intellij.psi.util.elementType
8 | import com.jetbrains.cidr.lang.OCLanguage
9 | import com.jetbrains.cidr.lang.parser.OCLexerTokenTypes
10 | import com.nasller.codeglance.util.Util
11 |
12 | class MarkClionVisitor : MyRainbowVisitor() {
13 | override fun visit(element: PsiElement) {
14 | if(element.elementType == OCLexerTokenTypes.PRAGMA_DIRECTIVE){
15 | val lastChild = element.parent.lastChild
16 | if(lastChild.elementType == OCLexerTokenTypes.IDENTIFIER){
17 | visitText(lastChild.text, lastChild.textRange, Util.MARK_CLION_REGION_ATTRIBUTES)
18 | }
19 | }
20 | }
21 |
22 | override fun suitableForFile(language: Language) = language is OCLanguage
23 |
24 | override fun clone(): HighlightVisitor = MarkClionVisitor()
25 | }
--------------------------------------------------------------------------------
/clion/src/main/resources/META-INF/plugin-clion-rd.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/clion/src/main/resources/META-INF/plugin-clion.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.intellij.platform.module")
3 | alias(libs.plugins.kotlin)
4 | }
5 |
6 | repositories {
7 | mavenCentral()
8 |
9 | intellijPlatform {
10 | defaultRepositories()
11 | }
12 | }
13 |
14 | dependencies {
15 | intellijPlatform {
16 | intellijIdeaUltimate(providers.gradleProperty("platformVersion"))
17 | }
18 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/MyRainbowVisitor.kt:
--------------------------------------------------------------------------------
1 | import com.intellij.codeHighlighting.RainbowHighlighter
2 | import com.intellij.codeInsight.daemon.impl.HighlightInfo
3 | import com.intellij.codeInsight.daemon.impl.HighlightVisitor
4 | import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder
5 | import com.intellij.lang.Language
6 | import com.intellij.openapi.editor.colors.TextAttributesKey
7 | import com.intellij.openapi.util.TextRange
8 | import com.intellij.psi.PsiFile
9 | import com.intellij.psi.PsiNameIdentifierOwner
10 | import com.nasller.codeglance.config.CodeGlanceConfigService
11 | import com.nasller.codeglance.util.Util
12 |
13 | var MARK_REGEX = CodeGlanceConfigService.Config.markRegex.run {
14 | if(isNotBlank()) Regex(this) else null
15 | }
16 |
17 | /**
18 | * Avoid report errors.
19 | * This isn't the error made by this plugin. It's the error of SDK.
20 | */
21 | abstract class MyRainbowVisitor : HighlightVisitor {
22 | private var myHolder: HighlightInfoHolder? = null
23 |
24 | abstract fun suitableForFile(language: Language): Boolean
25 |
26 | override fun suitableForFile(file: PsiFile): Boolean {
27 | val config = CodeGlanceConfigService.Config
28 | return config.enableMarker && (suitableForFile(file.language) && file.fileType.defaultExtension.let { (it.isBlank() ||
29 | config.disableLanguageSuffix.split(",").toSet().contains(it).not()) })
30 | }
31 |
32 | override fun analyze(file: PsiFile, updateWholeFile: Boolean, holder: HighlightInfoHolder, action: Runnable): Boolean {
33 | myHolder = holder
34 | try {
35 | action.run()
36 | } finally {
37 | myHolder = null
38 | }
39 | return true
40 | }
41 |
42 | protected fun addInfo(highlightInfo: HighlightInfo?) {
43 | myHolder!!.add(highlightInfo)
44 | }
45 |
46 | protected fun getInfo(start: Int, end: Int, colorKey: TextAttributesKey): HighlightInfo? {
47 | return HighlightInfo
48 | .newHighlightInfo(RainbowHighlighter.RAINBOW_ELEMENT)
49 | .textAttributes(colorKey)
50 | .unescapedToolTip("Mark attribute")
51 | .range(start,end).create()
52 | }
53 |
54 | protected fun visitPsiNameIdentifier(element: PsiNameIdentifierOwner) {
55 | val psiIdentifier = element.nameIdentifier ?: return
56 | visitText(psiIdentifier.text, psiIdentifier.textRange, Util.MARK_CLASS_ATTRIBUTES)
57 | }
58 |
59 | protected fun visitText(text: String, textRange: TextRange, textAttributesKey: TextAttributesKey) {
60 | if (text.isNotBlank()) {
61 | addInfo(getInfo(textRange.startOffset, textRange.endOffset, textAttributesKey))
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/config/CodeGlanceColorsPage.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.config
2 |
3 | import com.intellij.openapi.fileTypes.FileTypes
4 | import com.intellij.openapi.fileTypes.PlainSyntaxHighlighter
5 | import com.intellij.openapi.options.colors.AttributesDescriptor
6 | import com.intellij.openapi.options.colors.ColorDescriptor
7 | import com.intellij.openapi.options.colors.ColorSettingsPage
8 | import com.intellij.psi.codeStyle.DisplayPriority
9 | import com.intellij.psi.codeStyle.DisplayPrioritySortable
10 | import com.nasller.codeglance.util.Util
11 | import javax.swing.Icon
12 |
13 | class CodeGlanceColorsPage : ColorSettingsPage, DisplayPrioritySortable {
14 | override fun getAttributeDescriptors() = arrayOf(
15 | AttributesDescriptor("Class name", Util.MARK_CLASS_ATTRIBUTES),
16 | AttributesDescriptor("Mark comment", Util.MARK_COMMENT_ATTRIBUTES),
17 | AttributesDescriptor("Rider region", Util.MARK_RIDER_REGION_ATTRIBUTES),
18 | AttributesDescriptor("Clion region", Util.MARK_CLION_REGION_ATTRIBUTES),
19 | )
20 |
21 | override fun getColorDescriptors(): Array = emptyArray()
22 |
23 | override fun getDisplayName() = Util.PLUGIN_NAME
24 |
25 | override fun getIcon(): Icon = FileTypes.PLAIN_TEXT.icon
26 |
27 | override fun getHighlighter() = PlainSyntaxHighlighter()
28 |
29 | override fun getDemoText() = """
30 | class MyClass {}
31 | //This is a comment
32 | #region Rider
33 | #pragma region Clion
34 | """.trimIndent()
35 |
36 | override fun getAdditionalHighlightingTagToDescriptorMap() = mapOf(
37 | Pair("class", Util.MARK_CLASS_ATTRIBUTES),
38 | Pair("mark", Util.MARK_COMMENT_ATTRIBUTES),
39 | Pair("rider", Util.MARK_RIDER_REGION_ATTRIBUTES),
40 | Pair("clion", Util.MARK_CLION_REGION_ATTRIBUTES),
41 | )
42 |
43 | override fun getPriority() = DisplayPriority.OTHER_SETTINGS
44 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/config/CodeGlanceConfig.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.config
2 |
3 | import com.intellij.openapi.application.ApplicationManager
4 | import com.intellij.openapi.components.BaseState
5 | import com.intellij.openapi.editor.EditorKind
6 | import com.intellij.util.messages.Topic
7 | import com.nasller.codeglance.config.enums.ClickTypeEnum
8 | import com.nasller.codeglance.config.enums.EditorSizeEnum
9 | import com.nasller.codeglance.config.enums.MouseJumpEnum
10 |
11 | class CodeGlanceConfig : BaseState() {
12 | var pixelsPerLine by property(4)
13 | var editorSize by enum(EditorSizeEnum.Proportional)
14 | var minLinesCount by property(0)
15 | var maxLinesCount by property(100000)
16 | var disabled by property(false)
17 | var singleFileVisibleButton by property(true)
18 | var hideOriginalScrollBar by property(false)
19 | var autoCalWidthInSplitterMode by property(true)
20 | var showEditorToolTip by property(true)
21 | var mouseWheelMoveEditorToolTip by property(false)
22 | var isRightAligned by property(true)
23 | var hoveringToShowScrollBar by property(false)
24 | var delayHoveringToShowScrollBar by property(0)
25 | var clickType by enum(ClickTypeEnum.CODE_POSITION)
26 | var jumpOnMouseDown by enum(MouseJumpEnum.MOUSE_DOWN)
27 | var moveOnly by property(false)
28 | var disableLanguageSuffix by nonNullString("ipynb")
29 | var clean by property(true)
30 | var locked by property(false)
31 |
32 | /** Viewport */
33 | var viewportColor by nonNullString("A0A0A0")
34 | var viewportBorderColor by nonNullString("00FF00")
35 | var viewportBorderThickness by property(0)
36 | /** Highlight */
37 | var showFilterMarkupHighlight by property(true)
38 | var showMarkupHighlight by property(true)
39 | var showVcsHighlight by property(true)
40 | var showErrorStripesFullLineHighlight by property(true)
41 | var showOtherFullLineHighlight by property(false)
42 | var syntaxHighlight by property(true)
43 | /** Mark */
44 | var enableMarker by property(true)
45 | var enableBookmarksMark by property(true)
46 | var markRegex by nonNullString("\\b(MARK: - )\\b|\\b(MARK: )\\b|(?:region \\b)")
47 | var markersScaleFactor by property(3.0f)
48 |
49 | var diffTwoSide by property(true)
50 | var diffThreeSide by property(true)
51 | var diffThreeSideMiddle by property(false)
52 | var editorKindsStr by nonNullString("${EditorKind.MAIN_EDITOR},${EditorKind.PREVIEW},${EditorKind.DIFF}")
53 | var useEmptyMinimapStr by nonNullString(EditorKind.CONSOLE.name)
54 | var mainWidth by property(110)
55 | var diffWidth by property(50)
56 | var unTypedWidth by property(50)
57 | var consoleWidth by property(50)
58 | var previewWidth by property(50)
59 | var useFastMinimapForMain by property(true)
60 |
61 | fun singleFileVisibleButton() = !hoveringToShowScrollBar && singleFileVisibleButton
62 |
63 | private fun nonNullString(initialValue: String = "") = property(initialValue) { it == initialValue }
64 |
65 | companion object{
66 | fun EditorKind.getWidth() = when(this){
67 | EditorKind.UNTYPED -> CodeGlanceConfigService.Config.unTypedWidth
68 | EditorKind.CONSOLE -> CodeGlanceConfigService.Config.consoleWidth
69 | EditorKind.PREVIEW -> CodeGlanceConfigService.Config.previewWidth
70 | EditorKind.DIFF -> CodeGlanceConfigService.Config.diffWidth
71 | else -> CodeGlanceConfigService.Config.mainWidth
72 | }
73 |
74 | fun EditorKind.setWidth(value: Int) {
75 | when (this) {
76 | EditorKind.UNTYPED -> CodeGlanceConfigService.Config.unTypedWidth = value
77 | EditorKind.CONSOLE -> CodeGlanceConfigService.Config.consoleWidth = value
78 | EditorKind.PREVIEW -> CodeGlanceConfigService.Config.previewWidth = value
79 | EditorKind.DIFF -> CodeGlanceConfigService.Config.diffWidth = value
80 | else -> CodeGlanceConfigService.Config.mainWidth = value
81 | }
82 | }
83 | }
84 | }
85 |
86 | val SettingsChangePublisher: SettingsChangeListener = ApplicationManager.getApplication().messageBus.syncPublisher(SettingsChangeListener.TOPIC)
87 |
88 | interface SettingsChangeListener {
89 | fun onHoveringOriginalScrollBarChanged(value: Boolean) {}
90 |
91 | fun refreshDataAndImage() {}
92 |
93 | fun onGlobalChanged() {}
94 |
95 | companion object {
96 | val TOPIC = Topic.create("CodeGlanceSettingsChanged", SettingsChangeListener::class.java)
97 | }
98 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/config/CodeGlanceConfigService.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.config
2 |
3 | import com.intellij.openapi.application.ApplicationManager
4 | import com.intellij.openapi.components.SimplePersistentStateComponent
5 | import com.intellij.openapi.components.State
6 | import com.intellij.openapi.components.Storage
7 | import com.nasller.codeglance.util.Util
8 | import kotlinx.coroutines.CoroutineScope
9 |
10 | @State(name = Util.PLUGIN_NAME, storages = [Storage("CodeGlancePro.xml")])
11 | class CodeGlanceConfigService(val coroutineScope: CoroutineScope) : SimplePersistentStateComponent(CodeGlanceConfig()) {
12 | companion object {
13 | val Config by lazy { ApplicationManager.getApplication().getService(CodeGlanceConfigService::class.java).state }
14 | }
15 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/config/CodeGlanceConfigurable.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.config
2 |
3 | import MARK_REGEX
4 | import com.intellij.openapi.application.invokeLater
5 | import com.intellij.openapi.editor.EditorKind
6 | import com.intellij.openapi.options.BoundSearchableConfigurable
7 | import com.intellij.openapi.ui.ComboBox
8 | import com.intellij.openapi.ui.DialogPanel
9 | import com.intellij.ui.ColorUtil
10 | import com.intellij.ui.JBColor
11 | import com.intellij.ui.components.JBCheckBox
12 | import com.intellij.ui.dsl.builder.*
13 | import com.intellij.ui.tabs.ColorButtonBase
14 | import com.intellij.util.ui.JBUI
15 | import com.nasller.codeglance.config.CodeGlanceConfig.Companion.getWidth
16 | import com.nasller.codeglance.config.CodeGlanceConfig.Companion.setWidth
17 | import com.nasller.codeglance.config.enums.BaseEnum
18 | import com.nasller.codeglance.config.enums.ClickTypeEnum
19 | import com.nasller.codeglance.config.enums.EditorSizeEnum
20 | import com.nasller.codeglance.config.enums.MouseJumpEnum
21 | import com.nasller.codeglance.ui.ColorButton
22 | import com.nasller.codeglance.ui.DonationDialog
23 | import com.nasller.codeglance.util.Util
24 | import com.nasller.codeglance.util.localMessage
25 | import com.nasller.codeglance.util.message
26 | import java.awt.Component
27 | import java.awt.Dimension
28 | import java.awt.event.InputEvent
29 | import java.awt.event.MouseWheelEvent
30 | import javax.swing.*
31 | import kotlin.math.max
32 | import kotlin.math.min
33 |
34 | class CodeGlanceConfigurable : BoundSearchableConfigurable(Util.PLUGIN_NAME,"com.nasller.CodeGlancePro"){
35 | private val editorKinds = mutableSetOf()
36 | private val useEmptyMinimap = mutableSetOf()
37 | private lateinit var editorKindComboBox: ComboBox
38 | private lateinit var emptyMinimapComboBox: ComboBox
39 |
40 | override fun createPanel(): DialogPanel {
41 | val config = CodeGlanceConfigService.Config
42 | return panel {
43 | group(message("settings.general")) {
44 | twoColumnsRow({
45 | comboBox(listOf(1, 2, 3, 4)).label(message("settings.pixels"))
46 | .bindItem(config::pixelsPerLine.toNullableProperty())
47 | .accessibleName(message("settings.pixels"))
48 | }, {
49 | comboBox(EditorSizeEnum.entries.map { it.name }).label(message("settings.editor.size"))
50 | .bindItem({ config.editorSize.name }, { config.editorSize = enumValueOf(it!!) })
51 | }).bottomGap(BottomGap.SMALL)
52 | twoColumnsRow({
53 | val items = arrayOf("Clean", "Accurate")
54 | comboBox(DefaultComboBoxModel(items)).label(message("settings.render"))
55 | .bindItem({ if (config.clean) items[0] else items[1] },
56 | { config.clean = it == items[0] })
57 | .accessibleName(message("settings.render"))
58 | }, {
59 | val items = listOf(message("settings.alignment.right"), message("settings.alignment.left"))
60 | comboBox(items).label(message("settings.alignment"))
61 | .bindItem({ if (config.isRightAligned) items[0] else items[1] },
62 | { config.isRightAligned = it == items[0] })
63 | .accessibleName(message("settings.alignment"))
64 | }).bottomGap(BottomGap.SMALL)
65 | twoColumnsRow({
66 | comboBox(ClickTypeEnum.entries.map { it.getMessage() }).label(message("settings.click"))
67 | .bindItem({ config.clickType.getMessage() }, { config.clickType = BaseEnum.findEnum(it) })
68 | checkBox(message("settings.click.move")).bindSelected(config::moveOnly)
69 | }, {
70 | comboBox(MouseJumpEnum.entries.map { it.getMessage() }).label(message("settings.jump"))
71 | .bindItem({ config.jumpOnMouseDown.getMessage() }, { config.jumpOnMouseDown = BaseEnum.findEnum(it) })
72 | }).bottomGap(BottomGap.SMALL)
73 | val numberScrollListener: (e: MouseWheelEvent) -> Unit = {
74 | val spinner = it.source as JSpinner
75 | val model = spinner.model as SpinnerNumberModel
76 | var step = model.stepSize.toInt()
77 | when (it.modifiersEx and (InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK)) {
78 | InputEvent.CTRL_DOWN_MASK -> step *= 2
79 | InputEvent.SHIFT_DOWN_MASK -> step /= 2
80 | InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK -> step = 1
81 | }
82 | var newValue: Int = spinner.value as Int + step * -it.wheelRotation
83 | newValue = min(max(newValue, (model.minimum as Int)), (model.maximum as Int))
84 | spinner.value = newValue
85 | }
86 | twoColumnsRow({
87 | spinner(0..Int.MAX_VALUE, 10).label(message("settings.min.line"))
88 | .bindIntValue(config::minLinesCount)
89 | .applyToComponent {
90 | toolTipText = "0 - Int.Max lines"
91 | addMouseWheelListener(numberScrollListener)
92 | }
93 | }, {
94 | spinner(1..Int.MAX_VALUE, 10).label(message("settings.max.line"))
95 | .bindIntValue(config::maxLinesCount)
96 | .applyToComponent {
97 | toolTipText = "1 - Int.Max lines"
98 | addMouseWheelListener(numberScrollListener)
99 | }
100 | }).bottomGap(BottomGap.SMALL)
101 | twoColumnsRow({
102 | editorKindComboBox = comboBox(EditorKind.entries, CheckboxListCellRenderer(editorKinds))
103 | .label(message("settings.editor.kind")).applyToComponent {
104 | isSwingPopup = false
105 | addActionListener {
106 | val kind = editorKindComboBox.item ?: return@addActionListener
107 | if (!editorKinds.remove(kind)) editorKinds.add(kind)
108 | editorKindComboBox.repaint()
109 | }
110 | }.component
111 | }, {
112 | emptyMinimapComboBox = comboBox(EditorKind.entries, CheckboxListCellRenderer(useEmptyMinimap))
113 | .label(message("settings.use.empty.minimap")).applyToComponent {
114 | isSwingPopup = false
115 | addActionListener {
116 | val kind = emptyMinimapComboBox.item ?: return@addActionListener
117 | if (!useEmptyMinimap.remove(kind)) useEmptyMinimap.add(kind)
118 | emptyMinimapComboBox.repaint()
119 | }
120 | }.component
121 | }).bottomGap(BottomGap.SMALL)
122 | row {
123 | spinner(0..2000, 50).label(message("popup.hover.minimap.delay"))
124 | .bindIntValue(config::delayHoveringToShowScrollBar)
125 | .applyToComponent {
126 | toolTipText = "0 - 2000 ms"
127 | addMouseWheelListener(numberScrollListener)
128 | }
129 | @Suppress("DialogTitleCapitalization")
130 | label("ms").gap(RightGap.SMALL)
131 | }.bottomGap(BottomGap.SMALL)
132 | val widthList = EditorKind.entries.chunked(3)
133 | widthList.forEachIndexed { index, it ->
134 | row {
135 | for (kind in it) {
136 | spinner(Util.MIN_WIDTH..Util.MAX_WIDTH, 5).label(kind.getMessageWidth())
137 | .bindIntValue({ kind.getWidth() }, { kind.setWidth(it) })
138 | .applyToComponent {
139 | toolTipText = "30 - 250 pixels"
140 | addMouseWheelListener(numberScrollListener)
141 | }
142 | }
143 | if(widthList.lastIndex == index) {
144 | checkBox(message("settings.width.lock")).bindSelected(config::locked) { config.locked = it }
145 | }
146 | }
147 | }
148 | row {
149 | textField().label(message("settings.disabled.language")).bindText(config::disableLanguageSuffix)
150 | }
151 | }
152 | group(message("settings.viewport")) {
153 | threeColumnsRow({
154 | cell(ColorButton(config.viewportColor, JBColor.WHITE)).label(message("settings.viewport.color"))
155 | .bind({ it.text }, { p: ColorButtonBase, v: String ->
156 | p.setColor(ColorUtil.fromHex(v))
157 | p.text = v
158 | }, config::viewportColor.toMutableProperty())
159 | }, {
160 | cell(ColorButton(config.viewportBorderColor, JBColor.WHITE)).label(message("settings.viewport.border.color"))
161 | .bind({ it.text }, { p: ColorButtonBase, v: String ->
162 | p.setColor(ColorUtil.fromHex(v))
163 | p.text = v
164 | }, config::viewportBorderColor.toMutableProperty())
165 | }, {
166 | comboBox(listOf(0, 1, 2, 3, 4)).label(message("settings.viewport.border.thickness"))
167 | .bindItem(config::viewportBorderThickness.toNullableProperty())
168 | })
169 | }
170 | group(message("settings.mark")) {
171 | twoColumnsRow({
172 | checkBox(message("settings.markers.enable"))
173 | .bindSelected(config::enableMarker)
174 | }, {
175 | checkBox(message("settings.bookmarks.markers.enable"))
176 | .bindSelected(config::enableBookmarksMark)
177 | }).bottomGap(BottomGap.SMALL)
178 | twoColumnsRow({
179 | textField().label(message("settings.markers.regex")).bindText(config::markRegex)
180 | },{
181 | spinner(2.0..10.0, 0.1).label(message("settings.markers.scale"))
182 | .bindValue(getter = { config.markersScaleFactor.toDouble() }, setter = { value: Double -> config.markersScaleFactor = value.toFloat() })
183 | .applyToComponent {
184 | toolTipText = "Scale factor for font of markers in minimap[2 - 10]"
185 | addMouseWheelListener {
186 | val spinner = it.source as JSpinner
187 | val model = spinner.model as SpinnerNumberModel
188 | var step = model.stepSize.toDouble()
189 | when (it.modifiersEx and (InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK)) {
190 | InputEvent.CTRL_DOWN_MASK -> step *= 2
191 | InputEvent.SHIFT_DOWN_MASK -> step /= 2
192 | InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK -> step = 0.5
193 | }
194 | var newValue: Double = spinner.value as Double + step * -it.wheelRotation
195 | newValue = min(max(newValue, (model.minimum as Double)), (model.maximum as Double))
196 | spinner.value = newValue
197 | }
198 | }
199 | })
200 | }
201 | group(message("settings.option")) {
202 | twoColumnsRow({
203 | checkBox(message("settings.disabled"))
204 | .bindSelected(config::disabled)
205 | }, {
206 | checkBox(message("settings.hide.original.scrollbar"))
207 | .bindSelected(config::hideOriginalScrollBar)
208 | })
209 | threeColumnsRow({
210 | checkBox(message("settings.highlight.vcs"))
211 | .bindSelected(config::showVcsHighlight)
212 | }, {
213 | checkBox(message("settings.highlight.filter.markup"))
214 | .bindSelected(config::showFilterMarkupHighlight)
215 | .gap(RightGap.SMALL)
216 | contextHelp(message("settings.highlight.filter.markup.desc"))
217 | }, {
218 | checkBox(message("settings.highlight.markup"))
219 | .bindSelected(config::showMarkupHighlight)
220 | .gap(RightGap.SMALL)
221 | contextHelp(message("settings.highlight.markup.desc"))
222 | })
223 | row {
224 | checkBox(message("settings.highlight.syntax"))
225 | .bindSelected(config::syntaxHighlight)
226 | .gap(RightGap.SMALL)
227 | }
228 | threeColumnsRow({
229 | checkBox(message("settings.two.sides.diff"))
230 | .bindSelected(config::diffTwoSide)
231 | .gap(RightGap.SMALL)
232 | },{
233 | checkBox(message("settings.three.sides.diff"))
234 | .bindSelected(config::diffThreeSide)
235 | .gap(RightGap.SMALL)
236 | },{
237 | checkBox(message("settings.three.sides.middle.diff"))
238 | .bindSelected(config::diffThreeSideMiddle)
239 | .gap(RightGap.SMALL)
240 | })
241 | row {
242 | checkBox(message("settings.experiment.use.fast.minimap.for.main"))
243 | .bindSelected(config::useFastMinimapForMain)
244 | .gap(RightGap.SMALL)
245 | }
246 | }
247 | row {
248 | link(localMessage("donate.title")){
249 | DonationDialog().show()
250 | }.applyToComponent { border = JBUI.Borders.emptyTop(20) }
251 | }
252 | }
253 | }
254 |
255 | override fun apply() {
256 | super.apply()
257 | CodeGlanceConfigService.Config.apply {
258 | editorKindsStr = this@CodeGlanceConfigurable.editorKinds.joinToString(",")
259 | useEmptyMinimapStr = this@CodeGlanceConfigurable.useEmptyMinimap.joinToString(",")
260 | if((!isRightAligned || disabled) && hoveringToShowScrollBar) hoveringToShowScrollBar = false
261 | MARK_REGEX = if(markRegex.isNotBlank()) Regex(markRegex) else null
262 | }
263 | invokeLater{ SettingsChangePublisher.onGlobalChanged() }
264 | }
265 |
266 | override fun isModified(): Boolean {
267 | return super.isModified() ||
268 | editorKinds != CodeGlanceConfigService.Config.editorKindsStr
269 | .split(",").filter{ it.isNotBlank() }.mapTo(mutableSetOf()) { EditorKind.valueOf(it) } ||
270 | useEmptyMinimap != CodeGlanceConfigService.Config.useEmptyMinimapStr
271 | .split(",").filter{ it.isNotBlank() }.mapTo(mutableSetOf()) { EditorKind.valueOf(it) }
272 | }
273 |
274 | override fun reset() {
275 | super.reset()
276 | val config = CodeGlanceConfigService.Config
277 | editorKinds.clear()
278 | editorKinds += config.editorKindsStr.split(",").filter{ it.isNotBlank() }.map { EditorKind.valueOf(it) }
279 | useEmptyMinimap.clear()
280 | useEmptyMinimap += config.useEmptyMinimapStr.split(",").filter{ it.isNotBlank() }.map { EditorKind.valueOf(it) }
281 | editorKindComboBox.repaint()
282 | emptyMinimapComboBox.repaint()
283 | }
284 |
285 | private fun EditorKind.getMessageWidth() = when(this){
286 | EditorKind.UNTYPED -> message("settings.untyped.width")
287 | EditorKind.CONSOLE -> message("settings.console.width")
288 | EditorKind.PREVIEW -> message("settings.preview.width")
289 | EditorKind.DIFF -> message("settings.diff.width")
290 | else -> message("settings.main.width")
291 | }
292 |
293 | private inner class CheckboxListCellRenderer>(private val data: MutableSet) : DefaultListCellRenderer() {
294 | private val container = JPanel(null)
295 | private val checkBox = JBCheckBox()
296 |
297 | init {
298 | isOpaque = false
299 | container.isOpaque = false
300 | checkBox.isOpaque = false
301 |
302 | container.layout = BoxLayout(container, BoxLayout.X_AXIS)
303 | container.add(checkBox)
304 | container.add(this)
305 | preferredSize = Dimension(100, 0)
306 | }
307 |
308 | override fun getListCellRendererComponent(list: JList<*>?,
309 | value: Any?,
310 | index: Int,
311 | isSelected: Boolean,
312 | cellHasFocus: Boolean): Component {
313 | super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
314 | if (index == -1) {
315 | checkBox.isVisible = false
316 | text = data.minOrNull()?.name?.lowercase()?.replaceFirstChar { it.titlecase() } ?: ""
317 | return container
318 | }
319 | text = value.toString().lowercase().replaceFirstChar { it.titlecase() }
320 | checkBox.isVisible = true
321 | checkBox.isSelected = data.contains(value)
322 | return container
323 | }
324 | }
325 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/config/enums/BaseEnum.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.config.enums
2 |
3 | import com.nasller.codeglance.util.message
4 |
5 | interface BaseEnum {
6 | val messageCode: String
7 |
8 | fun getMessage(): String{
9 | return message(messageCode)
10 | }
11 |
12 | companion object{
13 | inline fun > findEnum(message: String?): T{
14 | return enumValues().find { (it as BaseEnum).getMessage() == message }!!
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/config/enums/ClickTypeEnum.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.config.enums
2 |
3 | enum class ClickTypeEnum(override val messageCode:String): BaseEnum{
4 | CODE_POSITION("settings.click.code"),
5 | MOUSE_POSITION("settings.click.mouse"),
6 | ;
7 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/config/enums/EditorSizeEnum.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.config.enums
2 |
3 | enum class EditorSizeEnum {
4 | Proportional, Fit
5 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/config/enums/MouseJumpEnum.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.config.enums
2 |
3 | enum class MouseJumpEnum(override val messageCode:String): BaseEnum{
4 | NONE("settings.jump.none"),
5 | MOUSE_DOWN("settings.jump.down"),
6 | MOUSE_UP("settings.jump.up"),
7 | ;
8 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/ui/ColorButton.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.ui
2 |
3 | import com.intellij.ide.IdeBundle
4 | import com.intellij.ui.ColorPicker
5 | import com.intellij.ui.ColorUtil
6 | import com.intellij.ui.scale.JBUIScale
7 | import com.intellij.ui.tabs.ColorButtonBase
8 | import java.awt.Color
9 | import java.awt.Dimension
10 | import java.awt.event.ActionEvent
11 | import javax.swing.BorderFactory
12 | import javax.swing.plaf.ButtonUI
13 |
14 | class ColorButton(text: String, color: Color) :ColorButtonBase(text, color){
15 | init {
16 | preferredSize = Dimension(75, 25)
17 | border = BorderFactory.createEmptyBorder()
18 | }
19 | override fun doPerformAction(e: ActionEvent) {
20 | ColorPicker.showDialog(this, IdeBundle.message("dialog.title.choose.color"), myColor,
21 | false, emptyList(), false)?.let {
22 | myColor = it
23 | text = ColorUtil.toHex(it)
24 | }
25 | }
26 |
27 | override fun createUI(): ButtonUI {
28 | return object : ColorButtonUI() {
29 | override fun getArcSize(): Int = JBUIScale.scale(10)
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/ui/DonationDialog.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.ui
2 |
3 | import com.intellij.ide.BrowserUtil
4 | import com.intellij.openapi.ui.DialogWrapper
5 | import com.intellij.openapi.ui.Messages
6 | import com.intellij.ui.components.JBLabel
7 | import com.intellij.ui.components.labels.LinkLabel
8 | import com.intellij.ui.components.panels.NonOpaquePanel
9 | import com.intellij.ui.components.panels.VerticalLayout
10 | import com.intellij.ui.scale.JBUIScale
11 | import com.intellij.util.ui.JBUI.Borders
12 | import com.nasller.codeglance.util.CodeGlanceIcons
13 | import com.nasller.codeglance.util.localMessage
14 | import net.miginfocom.layout.CC
15 | import net.miginfocom.swing.MigLayout
16 | import javax.swing.*
17 |
18 | class DonationDialog : DialogWrapper(null) {
19 | init {
20 | title = localMessage("donate.title")
21 | isResizable = false
22 | setOKButtonText(localMessage("donate.thanks"))
23 | init()
24 | }
25 |
26 | override fun createCenterPanel(): JComponent = JPanel(VerticalLayout(JBUIScale.scale(10),SwingConstants.CENTER)).apply {
27 | border = Borders.empty(16)
28 | background = UIManager.getColor("TextArea.background")
29 | add(Messages.configureMessagePaneUi(JTextPane(), localMessage("donate.contribution"))
30 | .apply { background = null })
31 | add(createDonatePanel())
32 | }
33 |
34 | private fun createDonatePanel(): JPanel {
35 | return NonOpaquePanel(MigLayout()).apply {
36 | add(LinkLabel(null, CodeGlanceIcons.loadRoundImageIcon("/image/paypal.png")) { _: LinkLabel, _: Any? ->
37 | BrowserUtil.browse("https://www.paypal.com/paypalme/Nasller")
38 | }, CC().alignX("center").spanX(2).wrap())
39 | val weChatPayLabel = JBLabel(localMessage("donate.wechat")).apply {
40 | horizontalTextPosition = SwingConstants.CENTER
41 | verticalTextPosition = SwingConstants.BOTTOM
42 | icon = CodeGlanceIcons.scaleIcon(CodeGlanceIcons.loadImageIcon("/image/wechat_pay.png"), 0.5f)
43 | }
44 | add(weChatPayLabel)
45 | val aliPayLabel = JBLabel(localMessage("donate.alipay")).apply {
46 | horizontalTextPosition = SwingConstants.CENTER
47 | verticalTextPosition = SwingConstants.BOTTOM
48 | icon = CodeGlanceIcons.scaleIcon(CodeGlanceIcons.loadImageIcon("/image/ali_pay.png"), 0.5f)
49 | }
50 | add(aliPayLabel, CC().wrap())
51 | }
52 | }
53 |
54 | override fun getStyle(): DialogStyle = DialogStyle.COMPACT
55 |
56 | override fun createActions(): Array = arrayOf(okAction)
57 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/util/CodeGlanceBundle.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.util
2 |
3 | import com.intellij.AbstractBundle
4 | import com.intellij.DynamicBundle
5 | import org.jetbrains.annotations.NonNls
6 | import org.jetbrains.annotations.PropertyKey
7 | import java.util.*
8 |
9 | @NonNls
10 | const val BUNDLE: String = "messages.CodeGlanceBundle"
11 |
12 | object CodeGlanceBundle : AbstractBundle(BUNDLE) {
13 | private val adaptedControl = ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_PROPERTIES)
14 |
15 | private val adaptedBundle: AbstractBundle? by lazy {
16 | val dynamicLocale = DynamicBundle.getLocale()
17 | if (dynamicLocale.toLanguageTag() == Locale.ENGLISH.toLanguageTag()) {
18 | object : AbstractBundle(BUNDLE) {
19 | override fun findBundle(pathToBundle: String, loader: ClassLoader, control: ResourceBundle.Control): ResourceBundle {
20 | val dynamicBundle = ResourceBundle.getBundle(pathToBundle, dynamicLocale, loader, adaptedControl)
21 | return dynamicBundle ?: super.findBundle(pathToBundle, loader, control)
22 | }
23 | }
24 | } else null
25 | }
26 |
27 | override fun findBundle(pathToBundle: String, loader: ClassLoader, control: ResourceBundle.Control): ResourceBundle =
28 | DynamicBundle.getLocale().let { ResourceBundle.getBundle(pathToBundle, it, loader, control) }
29 | ?: super.findBundle(pathToBundle, loader, control)
30 |
31 | fun getAdaptedMessage(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): String {
32 | return adaptedBundle?.getMessage(key, *params) ?: getMessage(key, *params)
33 | }
34 | }
35 |
36 | fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): String {
37 | return CodeGlanceBundle.getAdaptedMessage(key, *params)
38 | }
39 |
40 | fun localMessage(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): String {
41 | return CodeGlanceBundle.getMessage(key, *params)
42 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/util/CodeGlanceIcons.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.util
2 |
3 | import com.intellij.ui.IconManager
4 | import com.intellij.ui.RoundedIcon
5 | import com.intellij.util.IconUtil
6 | import com.intellij.util.ImageLoader
7 | import com.intellij.util.ui.JBImageIcon
8 | import javax.swing.Icon
9 |
10 | object CodeGlanceIcons {
11 | val GlanceShow = load("/icons/glanceShow.svg")
12 |
13 | val GlanceHide = load("/icons/glanceHide.svg")
14 |
15 | val Widget = load("/icons/widget.svg")
16 |
17 | private fun load(path: String): Icon {
18 | return IconManager.getInstance().getIcon(path, CodeGlanceIcons::class.java.classLoader)
19 | }
20 |
21 | @Suppress("UnstableApiUsage")
22 | fun loadRoundImageIcon(path: String): RoundedIcon {
23 | return RoundedIcon(ImageLoader.loadFromResource(path, CodeGlanceIcons::class.java)!!,50.0)
24 | }
25 |
26 | fun loadImageIcon(path: String): JBImageIcon {
27 | return JBImageIcon(ImageLoader.loadFromResource(path, CodeGlanceIcons::class.java)!!)
28 | }
29 |
30 | fun scaleIcon(icon: Icon, scale: Float): Icon {
31 | return IconUtil.scale(icon, null, scale)
32 | }
33 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/util/MySoftReference.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.util
2 |
3 | import java.lang.ref.SoftReference
4 |
5 | interface MySoftReference {
6 | fun get(): T?
7 |
8 | fun clear()
9 |
10 | fun clear(action: T.() -> Unit) {
11 | if(this is NoSoftReference) get()?.apply(action)
12 | clear()
13 | }
14 |
15 | companion object{
16 | fun create(referent: T?,useSoft: Boolean): MySoftReference =
17 | if(useSoft) WithSoftReference(referent) else NoSoftReference(referent)
18 | }
19 | }
20 |
21 | private class NoSoftReference(private var referent: T?): MySoftReference{
22 | override fun get(): T? = referent
23 |
24 | override fun clear() { referent = null }
25 | }
26 |
27 | private class WithSoftReference(referent: T?) : SoftReference(referent), MySoftReference
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/util/MyVisualLinesIterator.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.util
2 |
3 | import com.intellij.openapi.editor.FoldRegion
4 | import com.intellij.openapi.editor.Inlay
5 | import com.intellij.openapi.editor.SoftWrap
6 | import com.intellij.openapi.editor.impl.EditorImpl
7 | import com.intellij.openapi.editor.impl.InlayModelImpl
8 |
9 | class MyVisualLinesIterator(private val myEditor: EditorImpl, startVisualLine: Int){
10 | private val myDocument = myEditor.document
11 | private val myFoldRegions = myEditor.foldingModel.fetchTopLevel() ?: FoldRegion.EMPTY_ARRAY
12 | private val mySoftWraps = myEditor.softWrapModel.registeredSoftWraps.toList()
13 |
14 | private val myInlaysAbove = ArrayList>()
15 | private var myInlaySet = false
16 |
17 | private var myLocation = Location(startVisualLine)
18 | private var myNextLocation: Location? = null
19 |
20 | fun atEnd(): Boolean {
21 | return myLocation.atEnd()
22 | }
23 |
24 | fun advance() {
25 | checkEnd()
26 | if (myNextLocation == null) {
27 | myLocation.advance()
28 | } else {
29 | myLocation = myNextLocation!!
30 | myNextLocation = null
31 | }
32 | myInlaySet = false
33 | }
34 |
35 | fun getVisualLine(): Int {
36 | checkEnd()
37 | return myLocation.visualLine
38 | }
39 |
40 | fun getVisualLineStartOffset(): Int {
41 | checkEnd()
42 | return myLocation.offset
43 | }
44 |
45 | fun getVisualLineEndOffset(): Int {
46 | checkEnd()
47 | setNextLocation()
48 | return if (myNextLocation!!.atEnd()) myDocument.textLength
49 | else if (myNextLocation!!.softWrap == myLocation.softWrap) myDocument.getLineEndOffset(myNextLocation!!.logicalLine - 2)
50 | else myNextLocation!!.offset
51 | }
52 |
53 | fun getStartFoldingIndex(): Int {
54 | checkEnd()
55 | return myLocation.foldRegion
56 | }
57 |
58 | fun getStartsWithSoftWrap(): SoftWrap? {
59 | checkEnd()
60 | if(myLocation.softWrap > 0 && myLocation.softWrap <= mySoftWraps.size){
61 | val softWrap = mySoftWraps[myLocation.softWrap - 1]
62 | return if(softWrap.start == myLocation.offset) softWrap else null
63 | }
64 | return null
65 | }
66 |
67 | fun getBlockInlaysAbove(): List> {
68 | checkEnd()
69 | setInlays()
70 | return myInlaysAbove
71 | }
72 |
73 | fun getCurrentFoldRegion(): FoldRegion? {
74 | checkEnd()
75 | val foldIndex = myLocation.foldRegion
76 | if (foldIndex < myFoldRegions.size) {
77 | return myFoldRegions[foldIndex]
78 | }
79 | return null
80 | }
81 |
82 | fun getFoldRegion(foldIndex: Int): FoldRegion? {
83 | checkEnd()
84 | if (foldIndex < myFoldRegions.size) {
85 | return myFoldRegions[foldIndex]
86 | }
87 | return null
88 | }
89 |
90 | private fun checkEnd() {
91 | check(!atEnd()) { "Iteration finished" }
92 | }
93 |
94 | private fun setNextLocation() {
95 | if (myNextLocation == null) {
96 | myNextLocation = myLocation.clone()
97 | myNextLocation!!.advance()
98 | }
99 | }
100 |
101 | private fun setInlays() {
102 | if (myInlaySet) return
103 | myInlaySet = true
104 | myInlaysAbove.clear()
105 | setNextLocation()
106 | val inlays = myEditor.inlayModel
107 | .getBlockElementsInRange(myLocation.offset, if (myNextLocation!!.atEnd()) myDocument.textLength else myNextLocation!!.offset - 1)
108 | for (inlay in inlays) {
109 | val inlayOffset = inlay.offset - if (inlay.isRelatedToPrecedingText) 0 else 1
110 | var foldIndex = myLocation.foldRegion
111 | while (foldIndex < myFoldRegions.size && myFoldRegions[foldIndex].endOffset <= inlayOffset) foldIndex++
112 | if (foldIndex < myFoldRegions.size && myFoldRegions[foldIndex].startOffset <= inlayOffset && !InlayModelImpl.showWhenFolded(inlay)) continue
113 | if (inlay.placement == Inlay.Placement.ABOVE_LINE) myInlaysAbove.add(inlay)
114 | }
115 | }
116 |
117 | private inner class Location(startVisualLine: Int) : Cloneable {
118 | var visualLine = 0 // current visual line
119 | var offset = 0 // start offset of the current visual line
120 | var logicalLine = 1 // 1 + start logical line of the current visual line
121 | var foldRegion = 0 // index of the first folding region on current or following visual lines
122 | var softWrap = 0 // index of the first soft wrap after the start of the current visual line
123 |
124 | init {
125 | if (startVisualLine < 0 || startVisualLine >= myEditor.visibleLineCount) {
126 | offset = -1
127 | } else if (startVisualLine > 0) {
128 | visualLine = startVisualLine
129 | offset = myEditor.visualLineStartOffset(startVisualLine)
130 | logicalLine = myDocument.getLineNumber(offset) + 1
131 | softWrap = myEditor.softWrapModel.getSoftWrapIndex(offset) + 1
132 | if (softWrap <= 0) {
133 | softWrap = -softWrap
134 | }
135 | foldRegion = myEditor.foldingModel.getLastCollapsedRegionBefore(offset) + 1
136 | }
137 | }
138 |
139 | fun advance() {
140 | val nextWrapOffset = if (softWrap < mySoftWraps.size) mySoftWraps[softWrap].start else Int.MAX_VALUE
141 | offset = getNextVisualLineStartOffset(nextWrapOffset)
142 | if (offset == Int.MAX_VALUE) {
143 | offset = -1
144 | } else if (offset == nextWrapOffset) {
145 | softWrap++
146 | }
147 | visualLine++
148 | while (foldRegion < myFoldRegions.size && myFoldRegions[foldRegion].startOffset < offset) foldRegion++
149 | }
150 |
151 | private fun getNextVisualLineStartOffset(nextWrapOffset: Int): Int {
152 | while (logicalLine < myDocument.lineCount) {
153 | val lineStartOffset = myDocument.getLineStartOffset(logicalLine)
154 | if (lineStartOffset > nextWrapOffset) return nextWrapOffset
155 | logicalLine++
156 | if (!isCollapsed(lineStartOffset)) return lineStartOffset
157 | }
158 | return nextWrapOffset
159 | }
160 |
161 | private fun isCollapsed(offset: Int): Boolean {
162 | while (foldRegion < myFoldRegions.size) {
163 | val region = myFoldRegions[foldRegion]
164 | if (offset <= region.startOffset) return false
165 | if (offset <= region.endOffset) return true
166 | foldRegion++
167 | }
168 | return false
169 | }
170 |
171 | fun atEnd(): Boolean {
172 | return offset == -1
173 | }
174 |
175 | public override fun clone(): Location {
176 | return try {
177 | super.clone() as Location
178 | } catch (e: CloneNotSupportedException) {
179 | throw RuntimeException(e)
180 | }
181 | }
182 | }
183 | }
--------------------------------------------------------------------------------
/core/src/main/kotlin/com/nasller/codeglance/util/Util.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.util
2 |
3 | import com.intellij.openapi.editor.colors.TextAttributesKey
4 | import com.intellij.util.SmartList
5 |
6 | object Util {
7 | const val PLUGIN_NAME = "CodeGlance Pro"
8 | const val MIN_WIDTH = 30
9 | const val MAX_WIDTH = 250
10 | val MARK_COMMENT_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("MARK_COMMENT_ATTRIBUTES")
11 | val MARK_CLASS_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("MARK_CLASS_ATTRIBUTES")
12 | val MARK_RIDER_REGION_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("MARK_RIDER_REGION_ATTRIBUTES")
13 | val MARK_CLION_REGION_ATTRIBUTES = TextAttributesKey.createTextAttributesKey("MARK_CLION_REGION_ATTRIBUTES")
14 |
15 | inline fun Collection.mapSmart(transform: (T) -> R): List {
16 | return when (val size = size) {
17 | 1 -> SmartList(transform(first()))
18 | 0 -> emptyList()
19 | else -> mapTo(ArrayList(size), transform)
20 | }
21 | }
22 |
23 | fun TextAttributesKey.isMarkAttributes() = this == MARK_COMMENT_ATTRIBUTES || this == MARK_CLASS_ATTRIBUTES
24 | || this == MARK_RIDER_REGION_ATTRIBUTES || this == MARK_CLION_REGION_ATTRIBUTES
25 | }
--------------------------------------------------------------------------------
/core/src/main/python/CharacterWeight.py:
--------------------------------------------------------------------------------
1 | from PIL import Image, ImageFont,ImageDraw
2 | import sys
3 |
4 | # Generates java source for the static character weight map in CharacterWeight.java by
5 | # printing each char and observing the amount of black vs white present in the image.
6 |
7 | def getWeight(char,font):
8 | boostFactor = 2.0
9 | image = Image.new("RGBA", (7, 12), (255,255,255))
10 |
11 | draw = ImageDraw.Draw(image)
12 |
13 | draw.text((0,0), char, (0, 0, 0), font=font)
14 |
15 | pix = image.load()
16 |
17 | topAverage = 0
18 | count = 0
19 | for x in range(0, 7):
20 | for y in range(0, 6):
21 | topAverage += pix[x,y][0] / 255.0
22 | count += 1
23 | topAverage /= count
24 |
25 | bottomAverage = 0
26 | count = 0
27 | for x in range(0, 7):
28 | for y in range(7, 12):
29 | bottomAverage += pix[x,y][0] / 255.0
30 | count += 1
31 |
32 | bottomAverage /= count
33 |
34 | return (1 - topAverage) * boostFactor, (1 - bottomAverage) * boostFactor
35 |
36 | font = ImageFont.truetype("./cour.ttf", 12)
37 |
38 | print("{")
39 | for char in range(33, 127):
40 | top, bottom = getWeight(chr(char), font);
41 | print("\t%2.4ff,\t// %03d = '%s' (top)" % (top, char, chr(char)))
42 | print("\t%2.4ff,\t// %03d = '%s' (bottom)" % (bottom, char, chr(char)))
43 | print("};")
--------------------------------------------------------------------------------
/core/src/main/python/cour.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nasller/CodeGlancePro/b171af061d63e64f0b4847eed43b79ca141f2e9e/core/src/main/python/cour.ttf
--------------------------------------------------------------------------------
/core/src/main/resources/colorSchemes/color-default-darcula.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/core/src/main/resources/colorSchemes/color-default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/core/src/main/resources/image/ali_pay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nasller/CodeGlancePro/b171af061d63e64f0b4847eed43b79ca141f2e9e/core/src/main/resources/image/ali_pay.png
--------------------------------------------------------------------------------
/core/src/main/resources/image/paypal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nasller/CodeGlancePro/b171af061d63e64f0b4847eed43b79ca141f2e9e/core/src/main/resources/image/paypal.png
--------------------------------------------------------------------------------
/core/src/main/resources/image/wechat_pay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nasller/CodeGlancePro/b171af061d63e64f0b4847eed43b79ca141f2e9e/core/src/main/resources/image/wechat_pay.png
--------------------------------------------------------------------------------
/core/src/main/resources/messages/CodeGlanceBundle.properties:
--------------------------------------------------------------------------------
1 | popup.hover.minimap=Show Minimap on scrollbar hover
2 | popup.hover.minimap.delay=Delay show minimap on scrollbar hover\:
3 | popup.singleFileVisibleButton=Show quick hide button
4 | popup.showErrorStripesFullLineHighlight=Show errorStripes full line highlight
5 | popup.showOtherFullLineHighlight=Show another full line highlight
6 | popup.autoCalculateWidth=Automatically calculate width in splitter mode
7 | glance.visible.show=Show Minimap
8 | glance.visible.hide=Hide minimap
9 | glance.show.editor.preview.popup=Show code lens on minimap hover
10 | glance.mouse.wheel.editor.preview=Mouse wheel move code lens
11 | settings.general=General Setting
12 | settings.pixels=Pixels Per Line\:
13 | settings.alignment=Alignment\:
14 | settings.alignment.right=Right
15 | settings.alignment.left=Left
16 | settings.jump=Jump to position on\:
17 | settings.jump.none=None
18 | settings.jump.down=Mouse Down
19 | settings.jump.up=Mouse Up
20 | settings.click=Click Type\:
21 | settings.click.code=Code Position
22 | settings.click.mouse=Mouse Position
23 | settings.click.move=Move Only
24 | settings.viewport.color=Viewport Color\:
25 | settings.viewport.border.color=Viewport Border Color\:
26 | settings.viewport.border.thickness=Viewport Border Thickness\:
27 | settings.min.line=Min lines count\:
28 | settings.max.line=Max lines count\:
29 | settings.render=Render Style\:
30 | settings.viewport=Viewport Setting
31 | settings.mark=Mark Setting
32 | settings.option=Options Setting
33 | settings.disabled=Disabled by default
34 | settings.hide.original.scrollbar=Hide Original ScrollBar And ErrorStripes
35 | settings.highlight.vcs=VCS Highlight
36 | settings.highlight.filter.markup=Filter Markup Highlight
37 | settings.highlight.filter.markup.desc=Including debug,inspection,etc. highlight
38 | settings.highlight.markup=Markup Highlight
39 | settings.highlight.markup.desc=Including search results,Identifier under caret,etc. highlight
40 | settings.highlight.syntax=Syntax Highlight
41 | settings.disabled.language=Disable language extension name(,comma separated)\:
42 | settings.markers.enable=Enable Markers render
43 | settings.bookmarks.markers.enable=Enable Bookmarks Marker render
44 | settings.markers.scale=Markers font scale\:
45 | settings.markers.regex=Markers regex
46 | settings.two.sides.diff=Open TwoSides Diff
47 | settings.three.sides.diff=Open ThreeSides Diff
48 | settings.three.sides.middle.diff=Open ThreeSides DiffMiddle
49 | settings.editor.kind=Editor Kind\:
50 | settings.main.width=Main Width\:
51 | settings.diff.width=Diff Width\:
52 | settings.untyped.width=Untyped Width\:
53 | settings.console.width=Console Width\:
54 | settings.preview.width=Preview Width\:
55 | settings.width.lock=Lock
56 | settings.experiment.use.fast.minimap.for.main=Experimental: Use FastMinimap For Main Editor
57 | settings.use.empty.minimap=Use Empty Minimap\:
58 | settings.editor.size=Editor Size\:
59 | donate.title=Donate
60 | donate.wechat=WeChatPay
61 | donate.alipay=Alipay
62 | donate.thanks=Thanks for your support\!
63 | donate.contribution=If you like this plugin, please consider donating or star it on Github \
64 | to
support continuing development and maintenance!
--------------------------------------------------------------------------------
/core/src/main/resources/messages/CodeGlanceBundle_zh.properties:
--------------------------------------------------------------------------------
1 | popup.hover.minimap=显示minimap当鼠标划过原始滚动条
2 | popup.hover.minimap.delay=延迟显示minimap当鼠标划过原始滚动条\:
3 | popup.singleFileVisibleButton=显示快捷隐藏按钮
4 | popup.showErrorStripesFullLineHighlight=显示错误线条整行高亮
5 | popup.showOtherFullLineHighlight=显示其他线条整行高亮
6 | popup.autoCalculateWidth=在拆分窗口下自动计算宽度
7 | glance.visible.show=显示minimap
8 | glance.visible.hide=隐藏minimap
9 | glance.show.editor.preview.popup=悬停在迷你地图上时显示代码透镜
10 | glance.mouse.wheel.editor.preview=鼠标滚轮移动代码透镜
11 | settings.general=一般配置
12 | settings.pixels=每行像素\:
13 | settings.alignment=显示minimap位置\:
14 | settings.alignment.right=右
15 | settings.alignment.left=左
16 | settings.jump=点击跳转当\:
17 | settings.jump.none=不跳转
18 | settings.jump.down=鼠标按下
19 | settings.jump.up=鼠标松开
20 | settings.click=点击跳转方式\:
21 | settings.click.code=代码位置
22 | settings.click.mouse=鼠标位置
23 | settings.click.move=仅移动
24 | settings.viewport.color=视窗颜色\:
25 | settings.viewport.border.color=视窗边框颜色\:
26 | settings.viewport.border.thickness=视窗边框厚度\:
27 | settings.min.line=最小总行数\:
28 | settings.max.line=最大总行数\:
29 | settings.render=渲染类型\:
30 | settings.viewport=视窗配置
31 | settings.mark=MARK配置
32 | settings.option=选项配置
33 | settings.disabled=默认禁用
34 | settings.hide.original.scrollbar=隐藏原始滚动条及错误线条
35 | settings.highlight.vcs=VCS高亮
36 | settings.highlight.filter.markup=Filter Markup高亮
37 | settings.highlight.filter.markup.desc=包括调试,检查等高亮
38 | settings.highlight.markup=Markup高亮
39 | settings.highlight.markup.desc=包括搜索结果,插入符号下的标识符等高亮
40 | settings.highlight.syntax=语法高亮
41 | settings.disabled.language=禁用文件后缀(,分隔)\:
42 | settings.markers.enable=启用MARK渲染
43 | settings.bookmarks.markers.enable=启用书签MARK渲染
44 | settings.markers.scale=MARK字体大小\:
45 | settings.markers.regex=MARK正则表达式\:
46 | settings.two.sides.diff=开启两边差异对比
47 | settings.three.sides.diff=开启三边差异对比
48 | settings.three.sides.middle.diff=开启三边差异对比中间
49 | settings.editor.kind=编辑器类型\:
50 | settings.main.width=Main宽度\:
51 | settings.diff.width=Diff宽度\:
52 | settings.untyped.width=Untyped宽度\:
53 | settings.console.width=Console宽度\:
54 | settings.preview.width=Preview宽度\:
55 | settings.width.lock=锁定
56 | settings.experiment.use.fast.minimap.for.main=实验性:Main编辑器使用快速渲染小地图
57 | settings.use.empty.minimap=使用空白小地图\:
58 | settings.editor.size=迷你地图大小\:
59 | donate.title=捐赠
60 | donate.wechat=微信支付
61 | donate.alipay=支付宝
62 | donate.thanks=感谢您的支持\!
63 | donate.contribution=如果您喜欢这个插件,请考虑捐赠或给这个项目\
64 | 一个星星,以支持开发者继续开发!
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # suppress inspection "UnusedProperty" for whole file
2 |
3 | # IntelliJ Platform Artifacts Repositories
4 | # -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html
5 |
6 | pluginName = CodeGlance Pro
7 | pluginGroup = com.nasller.codeglance
8 |
9 | pluginVersion = 1.9.9
10 |
11 | # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
12 | # for insight into build numbers and IntelliJ Platform versions.
13 | pluginSinceBuild = 251.13774
14 | pluginUntilBuild = 252.*
15 |
16 | #RD,PY,CL,GO,PS
17 | platformType = IU
18 | #LATEST-EAP-SNAPSHOT
19 | platformVersion = 2025.1
20 | riderPlatformVersion = 2025.1
21 | clionPlatformVersion = 2025.1
22 | #idea-sandbox py-sandbox rider-sandbox clion-sandbox php-sandbox
23 | sandboxDir = idea-sandbox
24 | riderSandboxDir = rider-sandbox
25 | clionSandboxDir = clion-sandbox
26 |
27 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
28 | # Example: platformPlugins = com.jetbrains.php:203.4449.22, org.intellij.scala:2023.3.27@EAP
29 | #IDE Perf:com.google.ide-perf | Index Viewer:com.jetbrains.hackathon.indices.viewer | PsiViewer
30 | platformPlugins = org.intellij.scala:2025.1.20,Dart:251.25267,PsiViewer:251.175
31 | defaultPlugins = PsiViewer:251.175
32 | # Example: platformBundledPlugins = com.intellij.java
33 | platformBundledPlugins = com.intellij.java,org.jetbrains.kotlin
34 |
35 | # Java language level used to compile sources and to generate the files for - Java 21 is required since 2024.2
36 | javaVersion = 21
37 |
38 | # Gradle
39 | gradleVersion = 8.13
40 |
41 | # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
42 | kotlin.stdlib.default.dependency = false
43 |
44 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
45 | org.gradle.configuration-cache = false
46 |
47 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
48 | org.gradle.caching = true
49 |
50 | # Enable Gradle Kotlin DSL Lazy Property Assignment -> https://docs.gradle.org/current/userguide/kotlin_dsl.html#kotdsl:assignment
51 | systemProp.org.gradle.unsafe.kotlin.assignment = true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | kotlin = "2.1.20"
3 | gradleIntelliJPlugin = "2.6.0"
4 |
5 | [plugins]
6 | gradleIntelliJPlugin = { id = "org.jetbrains.intellij.platform", version.ref = "gradleIntelliJPlugin" }
7 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nasller/CodeGlancePro/b171af061d63e64f0b4847eed43b79ca141f2e9e/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-8.13-all.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Use the maximum available, or set MAX_FD != -1 to use that value.
89 | MAX_FD=maximum
90 |
91 | warn () {
92 | echo "$*"
93 | } >&2
94 |
95 | die () {
96 | echo
97 | echo "$*"
98 | echo
99 | exit 1
100 | } >&2
101 |
102 | # OS specific support (must be 'true' or 'false').
103 | cygwin=false
104 | msys=false
105 | darwin=false
106 | nonstop=false
107 | case "$( uname )" in #(
108 | CYGWIN* ) cygwin=true ;; #(
109 | Darwin* ) darwin=true ;; #(
110 | MSYS* | MINGW* ) msys=true ;; #(
111 | NONSTOP* ) nonstop=true ;;
112 | esac
113 |
114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
115 |
116 |
117 | # Determine the Java command to use to start the JVM.
118 | if [ -n "$JAVA_HOME" ] ; then
119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
120 | # IBM's JDK on AIX uses strange locations for the executables
121 | JAVACMD=$JAVA_HOME/jre/sh/java
122 | else
123 | JAVACMD=$JAVA_HOME/bin/java
124 | fi
125 | if [ ! -x "$JAVACMD" ] ; then
126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
127 |
128 | Please set the JAVA_HOME variable in your environment to match the
129 | location of your Java installation."
130 | fi
131 | else
132 | JAVACMD=java
133 | if ! command -v java >/dev/null 2>&1
134 | then
135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
136 |
137 | Please set the JAVA_HOME variable in your environment to match the
138 | location of your Java installation."
139 | fi
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
147 | # shellcheck disable=SC3045
148 | MAX_FD=$( ulimit -H -n ) ||
149 | warn "Could not query maximum file descriptor limit"
150 | esac
151 | case $MAX_FD in #(
152 | '' | soft) :;; #(
153 | *)
154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
155 | # shellcheck disable=SC3045
156 | ulimit -n "$MAX_FD" ||
157 | warn "Could not set maximum file descriptor limit to $MAX_FD"
158 | esac
159 | fi
160 |
161 | # Collect all arguments for the java command, stacking in reverse order:
162 | # * args from the command line
163 | # * the main class name
164 | # * -classpath
165 | # * -D...appname settings
166 | # * --module-path (only if needed)
167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
168 |
169 | # For Cygwin or MSYS, switch paths to Windows format before running java
170 | if "$cygwin" || "$msys" ; then
171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
173 |
174 | JAVACMD=$( cygpath --unix "$JAVACMD" )
175 |
176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
177 | for arg do
178 | if
179 | case $arg in #(
180 | -*) false ;; # don't mess with options #(
181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
182 | [ -e "$t" ] ;; #(
183 | *) false ;;
184 | esac
185 | then
186 | arg=$( cygpath --path --ignore --mixed "$arg" )
187 | fi
188 | # Roll the args list around exactly as many times as the number of
189 | # args, so each arg winds up back in the position where it started, but
190 | # possibly modified.
191 | #
192 | # NB: a `for` loop captures its iteration list before it begins, so
193 | # changing the positional parameters here affects neither the number of
194 | # iterations, nor the values presented in `arg`.
195 | shift # remove old arg
196 | set -- "$@" "$arg" # push replacement arg
197 | done
198 | fi
199 |
200 |
201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
203 |
204 | # Collect all arguments for the java command;
205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
206 | # shell script including quotes and variable substitutions, so put them in
207 | # double quotes to make sure that they get re-expanded; and
208 | # * put everything else in single quotes, so that it's not re-expanded.
209 |
210 | set -- \
211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
212 | -classpath "$CLASSPATH" \
213 | org.gradle.wrapper.GradleWrapperMain \
214 | "$@"
215 |
216 | # Stop when "xargs" is not available.
217 | if ! command -v xargs >/dev/null 2>&1
218 | then
219 | die "xargs is not available"
220 | fi
221 |
222 | # Use "xargs" to parse quoted args.
223 | #
224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
225 | #
226 | # In Bash we could simply go:
227 | #
228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
229 | # set -- "${ARGS[@]}" "$@"
230 | #
231 | # but POSIX shell has neither arrays nor command substitution, so instead we
232 | # post-process each arg (as a line of input to sed) to backslash-escape any
233 | # character that might be a shell metacharacter, then use eval to reverse
234 | # that process (while maintaining the separation between arguments), and wrap
235 | # the whole thing up as a single "set" statement.
236 | #
237 | # This will of course break if any of these variables contains a newline or
238 | # an unmatched quote.
239 | #
240 |
241 | eval "set -- $(
242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
243 | xargs -n1 |
244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
245 | tr '\n' ' '
246 | )" '"$@"'
247 |
248 | exec "$JAVACMD" "$@"
249 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/rider/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.intellij.platform.module")
3 | alias(libs.plugins.kotlin)
4 | }
5 |
6 | repositories {
7 | mavenCentral()
8 |
9 | intellijPlatform {
10 | defaultRepositories()
11 | }
12 | }
13 |
14 | dependencies {
15 | intellijPlatform {
16 | rider(providers.gradleProperty("riderPlatformVersion"))
17 | pluginModule(implementation(project(":core")))
18 | javaCompiler("243.21565.192")
19 | }
20 | }
--------------------------------------------------------------------------------
/rider/src/main/kotlin/com/nasller/codeglance/extensions/visitor/MarkRiderVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.extensions.visitor
2 |
3 | import MyRainbowVisitor
4 | import com.intellij.codeInsight.daemon.impl.HighlightVisitor
5 | import com.intellij.lang.Language
6 | import com.intellij.psi.PsiElement
7 | import com.intellij.psi.util.PsiTreeUtil
8 | import com.intellij.psi.util.elementType
9 | import com.jetbrains.rider.languages.fileTypes.csharp.CSharpLanguage
10 | import com.jetbrains.rider.languages.fileTypes.csharp.kotoparser.lexer.CSharpTokenType
11 | import com.nasller.codeglance.util.Util
12 |
13 | class MarkRiderVisitor : MyRainbowVisitor() {
14 | override fun visit(element: PsiElement) {
15 | if(element.elementType == CSharpTokenType.PP_START_REGION){
16 | val psiMessage = PsiTreeUtil.skipWhitespacesForward(element) ?: return
17 | if(psiMessage.elementType == CSharpTokenType.PP_MESSAGE){
18 | visitText(psiMessage.text, psiMessage.textRange, Util.MARK_RIDER_REGION_ATTRIBUTES)
19 | }
20 | }
21 | }
22 |
23 | override fun suitableForFile(language: Language) = language is CSharpLanguage
24 |
25 | override fun clone(): HighlightVisitor = MarkRiderVisitor()
26 | }
--------------------------------------------------------------------------------
/rider/src/main/resources/META-INF/plugin-rider.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | rootProject.name = "CodeGlancePro"
4 | include(":core")
5 | include(":rider")
6 | include(":clion")
7 |
8 | dependencyResolutionManagement {
9 | repositories {
10 | maven("https://mirrors.tencent.com/nexus/repository/maven-public/")
11 | mavenLocal()
12 | mavenCentral()
13 | }
14 | }
--------------------------------------------------------------------------------
/src/main/java/com/nasller/codeglance/agent/Interceptor.java:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.agent;
2 |
3 | import com.intellij.openapi.editor.impl.EditorImpl;
4 | import net.bytebuddy.implementation.bind.annotation.This;
5 |
6 | import javax.swing.*;
7 | import java.awt.*;
8 |
9 | public class Interceptor {
10 | public static int intercept(@This EditorImpl editor) {
11 | JComponent component = editor.getComponent();
12 | LayoutManager layoutManager = component.getLayout();
13 | if(layoutManager instanceof BorderLayout layout){
14 | Component layoutComponent = layout.getLayoutComponent(BorderLayout.LINE_END);
15 | if(layoutComponent != null){
16 | return component.getWidth() - layoutComponent.getWidth();
17 | }
18 | }
19 | return component.getWidth();
20 | }
21 | }
--------------------------------------------------------------------------------
/src/main/java/com/nasller/codeglance/agent/Main.java:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.agent;
2 |
3 | import org.jetbrains.org.objectweb.asm.ClassReader;
4 | import org.jetbrains.org.objectweb.asm.ClassWriter;
5 | import org.jetbrains.org.objectweb.asm.Label;
6 | import org.jetbrains.org.objectweb.asm.tree.*;
7 |
8 | import java.lang.instrument.ClassFileTransformer;
9 | import java.lang.instrument.Instrumentation;
10 | import java.lang.instrument.UnmodifiableClassException;
11 | import java.security.ProtectionDomain;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | import static org.jetbrains.org.objectweb.asm.Opcodes.*;
16 |
17 | public class Main {
18 | public static void premain(String args, Instrumentation inst) throws UnmodifiableClassException {
19 | agentmain(args, inst);
20 | }
21 |
22 | public static void agentmain(String args, Instrumentation inst) throws UnmodifiableClassException {
23 | String hookClassName = "com/intellij/openapi/editor/impl/EditorImpl";
24 | inst.addTransformer(new ClassFileTransformer() {
25 | @Override
26 | public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
27 | if(!hookClassName.equals(className)){
28 | return classfileBuffer;
29 | }
30 | ClassReader reader = new ClassReader(classfileBuffer);
31 | ClassNode node = new ClassNode(ASM5);
32 | reader.accept(node, 0);
33 | for (MethodNode mn : node.methods) {
34 | if ("getStickyLinesPanelWidth".equals(mn.name) && mn.desc.startsWith("()I")) {
35 | mn.instructions.clear();
36 | InsnList list = new InsnList();
37 | list.add(new LabelNode());
38 | list.add(new VarInsnNode(ALOAD,0));
39 | list.add(new FieldInsnNode(GETFIELD, hookClassName,
40 | "myPanel", "Ljavax/swing/JPanel;"));
41 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "javax/swing/JPanel", "getLayout",
42 | "()Ljava/awt/LayoutManager;", false));
43 | list.add(new VarInsnNode(ASTORE,1));
44 | list.add(new LabelNode());
45 | list.add(new VarInsnNode(ALOAD,1));
46 | list.add(new TypeInsnNode(INSTANCEOF, "java/awt/BorderLayout"));
47 | Label label2 = new Label();
48 | list.add(new JumpInsnNode(IFEQ, new LabelNode(label2)));
49 | list.add(new LabelNode());
50 | list.add(new VarInsnNode(ALOAD,1));
51 | list.add(new TypeInsnNode(CHECKCAST, "java/awt/BorderLayout"));
52 | list.add(new LdcInsnNode("After"));
53 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "java/awt/BorderLayout", "getLayoutComponent",
54 | "(Ljava/lang/Object;)Ljava/awt/Component;", false));
55 | list.add(new VarInsnNode(ASTORE, 2));
56 | list.add(new LabelNode());
57 | list.add(new VarInsnNode(ALOAD,2));
58 | list.add(new JumpInsnNode(IFNULL, new LabelNode(label2)));
59 | list.add(new LabelNode());
60 | list.add(new VarInsnNode(ALOAD,0));
61 | list.add(new FieldInsnNode(GETFIELD, hookClassName,
62 | "myPanel", "Ljavax/swing/JPanel;"));
63 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "javax/swing/JPanel", "getWidth",
64 | "()I", false));
65 | list.add(new VarInsnNode(ALOAD,2));
66 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "java/awt/Component", "getWidth",
67 | "()I", false));
68 | list.add(new InsnNode(ISUB));
69 | list.add(new InsnNode(IRETURN));
70 | list.add(new LabelNode(label2));
71 | list.add(new VarInsnNode(ALOAD,0));
72 | list.add(new FieldInsnNode(GETFIELD, hookClassName,
73 | "myPanel", "Ljavax/swing/JPanel;"));
74 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "javax/swing/JPanel", "getWidth",
75 | "()I", false));
76 | list.add(new InsnNode(IRETURN));
77 | mn.instructions.add(list);
78 | }
79 | }
80 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
81 | node.accept(writer);
82 | return writer.toByteArray();
83 | }
84 | }, true);
85 | List> classesToRetransform = new ArrayList<>();
86 | for (Class> clazz : inst.getAllLoadedClasses()) {
87 | String className = clazz.getName();
88 | if (hookClassName.equals(className)) {
89 | classesToRetransform.add(clazz);
90 | }
91 | }
92 | inst.retransformClasses(classesToRetransform.toArray(new Class[0]));
93 | }
94 | }
--------------------------------------------------------------------------------
/src/main/java/com/nasller/codeglance/agent/MyAppLifecycleListener.java:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.agent;
2 |
3 | import com.intellij.ide.AppLifecycleListener;
4 | import com.intellij.ide.plugins.IdeaPluginDescriptor;
5 | import com.intellij.ide.plugins.PluginManagerCore;
6 | import com.intellij.openapi.editor.impl.EditorImpl;
7 | import com.intellij.openapi.extensions.PluginId;
8 | import net.bytebuddy.ByteBuddy;
9 | import net.bytebuddy.ClassFileVersion;
10 | import net.bytebuddy.agent.ByteBuddyAgent;
11 | import net.bytebuddy.description.type.TypeDescription;
12 | import net.bytebuddy.dynamic.ClassFileLocator;
13 | import net.bytebuddy.dynamic.DynamicType.Unloaded;
14 | import net.bytebuddy.dynamic.loading.ClassInjector;
15 | import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
16 | import net.bytebuddy.implementation.MethodDelegation;
17 | import net.bytebuddy.matcher.ElementMatchers;
18 | import org.jetbrains.annotations.NotNull;
19 | import org.slf4j.Logger;
20 | import org.slf4j.LoggerFactory;
21 |
22 | import java.io.File;
23 | import java.lang.management.ManagementFactory;
24 | import java.util.List;
25 | import java.util.Map;
26 |
27 | public class MyAppLifecycleListener implements AppLifecycleListener {
28 | private static final Logger log = LoggerFactory.getLogger(MyAppLifecycleListener.class);
29 |
30 | @Override
31 | public void appFrameCreated(@NotNull List commandLineArgs) {
32 | injectAgent();
33 | }
34 |
35 | private static void injectAgent() {
36 | try {
37 | IdeaPluginDescriptor plugin = PluginManagerCore.getPlugin(PluginId.findId("com.nasller.CodeGlancePro"));
38 | if(plugin == null){
39 | return;
40 | }
41 | File[] files = plugin.getPluginPath().resolve("lib").toFile().listFiles(pathname -> {
42 | String name = pathname.getName();
43 | return !name.contains("searchableOptions") && name.contains("CodeGlancePro") && name.endsWith(".jar");
44 | });
45 | if(files == null || files.length == 0){
46 | log.warn("CodeGlance Pro not found!");
47 | return;
48 | }
49 | File file = files[0];
50 | if(!file.isFile()) {
51 | log.warn("CodeGlance Pro not found!");
52 | return;
53 | }
54 | String runtimeMxBeanName = ManagementFactory.getRuntimeMXBean().getName();
55 | String pid = runtimeMxBeanName.substring(0, runtimeMxBeanName.indexOf('@'));
56 | ByteBuddyAgent.attach(file,pid);
57 | }catch (Throwable e){
58 | log.warn("Start CodeGlance Pro Agent error!", e);
59 | }
60 | }
61 |
62 | private static void injectByteBuddy(){
63 | try {
64 | ByteBuddyAgent.install();
65 | ClassInjector.UsingReflection.ofSystemClassLoader().inject(Map.of(new TypeDescription.ForLoadedType(Interceptor.class),
66 | ClassFileLocator.ForClassLoader.read(Interceptor.class)));
67 | try (Unloaded made = new ByteBuddy(ClassFileVersion.ofThisVm(ClassFileVersion.JAVA_V21))
68 | .redefine(EditorImpl.class)
69 | .method(ElementMatchers.named("getStickyLinesPanelWidth").and(ElementMatchers.returns(int.class)))
70 | .intercept(MethodDelegation.to(Interceptor.class))
71 | .make()){
72 | made.load(EditorImpl.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
73 | }
74 | }catch (Throwable e){
75 | log.warn("Start CodeGlance Pro Agent error!", e);
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/EditorPanelInjector.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance
2 |
3 | import com.intellij.diff.DiffContext
4 | import com.intellij.diff.DiffExtension
5 | import com.intellij.diff.FrameDiffTool
6 | import com.intellij.diff.requests.DiffRequest
7 | import com.intellij.diff.tools.fragmented.UnifiedDiffViewer
8 | import com.intellij.diff.tools.util.side.OnesideTextDiffViewer
9 | import com.intellij.diff.tools.util.side.ThreesideTextDiffViewer
10 | import com.intellij.diff.tools.util.side.TwosideTextDiffViewer
11 | import com.intellij.diff.util.DiffUserDataKeysEx
12 | import com.intellij.openapi.application.runInEdt
13 | import com.intellij.openapi.editor.EditorFactory
14 | import com.intellij.openapi.editor.EditorKind
15 | import com.intellij.openapi.editor.colors.EditorColorsListener
16 | import com.intellij.openapi.editor.colors.EditorColorsScheme
17 | import com.intellij.openapi.editor.event.EditorFactoryEvent
18 | import com.intellij.openapi.editor.event.EditorFactoryListener
19 | import com.intellij.openapi.editor.impl.EditorImpl
20 | import com.intellij.openapi.fileEditor.FileDocumentManager
21 | import com.intellij.openapi.util.Disposer
22 | import com.intellij.openapi.util.Key
23 | import com.intellij.ui.components.JBPanel
24 | import com.nasller.codeglance.config.CodeGlanceConfigService
25 | import com.nasller.codeglance.config.SettingsChangeListener
26 | import com.nasller.codeglance.config.SettingsChangePublisher
27 | import com.nasller.codeglance.panel.GlancePanel
28 | import com.nasller.codeglance.panel.vcs.MyVcsPanel
29 | import java.awt.BorderLayout
30 | import java.awt.Color
31 |
32 | val CURRENT_EDITOR_DIFF_VIEW = Key("CURRENT_EDITOR_DIFF_VIEW")
33 |
34 | class EditorPanelInjector : EditorFactoryListener {
35 | override fun editorCreated(event: EditorFactoryEvent) {
36 | if(event.editor.editorKind == EditorKind.DIFF) return
37 | val editorImpl = event.editor as? EditorImpl ?: return
38 | firstRunEditor(EditorInfo(editorImpl, if (CodeGlanceConfigService.Config.isRightAligned)
39 | BorderLayout.LINE_END else BorderLayout.LINE_START), null)
40 | }
41 |
42 | override fun editorReleased(event: EditorFactoryEvent) {
43 | event.editor.putUserData(CURRENT_EDITOR_DIFF_VIEW, null)
44 | }
45 | }
46 |
47 | @Suppress("UnstableApiUsage")
48 | class DiffEditorPanelInjector : DiffExtension(){
49 | override fun onViewerCreated(viewer: FrameDiffTool.DiffViewer, context: DiffContext, request: DiffRequest) {
50 | val userData = context.getUserData(DiffUserDataKeysEx.COMBINED_DIFF_TOGGLE)
51 | if(userData == null || !userData.isCombinedDiffEnabled){
52 | viewer.diffEditorInjector()
53 | }
54 | }
55 | }
56 |
57 | class GlobalSettingsChangeListener : SettingsChangeListener{
58 | override fun onGlobalChanged() {
59 | processAllGlanceEditor { oldGlance, info ->
60 | oldGlance?.apply { Disposer.dispose(this) }
61 | if(info.editor.isDisableExtensionFile() || !CodeGlanceConfigService.Config.editorKindsStr.contains(info.editor.editorKind.name)) {
62 | oldGlance?.changeOriginScrollBarWidth(false)
63 | } else {
64 | if(info.editor.editorKind == EditorKind.DIFF) {
65 | info.editor.getUserData(CURRENT_EDITOR_DIFF_VIEW)?.apply { diffEditorInjector() }
66 | } else {
67 | setMyPanel(info).apply {
68 | oldGlance?.let{ originalScrollbarWidth = it.originalScrollbarWidth }
69 | changeOriginScrollBarWidth()
70 | }
71 | }
72 | }
73 | }
74 | }
75 | }
76 |
77 | class MyEditorColorsListener : EditorColorsListener {
78 | override fun globalSchemeChange(scheme: EditorColorsScheme?) = runInEdt { SettingsChangePublisher.onGlobalChanged() }
79 | }
80 |
81 | private fun firstRunEditor(info: EditorInfo, diffView: FrameDiffTool.DiffViewer?) {
82 | if(diffView != null) info.editor.putUserData(CURRENT_EDITOR_DIFF_VIEW, diffView)
83 | if(info.editor.isDisableExtensionFile() || !CodeGlanceConfigService.Config.editorKindsStr.contains(info.editor.editorKind.name)) {
84 | return
85 | }
86 | val layout = info.editor.component.layout
87 | if (layout is BorderLayout && layout.getLayoutComponent(info.place) == null) {
88 | setMyPanel(info).apply { changeOriginScrollBarWidth() }
89 | }
90 | }
91 |
92 | private fun FrameDiffTool.DiffViewer.diffEditorInjector() {
93 | val config = CodeGlanceConfigService.Config
94 | val where = if (config.isRightAligned) BorderLayout.LINE_END else BorderLayout.LINE_START
95 | when (this) {
96 | is UnifiedDiffViewer -> if(editor is EditorImpl) {
97 | firstRunEditor(EditorInfo(editor as EditorImpl, where),this)
98 | }
99 | is OnesideTextDiffViewer -> if(editor is EditorImpl) {
100 | firstRunEditor(EditorInfo(editor as EditorImpl, where),this)
101 | }
102 | is TwosideTextDiffViewer -> if(config.diffTwoSide) {
103 | editors.filterIsInstance().forEachIndexed { index, editor ->
104 | firstRunEditor(EditorInfo(editor, if (index == 0) BorderLayout.LINE_START else BorderLayout.LINE_END),this)
105 | }
106 | }
107 | is ThreesideTextDiffViewer -> if(config.diffThreeSide) {
108 | editors.filterIsInstance().forEachIndexed { index, editor ->
109 | if (index != 1 || config.diffThreeSideMiddle) {
110 | firstRunEditor(EditorInfo(editor, if (index == 0) BorderLayout.LINE_START else BorderLayout.LINE_END),this)
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
117 | private fun processAllGlanceEditor(action: (oldGlance:GlancePanel?, EditorInfo)->Unit){
118 | try {
119 | val where = if (CodeGlanceConfigService.Config.isRightAligned) BorderLayout.LINE_END else BorderLayout.LINE_START
120 | for (editor in EditorFactory.getInstance().allEditors.filterIsInstance()) {
121 | val info = EditorInfo(editor, where)
122 | val layout = info.editor.component.layout
123 | if (layout is BorderLayout) {
124 | action(((layout.getLayoutComponent(BorderLayout.LINE_END) ?:
125 | layout.getLayoutComponent(BorderLayout.LINE_START)) as? MyPanel)?.panel, info)
126 | }
127 | }
128 | }catch (e:Exception){
129 | e.printStackTrace()
130 | }
131 | }
132 |
133 | private fun setMyPanel(info: EditorInfo): GlancePanel {
134 | val glancePanel = GlancePanel(info)
135 | info.editor.component.add(MyPanel(glancePanel), info.place)
136 | glancePanel.hideScrollBarListener.addHideScrollBarListener()
137 | return glancePanel
138 | }
139 |
140 | private fun EditorImpl.isDisableExtensionFile(): Boolean{
141 | val extension = (virtualFile ?: FileDocumentManager.getInstance().getFile(document))?.run { fileType.defaultExtension } ?: ""
142 | return extension.isNotBlank() && CodeGlanceConfigService.Config.disableLanguageSuffix.split(",").toSet().contains(extension)
143 | }
144 |
145 | internal class MyPanel(val panel: GlancePanel?): JBPanel(BorderLayout()){
146 | init{
147 | add(panel!!)
148 | if (CodeGlanceConfigService.Config.hideOriginalScrollBar){
149 | panel.myVcsPanel = MyVcsPanel(panel)
150 | add(panel.myVcsPanel!!, if (panel.getPlaceIndex() == GlancePanel.PlaceIndex.Left) BorderLayout.EAST else BorderLayout.WEST)
151 | }
152 | }
153 |
154 | override fun getBackground(): Color? = panel?.run { editor.contentComponent.background } ?: super.getBackground()
155 | }
156 |
157 | data class EditorInfo(val editor: EditorImpl, val place: String)
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/actions/DisableByDefaultAction.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.actions
2 |
3 | import com.intellij.openapi.actionSystem.ActionUpdateThread
4 | import com.intellij.openapi.actionSystem.AnActionEvent
5 | import com.intellij.openapi.application.invokeLater
6 | import com.intellij.openapi.project.DumbAwareToggleAction
7 | import com.nasller.codeglance.config.CodeGlanceConfigService
8 | import com.nasller.codeglance.config.SettingsChangePublisher
9 |
10 | class DisableByDefaultAction : DumbAwareToggleAction() {
11 | override fun isSelected(e: AnActionEvent): Boolean = CodeGlanceConfigService.Config.disabled
12 |
13 | override fun setSelected(e: AnActionEvent, state: Boolean) {
14 | CodeGlanceConfigService.Config.disabled = state
15 | invokeLater{ SettingsChangePublisher.onGlobalChanged() }
16 | }
17 |
18 | override fun getActionUpdateThread(): ActionUpdateThread {
19 | return ActionUpdateThread.BGT
20 | }
21 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/actions/ToggleVisibleAction.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.actions
2 |
3 | import com.intellij.openapi.actionSystem.ActionUpdateThread
4 | import com.intellij.openapi.actionSystem.AnActionEvent
5 | import com.intellij.openapi.actionSystem.CommonDataKeys
6 | import com.intellij.openapi.actionSystem.Presentation
7 | import com.intellij.openapi.actionSystem.ex.ActionButtonLook
8 | import com.intellij.openapi.actionSystem.ex.CustomComponentAction
9 | import com.intellij.openapi.actionSystem.impl.ActionButton
10 | import com.intellij.openapi.project.DumbAwareToggleAction
11 | import com.intellij.util.ui.JBUI
12 | import com.nasller.codeglance.panel.GlancePanel
13 | import com.nasller.codeglance.util.CodeGlanceIcons
14 | import com.nasller.codeglance.util.message
15 | import javax.swing.JComponent
16 |
17 | class ToggleVisibleAction : DumbAwareToggleAction(), CustomComponentAction {
18 | init { isEnabledInModalContext = true }
19 |
20 | override fun createCustomComponent(presentation: Presentation, place: String): JComponent =
21 | object : ActionButton(this, presentation, place, JBUI.emptySize()) {
22 | override fun getPopState(): Int = if (myRollover && isEnabled) POPPED else NORMAL
23 | }.also {
24 | it.setLook(ActionButtonLook.INPLACE_LOOK)
25 | it.border = JBUI.Borders.empty(1, 2)
26 | }
27 |
28 | override fun isSelected(e: AnActionEvent): Boolean {
29 | return e.applyToGlance{ isVisible } != false
30 | }
31 |
32 | override fun setSelected(e: AnActionEvent, state: Boolean) {
33 | e.applyToGlance{
34 | if(!config.hoveringToShowScrollBar){
35 | isVisible = state
36 | if(isVisible) refreshDataAndImage()
37 | changeOriginScrollBarWidth(isVisible)
38 | }
39 | }
40 | }
41 |
42 | override fun update(e: AnActionEvent) {
43 | super.update(e)
44 | val presentation = e.presentation
45 | if(!isSelected(e)){
46 | presentation.text = message("glance.visible.show")
47 | presentation.icon = CodeGlanceIcons.GlanceShow
48 | }else {
49 | presentation.text = message("glance.visible.hide")
50 | presentation.icon = CodeGlanceIcons.GlanceHide
51 | }
52 | }
53 |
54 | override fun getActionUpdateThread(): ActionUpdateThread {
55 | return ActionUpdateThread.BGT
56 | }
57 |
58 | private fun AnActionEvent.applyToGlance(action: GlancePanel.()->T) =
59 | getData(CommonDataKeys.EDITOR)?.getUserData(GlancePanel.CURRENT_GLANCE)?.run(action)
60 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/extensions/GlanceVisibleActionProvider.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.extensions
2 |
3 | import com.intellij.openapi.actionSystem.ActionUpdateThread
4 | import com.intellij.openapi.actionSystem.AnAction
5 | import com.intellij.openapi.actionSystem.AnActionEvent
6 | import com.intellij.openapi.actionSystem.DefaultActionGroup
7 | import com.intellij.openapi.actionSystem.ex.ActionManagerEx
8 | import com.intellij.openapi.editor.Editor
9 | import com.intellij.openapi.editor.EditorKind
10 | import com.intellij.openapi.editor.markup.InspectionWidgetActionProvider
11 | import com.nasller.codeglance.panel.GlancePanel.Companion.CURRENT_GLANCE
12 |
13 | private class GlanceVisibleActionProvider : InspectionWidgetActionProvider {
14 | override fun createAction(editor: Editor): AnAction {
15 | return object : DefaultActionGroup(ActionManagerEx.getInstanceEx().getAction("CodeGlancePro.toggle")) {
16 | override fun update(e: AnActionEvent) {
17 | e.presentation.isEnabledAndVisible = editor.getUserData(CURRENT_GLANCE)?.run {
18 | editor.editorKind == EditorKind.MAIN_EDITOR && !config.hoveringToShowScrollBar && config.singleFileVisibleButton()
19 | } == true
20 | }
21 |
22 | override fun getActionUpdateThread(): ActionUpdateThread {
23 | return ActionUpdateThread.BGT
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/extensions/visitor/MarkCommentVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.extensions.visitor
2 |
3 | import MARK_REGEX
4 | import MyRainbowVisitor
5 | import com.intellij.codeInsight.daemon.impl.HighlightVisitor
6 | import com.intellij.lang.Language
7 | import com.intellij.lang.LanguageCommenters
8 | import com.intellij.psi.PsiComment
9 | import com.intellij.psi.PsiElement
10 | import com.nasller.codeglance.util.Util
11 |
12 | class MarkCommentVisitor : MyRainbowVisitor() {
13 | override fun visit(element: PsiElement) {
14 | if (element is PsiComment) {
15 | val text = element.text
16 | MARK_REGEX?.find(text)?.let {
17 | val textRange = element.textRange
18 | val index = text.indexOf('\n',it.range.last)
19 | val blockCommentSuffix by lazy(LazyThreadSafetyMode.NONE) { getLanguageBlockCommentSuffix(element.language) ?: "" }
20 | val start = it.range.last + textRange.startOffset + 1
21 | val end = if (index > 0) index + textRange.startOffset else {
22 | textRange.endOffset - if(index < 0 && blockCommentSuffix.isNotBlank() && text.endsWith(blockCommentSuffix)){
23 | blockCommentSuffix.length
24 | } else 0
25 | }
26 | if(start != end) {
27 | addInfo(getInfo(start, end, Util.MARK_COMMENT_ATTRIBUTES))
28 | }
29 | }
30 | }
31 | }
32 |
33 | private fun getLanguageBlockCommentSuffix(language: Language) : String?{
34 | return when(language.displayName){
35 | "C#" -> "*/"
36 | else -> LanguageCommenters.INSTANCE.forLanguage(language)?.blockCommentSuffix
37 | }
38 | }
39 |
40 | override fun suitableForFile(language: Language) = true
41 |
42 | override fun clone(): HighlightVisitor = MarkCommentVisitor()
43 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/extensions/visitor/MarkDartVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.extensions.visitor
2 |
3 | import MyRainbowVisitor
4 | import com.intellij.codeInsight.daemon.impl.HighlightVisitor
5 | import com.intellij.lang.Language
6 | import com.intellij.psi.PsiElement
7 | import com.intellij.psi.util.PsiTreeUtil
8 | import com.jetbrains.lang.dart.DartLanguage
9 | import com.jetbrains.lang.dart.psi.DartClass
10 | import com.jetbrains.lang.dart.psi.DartExtensionDeclaration
11 | import com.jetbrains.lang.dart.psi.DartId
12 | import com.nasller.codeglance.util.Util
13 |
14 | class MarkDartVisitor : MyRainbowVisitor() {
15 | override fun visit(element: PsiElement) {
16 | when (element) {
17 | is DartClass -> visitPsiNameIdentifier(element)
18 | is DartExtensionDeclaration -> {
19 | val psiElement = PsiTreeUtil.findChildOfType(element, DartId::class.java) ?: return
20 | visitText(psiElement.text, psiElement.textRange, Util.MARK_CLASS_ATTRIBUTES)
21 | }
22 | }
23 | }
24 |
25 | override fun suitableForFile(language: Language) = language is DartLanguage
26 |
27 | override fun clone(): HighlightVisitor = MarkDartVisitor()
28 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/extensions/visitor/MarkJavaVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.extensions.visitor
2 |
3 | import MyRainbowVisitor
4 | import com.intellij.codeInsight.daemon.impl.HighlightVisitor
5 | import com.intellij.lang.Language
6 | import com.intellij.lang.java.JavaLanguage
7 | import com.intellij.psi.PsiClass
8 | import com.intellij.psi.PsiElement
9 | import com.intellij.psi.impl.java.stubs.JavaStubElementTypes
10 | import com.intellij.psi.util.elementType
11 |
12 | class MarkJavaVisitor : MyRainbowVisitor() {
13 | override fun visit(element: PsiElement) {
14 | if (element is PsiClass && element.elementType == JavaStubElementTypes.CLASS) {
15 | visitPsiNameIdentifier(element)
16 | }
17 | }
18 |
19 | override fun suitableForFile(language: Language) = language is JavaLanguage
20 |
21 | override fun clone(): HighlightVisitor = MarkJavaVisitor()
22 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/extensions/visitor/MarkKotlinVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.extensions.visitor
2 |
3 | import MyRainbowVisitor
4 | import com.intellij.codeInsight.daemon.impl.HighlightVisitor
5 | import com.intellij.lang.Language
6 | import com.intellij.psi.PsiElement
7 | import com.intellij.psi.StubBasedPsiElement
8 | import org.jetbrains.kotlin.idea.KotlinLanguage
9 | import org.jetbrains.kotlin.psi.KtClass
10 | import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
11 |
12 | class MarkKotlinVisitor : MyRainbowVisitor() {
13 | override fun visit(element: PsiElement) {
14 | if (element is KtClass && (element as? StubBasedPsiElement<*>)?.iElementType == KtStubElementTypes.CLASS) {
15 | visitPsiNameIdentifier(element)
16 | }
17 | }
18 |
19 | override fun suitableForFile(language: Language) = language is KotlinLanguage
20 |
21 | override fun clone(): HighlightVisitor = MarkKotlinVisitor()
22 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/extensions/visitor/MarkScalaVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.extensions.visitor
2 |
3 | import MyRainbowVisitor
4 | import com.intellij.codeInsight.daemon.impl.HighlightVisitor
5 | import com.intellij.lang.Language
6 | import com.intellij.psi.PsiElement
7 | import com.intellij.psi.PsiNameIdentifierOwner
8 | import org.jetbrains.plugins.scala.ScalaLanguage
9 | import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.ScClass
10 | import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.ScObject
11 | import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.ScTrait
12 |
13 | class MarkScalaVisitor : MyRainbowVisitor() {
14 | override fun visit(element: PsiElement) {
15 | if(element is ScClass || element is ScTrait || element is ScObject) {
16 | visitPsiNameIdentifier(element as PsiNameIdentifierOwner)
17 | }
18 | }
19 |
20 | override fun suitableForFile(language: Language) = language is ScalaLanguage
21 |
22 | override fun clone(): HighlightVisitor = MarkScalaVisitor()
23 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/listener/GlanceListener.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.listener
2 |
3 | import com.intellij.openapi.Disposable
4 | import com.intellij.openapi.application.ApplicationManager
5 | import com.intellij.openapi.editor.event.*
6 | import com.intellij.openapi.editor.ex.RangeHighlighterEx
7 | import com.intellij.openapi.editor.impl.event.MarkupModelListener
8 | import com.nasller.codeglance.config.SettingsChangeListener
9 | import com.nasller.codeglance.config.enums.EditorSizeEnum
10 | import com.nasller.codeglance.panel.GlancePanel
11 | import java.awt.event.*
12 |
13 | class GlanceListener(private val glancePanel: GlancePanel) : ComponentAdapter(), SettingsChangeListener, CaretListener,
14 | VisibleAreaListener, SelectionListener, HierarchyBoundsListener, HierarchyListener, Disposable {
15 | init {
16 | glancePanel.addHierarchyListener(this)
17 | glancePanel.addHierarchyBoundsListener(this)
18 | glancePanel.editor.let {
19 | it.contentComponent.addComponentListener(this)
20 | it.selectionModel.addSelectionListener(this, glancePanel)
21 | it.scrollingModel.addVisibleAreaListener(this, glancePanel)
22 | it.caretModel.addCaretListener(this, glancePanel)
23 | it.markupModel.addMarkupModelListener(glancePanel, GlanceOtherListener(glancePanel))
24 | }
25 | ApplicationManager.getApplication().messageBus.connect(glancePanel).subscribe(SettingsChangeListener.TOPIC, this)
26 | }
27 |
28 | /** CaretListener */
29 | override fun caretPositionChanged(event: CaretEvent) {
30 | if(event.oldPosition.line != event.newPosition.line) {
31 | repaint()
32 | }
33 | }
34 |
35 | override fun caretAdded(event: CaretEvent) = repaint()
36 |
37 | override fun caretRemoved(event: CaretEvent) = repaint()
38 |
39 | /** SelectionListener */
40 | override fun selectionChanged(e: SelectionEvent) = repaint()
41 |
42 | /** ComponentAdapter */
43 | override fun componentResized(componentEvent: ComponentEvent) = glancePanel.run {
44 | if(glancePanel.updateScrollState(visibleChange = true)){
45 | repaint()
46 | }
47 | }
48 |
49 | /** SettingsChangeListener */
50 | override fun onHoveringOriginalScrollBarChanged(value: Boolean) = if (value) glancePanel.hideScrollBarListener.addHideScrollBarListener()
51 | else glancePanel.hideScrollBarListener.removeHideScrollBarListener()
52 |
53 | override fun refreshDataAndImage() = glancePanel.refreshDataAndImage()
54 |
55 | /** VisibleAreaListener */
56 | override fun visibleAreaChanged(e: VisibleAreaEvent) {
57 | if(glancePanel.config.editorSize == EditorSizeEnum.Fit){
58 | if(glancePanel.updateScrollState(e.newRectangle, true)){
59 | repaint()
60 | }
61 | }else {
62 | glancePanel.scrollState.recomputeVisible(e.newRectangle)
63 | repaint()
64 | }
65 | }
66 |
67 | /** HierarchyBoundsListener */
68 | override fun ancestorMoved(e: HierarchyEvent) {}
69 |
70 | override fun ancestorResized(e: HierarchyEvent) {
71 | if (checkWithGlance {config.autoCalWidthInSplitterMode && !config.hoveringToShowScrollBar}){
72 | glancePanel.refresh()
73 | }
74 | }
75 |
76 | /** HierarchyListener */
77 | override fun hierarchyChanged(e: HierarchyEvent) {
78 | if (checkWithGlance {config.autoCalWidthInSplitterMode && !config.hoveringToShowScrollBar} &&
79 | e.changeFlags and HierarchyEvent.PARENT_CHANGED.toLong() != 0L) glancePanel.refresh()
80 | }
81 |
82 | private fun repaint() = if (checkWithGlance()) glancePanel.repaint() else Unit
83 |
84 | private fun checkWithGlance(predicate:(GlancePanel.()->Boolean)? = null) = glancePanel.checkVisible() &&
85 | (predicate == null || predicate.invoke(glancePanel))
86 |
87 | override fun dispose() {
88 | glancePanel.removeHierarchyListener(this)
89 | glancePanel.removeHierarchyBoundsListener(this)
90 | glancePanel.editor.contentComponent.removeComponentListener(this)
91 | }
92 | }
93 |
94 | class GlanceOtherListener(private val glancePanel: GlancePanel) : MarkupModelListener {
95 | override fun afterAdded(highlighter: RangeHighlighterEx) = repaint(highlighter)
96 |
97 | override fun beforeRemoved(highlighter: RangeHighlighterEx) = repaint(highlighter)
98 |
99 | private fun repaint(highlighter: RangeHighlighterEx) {
100 | if (glancePanel.run { highlighter.getMarkupColor() } != null && glancePanel.checkVisible()) {
101 | glancePanel.repaint()
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/listener/HideScrollBarListener.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.listener
2 |
3 | import com.intellij.openapi.Disposable
4 | import com.intellij.util.Alarm
5 | import com.intellij.util.animation.JBAnimator
6 | import com.intellij.util.animation.animation
7 | import com.nasller.codeglance.panel.GlancePanel
8 | import java.awt.Dimension
9 | import java.awt.event.MouseAdapter
10 | import java.awt.event.MouseEvent
11 |
12 | @Suppress("UnstableApiUsage")
13 | class HideScrollBarListener(private val glancePanel: GlancePanel) : MouseAdapter(), Disposable {
14 | private var animationId = -1L
15 | private val animator by lazy {
16 | JBAnimator(glancePanel).apply {
17 | name = "Minimap Width Animator"
18 | period = 5
19 | ignorePowerSaveMode()
20 | }
21 | }
22 | private val alarm by lazy { Alarm(glancePanel) }
23 | private val checkHide
24 | get()= glancePanel.config.hoveringToShowScrollBar && glancePanel.width > 0
25 | && !glancePanel.myPopHandler.isVisible && glancePanel.scrollbar.isNotHoverScrollBar()
26 |
27 | override fun mouseEntered(e: MouseEvent) {
28 | if(glancePanel.width == 0) {
29 | val delay = glancePanel.config.delayHoveringToShowScrollBar
30 | val action = { start(glancePanel.getConfigSize().width) }
31 | if(delay > 0) {
32 | alarm.cancelAllRequests()
33 | alarm.addRequest(action, delay)
34 | } else action.invoke()
35 | }
36 | }
37 |
38 | override fun mouseExited(e: MouseEvent) {
39 | if (glancePanel.config.delayHoveringToShowScrollBar > 0 &&
40 | animator.isRunning(animationId).not() && glancePanel.width == 0){
41 | alarm.cancelAllRequests()
42 | }
43 | }
44 |
45 | fun hideGlanceRequest(delay : Int = 500) {
46 | if (checkHide) {
47 | alarm.cancelAllRequests()
48 | alarm.addRequest({if (checkHide) start(0) },delay)
49 | }
50 | }
51 |
52 | fun isNotRunning() = !(glancePanel.config.hoveringToShowScrollBar && animator.isRunning(animationId))
53 |
54 | private fun start(to: Int) {
55 | if (animator.isRunning(animationId).not()){
56 | animationId = animator.animate(
57 | animation(glancePanel.preferredSize, Dimension(to, 0),glancePanel::setPreferredSize).apply {
58 | duration = 300
59 | runWhenUpdated {
60 | glancePanel.revalidate()
61 | glancePanel.repaint()
62 | }
63 | runWhenScheduled {
64 | showHideOriginScrollBar(to == 0)
65 | }
66 | runWhenExpiredOrCancelled {
67 | if(to != 0) {
68 | hideGlanceRequest(1000)
69 | }
70 | }
71 | }
72 | )
73 | }
74 | }
75 |
76 | private fun showHideOriginScrollBar(show : Boolean){
77 | if(!glancePanel.config.hideOriginalScrollBar){
78 | if(show) glancePanel.editor.scrollPane.verticalScrollBar.apply {
79 | preferredSize = Dimension(glancePanel.originalScrollbarWidth, preferredSize.height)
80 | } else glancePanel.editor.scrollPane.verticalScrollBar.apply {
81 | preferredSize = Dimension(0, preferredSize.height)
82 | }
83 | }
84 | }
85 |
86 | fun addHideScrollBarListener() {
87 | glancePanel.apply {
88 | if (config.hoveringToShowScrollBar && !isDefaultDisable) {
89 | if (!config.hideOriginalScrollBar) editor.scrollPane.verticalScrollBar.addMouseListener(hideScrollBarListener)
90 | else myVcsPanel?.addMouseListener(hideScrollBarListener)
91 | if(checkVisible()) start(0)
92 | }
93 | }
94 | }
95 |
96 | fun removeHideScrollBarListener() {
97 | dispose()
98 | alarm.cancelAllRequests()
99 | animator.stop()
100 | glancePanel.apply {
101 | showHideOriginScrollBar(true)
102 | if(checkVisible()) refresh()
103 | }
104 | }
105 |
106 | override fun dispose() {
107 | if (!glancePanel.config.hideOriginalScrollBar) {
108 | glancePanel.editor.scrollPane.verticalScrollBar.removeMouseListener(this@HideScrollBarListener)
109 | }else {
110 | glancePanel.myVcsPanel?.removeMouseListener(this@HideScrollBarListener)
111 | }
112 | }
113 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/listener/MyVcsListener.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.listener
2 |
3 | import com.intellij.openapi.Disposable
4 | import com.intellij.openapi.editor.event.VisibleAreaEvent
5 | import com.intellij.openapi.editor.event.VisibleAreaListener
6 | import com.intellij.openapi.editor.ex.RangeHighlighterEx
7 | import com.intellij.openapi.editor.impl.event.MarkupModelListener
8 | import com.nasller.codeglance.panel.vcs.MyVcsPanel
9 | import java.awt.event.ComponentAdapter
10 | import java.awt.event.ComponentEvent
11 |
12 | class MyVcsListener(private val myVcsPanel: MyVcsPanel) : ComponentAdapter(), VisibleAreaListener, MarkupModelListener, Disposable {
13 | init {
14 | myVcsPanel.glancePanel.editor.let {
15 | it.contentComponent.addComponentListener(this)
16 | it.scrollingModel.addVisibleAreaListener(this, this)
17 | it.filteredDocumentMarkupModel.addMarkupModelListener(this, this)
18 | }
19 | }
20 | /** ComponentAdapter */
21 | override fun componentResized(componentEvent: ComponentEvent?) = repaint()
22 |
23 | /** VisibleAreaListener */
24 | override fun visibleAreaChanged(e: VisibleAreaEvent) = repaint()
25 |
26 | /** MarkupModelListener */
27 | override fun afterAdded(highlighter: RangeHighlighterEx) = repaint(highlighter)
28 |
29 | override fun beforeRemoved(highlighter: RangeHighlighterEx) = repaint(highlighter)
30 |
31 | private fun repaint(highlighter: RangeHighlighterEx? = null) {
32 | val editor = myVcsPanel.glancePanel.editor
33 | if(myVcsPanel.isVisible && (highlighter == null || (highlighter.isThinErrorStripeMark &&
34 | highlighter.getErrorStripeMarkColor(editor.colorsScheme) != null))) myVcsPanel.repaint()
35 | }
36 |
37 | override fun dispose() {
38 | myVcsPanel.glancePanel.editor.contentComponent.removeComponentListener(this)
39 | }
40 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/panel/scroll/CustomEditorFragmentRenderer.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.panel.scroll
2 |
3 | import com.intellij.codeInsight.daemon.impl.HighlightInfo
4 | import com.intellij.codeInsight.hint.HintManager
5 | import com.intellij.codeInsight.hint.HintManagerImpl
6 | import com.intellij.openapi.editor.colors.EditorColors
7 | import com.intellij.openapi.editor.colors.EditorFontType
8 | import com.intellij.openapi.editor.ex.EditorGutterComponentEx
9 | import com.intellij.openapi.editor.ex.RangeHighlighterEx
10 | import com.intellij.openapi.editor.ex.util.EditorUIUtil
11 | import com.intellij.openapi.editor.impl.EditorImpl
12 | import com.intellij.openapi.ui.MessageType
13 | import com.intellij.openapi.util.text.StringUtil
14 | import com.intellij.ui.*
15 | import com.intellij.ui.scale.JBUIScale
16 | import com.intellij.util.ui.GraphicsUtil
17 | import com.intellij.util.ui.ImageUtil
18 | import com.intellij.util.ui.UIUtil
19 | import com.nasller.codeglance.panel.GlancePanel.Companion.fitLineToEditor
20 | import com.nasller.codeglance.panel.scroll.ScrollBar.Companion.PREVIEW_LINES
21 | import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap
22 | import java.awt.*
23 | import java.awt.geom.*
24 | import java.awt.image.BufferedImage
25 | import javax.swing.JPanel
26 | import javax.swing.SwingUtilities
27 | import kotlin.math.max
28 | import kotlin.math.min
29 |
30 | class CustomEditorFragmentRenderer(private val myEditor:EditorImpl){
31 | private var myVisualLine = 0
32 | private var myStartVisualLine = 0
33 | private var myEndVisualLine = 0
34 | private var isDirty = false
35 | private var myEditorPreviewHint: LightweightHint? = null
36 |
37 | fun getEditorPreviewHint(): LightweightHint? {
38 | return myEditorPreviewHint
39 | }
40 |
41 | private fun update(visualLine: Int) {
42 | myVisualLine = visualLine
43 | if (myVisualLine == -1) return
44 | val oldStartLine = myStartVisualLine
45 | val oldEndLine = myEndVisualLine
46 | myStartVisualLine = fitLineToEditor(myEditor, myVisualLine - PREVIEW_LINES)
47 | myEndVisualLine = fitLineToEditor(myEditor, myVisualLine + PREVIEW_LINES)
48 | isDirty = isDirty or (oldStartLine != myStartVisualLine || oldEndLine != myEndVisualLine)
49 | }
50 |
51 | private fun showEditorHint(point: Point, hintInfo: HintHint) {
52 | val flags = HintManager.HIDE_BY_ANY_KEY or HintManager.HIDE_BY_TEXT_CHANGE or HintManager.HIDE_BY_MOUSEOVER or
53 | HintManager.HIDE_BY_ESCAPE or HintManager.HIDE_BY_SCROLLING
54 | HintManagerImpl.getInstanceImpl().showEditorHint(myEditorPreviewHint!!, myEditor, point, flags, 0, false, hintInfo)
55 | }
56 |
57 | fun show(visualLine: Int, rangeHighlighters: MutableList, hintInfo: HintHint) {
58 | val scrollBar = myEditor.scrollPane.verticalScrollBar
59 | val rootPane = myEditor.component.rootPane ?: SwingUtilities.getWindowAncestor(scrollBar) ?: return
60 | update(visualLine)
61 | rangeHighlighters.sortWith { ex1: RangeHighlighterEx, ex2: RangeHighlighterEx ->
62 | val startPos1 = myEditor.offsetToLogicalPosition(ex1.affectedAreaStartOffset)
63 | val startPos2 = myEditor.offsetToLogicalPosition(ex2.affectedAreaStartOffset)
64 | if (startPos1.line != startPos2.line) return@sortWith 0
65 | startPos1.column - startPos2.column
66 | }
67 | val contentInsets = JBUIScale.scale(2) // BalloonPopupBuilderImpl.myContentInsets
68 | if (myEditorPreviewHint == null) {
69 | val editorFragmentPreviewPanel = EditorFragmentPreviewPanel(contentInsets, rangeHighlighters)
70 | editorFragmentPreviewPanel.putClientProperty(BalloonImpl.FORCED_NO_SHADOW, true)
71 | myEditorPreviewHint = LightweightHint(editorFragmentPreviewPanel)
72 | myEditorPreviewHint!!.setForceLightweightPopup(true)
73 | }
74 | hintInfo.setTextBg(myEditor.backgroundColor)
75 | val borderColor = myEditor.colorsScheme.getAttributes(EditorColors.CODE_LENS_BORDER_COLOR).effectColor
76 | hintInfo.borderColor = borderColor ?: myEditor.colorsScheme.defaultForeground
77 | val point = SwingUtilities.convertPoint(scrollBar, Point(hintInfo.originalPoint), rootPane)
78 | showEditorHint(point, hintInfo)
79 | }
80 |
81 | fun clearHint() {
82 | myEditorPreviewHint = null
83 | }
84 |
85 | fun hideHint() {
86 | myEditorPreviewHint?.hide()
87 | myEditorPreviewHint = null
88 | }
89 |
90 | private inner class EditorFragmentPreviewPanel(private val myContentInsets:Int,
91 | private val myHighlighters:MutableList):JPanel() {
92 |
93 | private var myCacheLevel1: BufferedImage? = null
94 | private var myCacheLevel2: BufferedImage? = null
95 | private var myCacheFromY = 0
96 | private var myCacheToY = 0
97 |
98 | @DirtyUI
99 | override fun getPreferredSize(): Dimension {
100 | var width = (myEditor.gutterComponentEx.width + myEditor.scrollingModel.visibleArea.width
101 | - myEditor.scrollPane.verticalScrollBar.width)
102 | width -= JBUIScale.scale(EDITOR_FRAGMENT_POPUP_BORDER) * 2 + myContentInsets
103 | return Dimension(width - BalloonImpl.POINTER_LENGTH.get(),
104 | min(2 * PREVIEW_LINES * myEditor.lineHeight, myEditor.visualLineToY(myEndVisualLine) - myEditor.visualLineToY(myStartVisualLine)))
105 | }
106 |
107 | @DirtyUI
108 | override fun paintComponent(g: Graphics) {
109 | if (myVisualLine == -1 || myEditor.isDisposed) return
110 | val size = preferredSize
111 | if (size.width <= 0 || size.height <= 0) return
112 | val gutter: EditorGutterComponentEx = myEditor.gutterComponentEx
113 | val content = myEditor.contentComponent
114 | val gutterWidth = gutter.width
115 | val lineHeight = myEditor.lineHeight
116 | if (myCacheLevel2 != null && (myEditor.visualLineToY(myStartVisualLine) < myCacheFromY ||
117 | myEditor.visualLineToY(myEndVisualLine) + lineHeight > myCacheToY)
118 | ) myCacheLevel2 = null
119 | if (myCacheLevel2 == null) {
120 | myCacheFromY = max(0, myEditor.visualLineToY(myVisualLine) - CACHE_PREVIEW_LINES * lineHeight)
121 | myCacheToY = min(myEditor.visualLineToY(myEditor.visibleLineCount), myCacheFromY + (2 * CACHE_PREVIEW_LINES + 1) * lineHeight)
122 | myCacheLevel2 = ImageUtil.createImage(g, size.width, myCacheToY - myCacheFromY, BufferedImage.TYPE_INT_RGB)
123 | val cg = myCacheLevel2!!.createGraphics()
124 | val t = cg.transform
125 | EditorUIUtil.setupAntialiasing(cg)
126 | val lineShift = -myCacheFromY
127 | val shift = JBUIScale.scale(EDITOR_FRAGMENT_POPUP_BORDER) + myContentInsets
128 | val gutterAT = AffineTransform.getTranslateInstance(-shift.toDouble(), lineShift.toDouble())
129 | val contentAT = AffineTransform.getTranslateInstance((gutterWidth - shift).toDouble(), lineShift.toDouble())
130 | gutterAT.preConcatenate(t)
131 | contentAT.preConcatenate(t)
132 | EditorTextField.SUPPLEMENTARY_KEY[myEditor] = true
133 | try {
134 | cg.transform = gutterAT
135 | cg.setClip(0, -lineShift, gutterWidth, myCacheLevel2!!.height)
136 | gutter.paint(cg)
137 | cg.transform = contentAT
138 | cg.setClip(0, -lineShift, content.width, myCacheLevel2!!.height)
139 | content.paint(cg)
140 | } finally {
141 | EditorTextField.SUPPLEMENTARY_KEY[myEditor] = null
142 | }
143 | }
144 | if (myCacheLevel1 == null) {
145 | myCacheLevel1 = ImageUtil.createImage(g, size.width, lineHeight * (2 * PREVIEW_LINES + 1), BufferedImage.TYPE_INT_RGB)
146 | isDirty = true
147 | }
148 | if (isDirty) {
149 | val g2d = myCacheLevel1!!.createGraphics()
150 | val transform = g2d.transform
151 | EditorUIUtil.setupAntialiasing(g2d)
152 | GraphicsUtil.setupAAPainting(g2d)
153 | g2d.color = myEditor.backgroundColor
154 | g2d.fillRect(0, 0, width, height)
155 | val topDisplayedY = max(myEditor.visualLineToY(myStartVisualLine), myEditor.visualLineToY(myVisualLine) - PREVIEW_LINES * lineHeight)
156 | val translateInstance = AffineTransform.getTranslateInstance(gutterWidth.toDouble(), (myCacheFromY - topDisplayedY).toDouble())
157 | translateInstance.preConcatenate(transform)
158 | g2d.transform = translateInstance
159 | UIUtil.drawImage(g2d, myCacheLevel2!!, -gutterWidth, 0, null)
160 | val rightEdges = Int2IntOpenHashMap()
161 | val h = lineHeight - 2
162 | val colorsScheme = myEditor.colorsScheme
163 | val font = UIUtil.getFontWithFallback(colorsScheme.getFont(EditorFontType.PLAIN))
164 | g2d.font = font.deriveFont(font.size * .8f)
165 | for (ex in myHighlighters) {
166 | if (!ex.isValid) continue
167 | val hEndOffset = ex.affectedAreaEndOffset
168 | val tooltip = ex.errorStripeTooltip ?: continue
169 | var s = if (tooltip is HighlightInfo) tooltip.description else tooltip.toString()
170 | if (StringUtil.isEmpty(s)) continue
171 | s = s.replace(" ".toRegex(), " ").replace("\\s+".toRegex(), " ")
172 | s = StringUtil.unescapeXmlEntities(s)
173 | var logicalPosition = myEditor.offsetToLogicalPosition(hEndOffset)
174 | val endOfLineOffset = myEditor.document.getLineEndOffset(logicalPosition.line)
175 | logicalPosition = myEditor.offsetToLogicalPosition(endOfLineOffset)
176 | val placeToShow = myEditor.logicalPositionToXY(logicalPosition)
177 | logicalPosition = myEditor.xyToLogicalPosition(placeToShow) //wraps&folding workaround
178 | placeToShow.x += R * 3 / 2
179 | placeToShow.y -= myCacheFromY - 1
180 | val w = g2d.fontMetrics.stringWidth(s)
181 | var rightEdge = rightEdges[logicalPosition.line]
182 | placeToShow.x = max(placeToShow.x, rightEdge)
183 | rightEdge = max(rightEdge, placeToShow.x + w + 3 * R)
184 | rightEdges.put(logicalPosition.line, rightEdge)
185 | g2d.color = MessageType.WARNING.popupBackground
186 | g2d.fillRoundRect(placeToShow.x, placeToShow.y, w + 2 * R, h, R, R)
187 | g2d.color = JBColor(JBColor.GRAY, Gray._200)
188 | g2d.drawRoundRect(placeToShow.x, placeToShow.y, w + 2 * R, h, R, R)
189 | g2d.color = JBColor.foreground()
190 | g2d.drawString(s, placeToShow.x + R, placeToShow.y + h - g2d.getFontMetrics(g2d.font).descent / 2 - 2)
191 | }
192 | isDirty = false
193 | }
194 | val g2 = g.create() as Graphics2D
195 | try {
196 | GraphicsUtil.setupAAPainting(g2)
197 | g2.clip = RoundRectangle2D.Double(0.0, 0.0, size.width - .5, size.height - .5, 2.0, 2.0)
198 | UIUtil.drawImage(g2, myCacheLevel1!!, 0, 0, this)
199 | if (!JBColor.isBright() && !NewUiValue.isEnabled()) {
200 | //Add glass effect
201 | val s = Rectangle(0, 0, size.width, size.height)
202 | val cx = size.width / 2.0
203 | val rx = size.width / 10.0
204 | val ry = lineHeight * 3 / 2
205 | g2.paint = GradientPaint(0f, 0f, Gray._255.withAlpha(75), 0f, ry.toFloat(), Gray._255.withAlpha(10))
206 | val pseudoMajorAxis = size.width - rx * 9 / 5
207 | val cy = 0.0
208 | val topShape1 = Ellipse2D.Double(cx - rx - pseudoMajorAxis / 2, cy - ry, 2 * rx, (2 * ry).toDouble())
209 | val topShape2 = Ellipse2D.Double(cx - rx + pseudoMajorAxis / 2, cy - ry, 2 * rx, (2 * ry).toDouble())
210 | val topArea = Area(topShape1)
211 | topArea.add(Area(topShape2))
212 | topArea.add(Area(Rectangle2D.Double(cx - pseudoMajorAxis / 2, cy, pseudoMajorAxis, ry.toDouble())))
213 | g2.fill(topArea)
214 | val bottomArea = Area(s)
215 | bottomArea.subtract(topArea)
216 | g2.paint = GradientPaint(0f, (size.height - ry).toFloat(), Gray._0.withAlpha(10), 0f, size.height.toFloat(), Gray._255.withAlpha(30))
217 | g2.fill(bottomArea)
218 | }
219 | } finally {
220 | g2.dispose()
221 | }
222 | }
223 | }
224 |
225 | companion object{
226 | const val EDITOR_FRAGMENT_POPUP_BORDER = 1
227 | private const val CACHE_PREVIEW_LINES = 100
228 | private const val R = 6
229 | }
230 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/panel/scroll/CustomScrollBarPopup.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.panel.scroll
2 |
3 | import com.intellij.codeInsight.CodeInsightBundle
4 | import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
5 | import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings
6 | import com.intellij.codeInsight.daemon.impl.getConfigureHighlightingLevelPopup
7 | import com.intellij.openapi.actionSystem.*
8 | import com.intellij.openapi.editor.EditorBundle
9 | import com.intellij.openapi.keymap.KeymapUtil
10 | import com.intellij.openapi.project.DumbAware
11 | import com.intellij.openapi.project.DumbAwareAction
12 | import com.intellij.openapi.project.DumbAwareToggleAction
13 | import com.intellij.ui.PopupHandler
14 | import com.intellij.ui.PopupMenuListenerAdapter
15 | import com.intellij.ui.awt.RelativePoint
16 | import com.nasller.codeglance.config.SettingsChangePublisher
17 | import com.nasller.codeglance.panel.GlancePanel
18 | import com.nasller.codeglance.util.message
19 | import java.awt.Component
20 | import java.awt.Point
21 | import javax.swing.event.PopupMenuEvent
22 |
23 | class CustomScrollBarPopup(private val glancePanel: GlancePanel) : PopupHandler() {
24 | var isVisible = false
25 |
26 | override fun invokePopup(comp: Component?, x: Int, y: Int) {
27 | val config = glancePanel.config
28 | val actionGroup = object: DefaultActionGroup(
29 | DumbAwareToggleOptionAction(object : ToggleOptionAction.Option {
30 | override fun getName(): String = message("popup.hover.minimap")
31 | override fun isEnabled(): Boolean = config.isRightAligned && !glancePanel.isDefaultDisable
32 | override fun isAlwaysVisible(): Boolean = true
33 | override fun isSelected(): Boolean = config.hoveringToShowScrollBar
34 | override fun setSelected(selected: Boolean) {
35 | config.hoveringToShowScrollBar = selected
36 | SettingsChangePublisher.onHoveringOriginalScrollBarChanged(selected)
37 | }
38 | }),
39 | object : DumbAwareToggleAction(message("popup.showErrorStripesFullLineHighlight")){
40 | override fun isSelected(e: AnActionEvent): Boolean = config.showErrorStripesFullLineHighlight
41 | override fun setSelected(e: AnActionEvent, state: Boolean) {
42 | config.showErrorStripesFullLineHighlight = state
43 | }
44 | override fun getActionUpdateThread() = ActionUpdateThread.BGT
45 | },
46 | object : DumbAwareToggleAction(message("popup.showOtherFullLineHighlight")){
47 | override fun isSelected(e: AnActionEvent): Boolean = config.showOtherFullLineHighlight
48 | override fun setSelected(e: AnActionEvent, state: Boolean) {
49 | config.showOtherFullLineHighlight = state
50 | }
51 | override fun getActionUpdateThread() = ActionUpdateThread.BGT
52 | },
53 | object : DumbAwareToggleAction(message("popup.autoCalculateWidth")){
54 | override fun isSelected(e: AnActionEvent): Boolean = config.autoCalWidthInSplitterMode
55 | override fun setSelected(e: AnActionEvent, state: Boolean) {
56 | config.autoCalWidthInSplitterMode = state
57 | if(!config.hoveringToShowScrollBar) {
58 | SettingsChangePublisher.refreshDataAndImage()
59 | }
60 | }
61 | override fun getActionUpdateThread() = ActionUpdateThread.BGT
62 | },
63 | DumbAwareToggleOptionAction(object : ToggleOptionAction.Option {
64 | override fun getName(): String = message("popup.singleFileVisibleButton")
65 | override fun isEnabled(): Boolean = !config.hoveringToShowScrollBar
66 | override fun isAlwaysVisible(): Boolean = true
67 | override fun isSelected(): Boolean = config.singleFileVisibleButton
68 | override fun setSelected(selected: Boolean) {
69 | config.singleFileVisibleButton = selected
70 | }
71 | })
72 | ){}
73 | glancePanel.psiDocumentManager.getPsiFile(glancePanel.editor.document)?.let {
74 | if (DaemonCodeAnalyzer.getInstance(glancePanel.project).isHighlightingAvailable(it)) {
75 | actionGroup.addSeparator()
76 | actionGroup.add(createGotoGroup())
77 | actionGroup.addSeparator()
78 | actionGroup.add(object : DumbAwareAction(EditorBundle.messagePointer("customize.highlighting.level.menu.item")) {
79 | override fun actionPerformed(e: AnActionEvent) {
80 | val popup = getConfigureHighlightingLevelPopup(e.dataContext)
81 | popup?.show(RelativePoint(comp!!, Point(x, y)))
82 | }
83 | })
84 | }
85 | }
86 | actionGroup.addSeparator()
87 | actionGroup.add(object : DumbAwareToggleAction(message("glance.mouse.wheel.editor.preview")) {
88 | override fun isSelected(e: AnActionEvent): Boolean = config.mouseWheelMoveEditorToolTip
89 | override fun setSelected(e: AnActionEvent, state: Boolean) {
90 | config.mouseWheelMoveEditorToolTip = state
91 | }
92 | override fun getActionUpdateThread() = ActionUpdateThread.BGT
93 | })
94 | actionGroup.add(object : ToggleAction(message("glance.show.editor.preview.popup")) {
95 | override fun isSelected(e: AnActionEvent): Boolean = config.showEditorToolTip
96 | override fun setSelected(e: AnActionEvent, state: Boolean) {
97 | config.showEditorToolTip = state
98 | }
99 | override fun getActionUpdateThread() = ActionUpdateThread.BGT
100 | })
101 | val menu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.RIGHT_EDITOR_GUTTER_POPUP, actionGroup).component
102 | menu.addPopupMenuListener(object :PopupMenuListenerAdapter(){
103 | override fun popupMenuWillBecomeVisible(e: PopupMenuEvent) { isVisible = true }
104 |
105 | override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent) = hideRequest()
106 |
107 | override fun popupMenuCanceled(e: PopupMenuEvent) = hideRequest()
108 |
109 | private fun hideRequest() {
110 | isVisible = false
111 | glancePanel.hideScrollBarListener.hideGlanceRequest()
112 | }
113 | })
114 | menu.show(comp,x,y)
115 | }
116 |
117 | private companion object{
118 | fun createGotoGroup(): DefaultActionGroup {
119 | val shortcut = KeymapUtil.getPrimaryShortcut("GotoNextError")
120 | val shortcutText = if (shortcut != null) " (" + KeymapUtil.getShortcutText(shortcut) + ")" else ""
121 | val gotoGroup = DefaultActionGroup.createPopupGroup {
122 | CodeInsightBundle.message("popup.title.next.error.action.0.goes.through", shortcutText)
123 | }
124 | gotoGroup.add(object : ToggleAction(EditorBundle.message("errors.panel.go.to.errors.first.radio")) {
125 | override fun isSelected(e: AnActionEvent): Boolean =
126 | DaemonCodeAnalyzerSettings.getInstance().isNextErrorActionGoesToErrorsFirst
127 | override fun setSelected(e: AnActionEvent, state: Boolean) {
128 | DaemonCodeAnalyzerSettings.getInstance().isNextErrorActionGoesToErrorsFirst = state
129 | }
130 | override fun getActionUpdateThread() = ActionUpdateThread.BGT
131 |
132 | override fun isDumbAware(): Boolean = true
133 | })
134 | gotoGroup.add(object : ToggleAction(EditorBundle.message("errors.panel.go.to.next.error.warning.radio")) {
135 | override fun isSelected(e: AnActionEvent): Boolean =
136 | !DaemonCodeAnalyzerSettings.getInstance().isNextErrorActionGoesToErrorsFirst
137 | override fun setSelected(e: AnActionEvent, state: Boolean) {
138 | DaemonCodeAnalyzerSettings.getInstance().isNextErrorActionGoesToErrorsFirst = !state
139 | }
140 | override fun isDumbAware(): Boolean = true
141 | override fun getActionUpdateThread() = ActionUpdateThread.BGT
142 | })
143 | return gotoGroup
144 | }
145 | }
146 |
147 | private class DumbAwareToggleOptionAction(option:Option):ToggleOptionAction(option),DumbAware
148 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/panel/scroll/ScrollBar.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.panel.scroll
2 |
3 | import com.intellij.codeInsight.daemon.impl.HighlightInfo
4 | import com.intellij.codeInsight.daemon.impl.HighlightInfoType
5 | import com.intellij.diff.EditorDiffViewer
6 | import com.intellij.openapi.editor.ScrollType
7 | import com.intellij.openapi.editor.VisualPosition
8 | import com.intellij.openapi.editor.ex.MarkupModelEx
9 | import com.intellij.openapi.editor.ex.RangeHighlighterEx
10 | import com.intellij.openapi.ui.popup.Balloon
11 | import com.intellij.ui.ColorUtil
12 | import com.intellij.ui.HintHint
13 | import com.intellij.util.Alarm
14 | import com.intellij.util.ui.JBUI
15 | import com.intellij.util.ui.MouseEventAdapter
16 | import com.nasller.codeglance.CURRENT_EDITOR_DIFF_VIEW
17 | import com.nasller.codeglance.config.CodeGlanceConfig.Companion.setWidth
18 | import com.nasller.codeglance.config.SettingsChangePublisher
19 | import com.nasller.codeglance.config.enums.ClickTypeEnum
20 | import com.nasller.codeglance.config.enums.MouseJumpEnum
21 | import com.nasller.codeglance.panel.GlancePanel
22 | import com.nasller.codeglance.panel.GlancePanel.Companion.fitLineToEditor
23 | import com.nasller.codeglance.util.Util
24 | import java.awt.AlphaComposite
25 | import java.awt.Cursor
26 | import java.awt.Graphics2D
27 | import java.awt.Shape
28 | import java.awt.event.MouseAdapter
29 | import java.awt.event.MouseEvent
30 | import java.awt.event.MouseWheelEvent
31 | import java.awt.geom.Path2D
32 | import java.awt.geom.Rectangle2D
33 | import java.awt.geom.RoundRectangle2D
34 | import javax.swing.SwingUtilities
35 | import kotlin.math.max
36 | import kotlin.math.min
37 | import kotlin.math.roundToInt
38 |
39 | class ScrollBar(private val glancePanel: GlancePanel) : MouseAdapter() {
40 | private val config
41 | get() = glancePanel.config
42 | private val editor
43 | get() = glancePanel.editor
44 | private val scrollState
45 | get() = glancePanel.scrollState
46 | private val alarm = Alarm(glancePanel)
47 | private val myEditorFragmentRenderer = CustomEditorFragmentRenderer(editor)
48 | private var visibleRectAlpha = DEFAULT_ALPHA
49 | set(value) {
50 | if (field != value) {
51 | field = value
52 | glancePanel.repaint()
53 | }
54 | }
55 | private var hovering = false
56 | //矩形y轴
57 | private val vOffset: Int
58 | get() = scrollState.viewportStart - scrollState.visibleStart
59 | //视图滚动
60 | private var myWheelAccumulator = 0
61 | private var myLastVisualLine = 0
62 | //宽带调整鼠标事件
63 | private var resizing = false
64 | private var resizeStart: Int = 0
65 | private var widthStart: Int = 0
66 | //拖拽鼠标事件
67 | private var dragging = false
68 | private var dragStart: Int = 0
69 | private var dragStartDelta: Int = 0
70 |
71 | init {
72 | glancePanel.addMouseListener(this)
73 | glancePanel.addMouseWheelListener(this)
74 | glancePanel.addMouseMotionListener(this)
75 | glancePanel.addMouseListener(glancePanel.myPopHandler)
76 | }
77 |
78 | fun paint(gfx: Graphics2D) {
79 | gfx.color = ColorUtil.fromHex(config.viewportColor)
80 | gfx.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, visibleRectAlpha)
81 | if(scrollState.viewportHeight > MIN_VIEWPORT_HEIGHT) {
82 | gfx.fillRoundRect(0, vOffset, glancePanel.width, scrollState.viewportHeight,5, 5)
83 | }else {
84 | gfx.fillRect(0, vOffset, glancePanel.width, scrollState.viewportHeight)
85 | }
86 | getBorderShape(vOffset, glancePanel.width, scrollState.viewportHeight, config.viewportBorderThickness)?.let {
87 | gfx.composite = GlancePanel.srcOver
88 | gfx.color = ColorUtil.fromHex(config.viewportBorderColor)
89 | gfx.fill(it)
90 | }
91 | }
92 |
93 | fun clear() = myEditorFragmentRenderer.clearHint()
94 |
95 | override fun mouseEntered(e: MouseEvent) {
96 | hovering = true
97 | }
98 |
99 | override fun mousePressed(e: MouseEvent) {
100 | if (e.button != MouseEvent.BUTTON1) return
101 | when {
102 | isInResizeGutter(e.x) -> {
103 | resizing = true
104 | resizeStart = e.xOnScreen
105 | widthStart = glancePanel.width
106 | }
107 | isInRect(e.y) || MouseJumpEnum.NONE == config.jumpOnMouseDown -> dragMove(e.y)
108 | MouseJumpEnum.MOUSE_DOWN == config.jumpOnMouseDown -> jumpToLineAt(e) {
109 | visibleRectAlpha = DEFAULT_ALPHA
110 | glancePanel.cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)
111 | dragMove(e.y)
112 | }
113 | }
114 | }
115 |
116 | override fun mouseDragged(e: MouseEvent) {
117 | if (resizing) {
118 | val newWidth = if(editor.getUserData(GlancePanel.CURRENT_GLANCE_PLACE_INDEX) == GlancePanel.PlaceIndex.Left)
119 | widthStart + e.xOnScreen - resizeStart
120 | else widthStart + resizeStart - e.xOnScreen
121 | editor.editorKind.setWidth(newWidth.coerceIn(Util.MIN_WIDTH, Util.MAX_WIDTH))
122 | resizeGlancePanel(false)
123 | } else if (dragging) {
124 | val delta = (dragStartDelta + (e.y - dragStart)).toFloat()
125 | val newPos = if (scrollState.documentHeight < scrollState.visibleHeight)
126 | // Full doc fits into minimap, use exact value
127 | delta
128 | else scrollState.run {
129 | // Who says algebra is useless?
130 | // delta = newPos - ((newPos / (documentHeight - viewportHeight + 1)) * (documentHeight - visibleHeight + 1))
131 | // ...Solve for newPos...
132 | delta * (documentHeight - viewportHeight + 1) / (visibleHeight - viewportHeight)
133 | }
134 | editor.scrollPane.verticalScrollBar.value = (newPos / scrollState.scale).roundToInt()
135 | } else if (MouseJumpEnum.MOUSE_UP == config.jumpOnMouseDown) showMyEditorPreviewHint(e)
136 | }
137 |
138 | override fun mouseReleased(e: MouseEvent) {
139 | val action = {
140 | resizeGlancePanel(true)
141 | updateAlpha(e.y)
142 | dragging = false
143 | resizing = false
144 | hoveringOverAndHideScrollBar(e)
145 | }
146 | if (MouseJumpEnum.MOUSE_UP == config.jumpOnMouseDown && !dragging && !resizing && !e.isPopupTrigger) {
147 | jumpToLineAt(e, action)
148 | }else {
149 | editor.scrollingModel.runActionOnScrollingFinished(action)
150 | }
151 | }
152 |
153 | override fun mouseMoved(e: MouseEvent) {
154 | val isInRect = updateAlpha(e.y)
155 | if (isInResizeGutter(e.x)) {
156 | glancePanel.cursor = Cursor(Cursor.W_RESIZE_CURSOR)
157 | } else if (!isInRect && !resizing && !dragging && showMyEditorPreviewHint(e)) {
158 | return
159 | }
160 | hideMyEditorPreviewHint()
161 | }
162 |
163 | override fun mouseExited(e: MouseEvent) {
164 | hovering = false
165 | if (!dragging) visibleRectAlpha = DEFAULT_ALPHA
166 | hideMyEditorPreviewHint()
167 | hoveringOverAndHideScrollBar(e)
168 | }
169 |
170 | override fun mouseWheelMoved(e: MouseWheelEvent) {
171 | val hasEditorPreviewHint = myEditorFragmentRenderer.getEditorPreviewHint() != null
172 | if (config.mouseWheelMoveEditorToolTip && hasEditorPreviewHint){
173 | val units = e.unitsToScroll
174 | if (units == 0) return
175 | if (myLastVisualLine < editor.visibleLineCount - 1 && units > 0 || myLastVisualLine > 0 && units < 0) {
176 | myWheelAccumulator += units
177 | }
178 | showToolTipByMouseMove(e)
179 | } else {
180 | if(hasEditorPreviewHint) hideMyEditorPreviewHint()
181 | MouseEventAdapter.redispatch(e,editor.contentComponent)
182 | }
183 | }
184 |
185 | private fun resizeGlancePanel(refreshImage: Boolean) {
186 | if(!resizing) return
187 | val action = {it: GlancePanel-> if(refreshImage) SettingsChangePublisher.refreshDataAndImage() else it.refresh() }
188 | val diffViewer = editor.getUserData(CURRENT_EDITOR_DIFF_VIEW)
189 | if (diffViewer != null) {
190 | if (diffViewer is EditorDiffViewer) {
191 | diffViewer.editors.mapNotNull { it.getUserData(GlancePanel.CURRENT_GLANCE) }.forEach(action)
192 | }
193 | } else {
194 | action(glancePanel)
195 | }
196 | }
197 |
198 |
199 | private fun dragMove(y: Int) {
200 | dragging = true
201 | visibleRectAlpha = DRAG_ALPHA
202 | dragStart = y
203 | dragStartDelta = vOffset
204 | }
205 |
206 | private fun showMyEditorPreviewHint(e: MouseEvent): Boolean {
207 | return if(config.showEditorToolTip && e.x > 10 && e.y < scrollState.drawHeight) {
208 | if (myEditorFragmentRenderer.getEditorPreviewHint() == null) {
209 | alarm.cancelAllRequests()
210 | alarm.addRequest({
211 | if (myEditorFragmentRenderer.getEditorPreviewHint() == null) showToolTipByMouseMove(e)
212 | }, 400)
213 | } else showToolTipByMouseMove(e)
214 | true
215 | }else false
216 | }
217 |
218 | private fun showToolTipByMouseMove(e: MouseEvent) {
219 | val y = e.y + myWheelAccumulator
220 | val visualLine = fitLineToEditor(editor, glancePanel.getMyRenderVisualLine(y + scrollState.visibleStart))
221 | myLastVisualLine = visualLine
222 | val point = SwingUtilities.convertPoint(glancePanel, 0, if (y > 0 && y < scrollState.drawHeight) y else if (y <= 0) 0 else scrollState.drawHeight,
223 | editor.scrollPane.verticalScrollBar)
224 | val me = MouseEvent(editor.scrollPane.verticalScrollBar, e.id, e.`when`, e.modifiersEx, 1, point.y, e.clickCount, e.isPopupTrigger)
225 | val highlighters = mutableListOf()
226 | collectRangeHighlighters(editor.markupModel, visualLine, highlighters)
227 | collectRangeHighlighters(editor.filteredDocumentMarkupModel, visualLine, highlighters)
228 | myEditorFragmentRenderer.show(visualLine, highlighters, createHint(me))
229 | }
230 |
231 | private fun collectRangeHighlighters(markupModel: MarkupModelEx, visualLine: Int, highlighters: MutableCollection) {
232 | val startOffset: Int = getOffset(fitLineToEditor(editor, visualLine - PREVIEW_LINES), true)
233 | val endOffset: Int = getOffset(fitLineToEditor(editor, visualLine + PREVIEW_LINES), false)
234 | markupModel.processRangeHighlightersOverlappingWith(startOffset, endOffset) { highlighter ->
235 | val tooltip = highlighter.errorStripeTooltip
236 | if (tooltip != null && !(tooltip is HighlightInfo && tooltip.type === HighlightInfoType.TODO) &&
237 | highlighter.startOffset < endOffset && highlighter.endOffset > startOffset &&
238 | highlighter.getErrorStripeMarkColor(editor.colorsScheme) != null) {
239 | highlighters.add(highlighter)
240 | }
241 | true
242 | }
243 | }
244 |
245 | private fun getOffset(visualLine: Int, startLine: Boolean): Int =
246 | editor.visualPositionToOffset(VisualPosition(visualLine, if (startLine) 0 else Int.MAX_VALUE))
247 |
248 | private fun hideMyEditorPreviewHint() {
249 | alarm.cancelAllRequests()
250 | myEditorFragmentRenderer.hideHint()
251 | myWheelAccumulator = 0
252 | myLastVisualLine = 0
253 | }
254 |
255 | private fun isInResizeGutter(x: Int): Boolean =
256 | if (config.locked || config.hoveringToShowScrollBar || glancePanel.isInSplitter()) false else {
257 | if(editor.getUserData(GlancePanel.CURRENT_GLANCE_PLACE_INDEX) == GlancePanel.PlaceIndex.Left)
258 | x in glancePanel.width - 7 .. glancePanel.width
259 | else x in 0..7
260 | }
261 |
262 | private fun isInRect(y: Int): Boolean = y in vOffset..(vOffset + scrollState.viewportHeight)
263 |
264 | private fun updateAlpha(y: Int): Boolean {
265 | return when {
266 | isInRect(y) -> {
267 | visibleRectAlpha = HOVER_ALPHA
268 | glancePanel.cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)
269 | true
270 | }
271 | else -> {
272 | visibleRectAlpha = DEFAULT_ALPHA
273 | glancePanel.cursor = if (MouseJumpEnum.NONE != config.jumpOnMouseDown && y < scrollState.drawHeight) Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
274 | else Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)
275 | false
276 | }
277 | }
278 | }
279 |
280 | private fun hoveringOverAndHideScrollBar(e: MouseEvent) {
281 | if (!e.isPopupTrigger) glancePanel.hideScrollBarListener.hideGlanceRequest()
282 | }
283 |
284 | private fun jumpToLineAt(e: MouseEvent, action: () -> Unit) {
285 | hideMyEditorPreviewHint()
286 | val visualLine = if(config.clickType == ClickTypeEnum.CODE_POSITION){
287 | fitLineToEditor(editor, glancePanel.getMyRenderVisualLine(e.y + scrollState.visibleStart))
288 | }else{
289 | if(scrollState.drawHeight == scrollState.visibleHeight){
290 | editor.yToVisualLine((e.y / scrollState.visibleHeight.toFloat() * editor.contentComponent.height).roundToInt())
291 | }else{
292 | fitLineToEditor(editor, glancePanel.getMyRenderVisualLine(e.y + scrollState.visibleStart))
293 | }
294 | }
295 | val visualPosition = VisualPosition(visualLine, e.x)
296 | if(e.isShiftDown){
297 | editor.selectionModel.setSelection(editor.caretModel.offset, editor.visualPositionToOffset(visualPosition))
298 | }
299 | if(config.moveOnly.not()){
300 | editor.caretModel.moveToVisualPosition(visualPosition)
301 | editor.scrollingModel.scrollToCaret(ScrollType.CENTER)
302 | }else {
303 | editor.scrollingModel.scrollTo(editor.visualToLogicalPosition(visualPosition), ScrollType.CENTER)
304 | }
305 | editor.scrollingModel.runActionOnScrollingFinished(action)
306 | }
307 |
308 | fun isNotHoverScrollBar() = !hovering && !dragging && !resizing
309 |
310 | companion object {
311 | private const val DEFAULT_ALPHA = 0.15f
312 | private const val HOVER_ALPHA = 0.25f
313 | private const val DRAG_ALPHA = 0.35f
314 | private const val MIN_VIEWPORT_HEIGHT = 20
315 | val PREVIEW_LINES = max(2, min(25, Integer.getInteger("preview.lines", 5)))
316 |
317 | private fun createHint(me: MouseEvent): HintHint = HintHint(me)
318 | .setAwtTooltip(true)
319 | .setPreferredPosition(Balloon.Position.atLeft)
320 | .setBorderInsets(JBUI.insets(CustomEditorFragmentRenderer.EDITOR_FRAGMENT_POPUP_BORDER))
321 | .setShowImmediately(true)
322 | .setAnimationEnabled(false)
323 |
324 | private fun getBorderShape(y: Int, width: Int, height: Int, thickness: Int): Shape? {
325 | if (width <= 0 || height <= 0 || thickness <= 0) return null
326 | val thicknessSize = if(height > MIN_VIEWPORT_HEIGHT) thickness else 1
327 | val outer = if(height > MIN_VIEWPORT_HEIGHT) RoundRectangle2D.Float(0f, y.toFloat(), width.toFloat(), height.toFloat(), 5f, 5f)
328 | else Rectangle2D.Float(0f, y.toFloat(), width.toFloat(), height.toFloat())
329 | val doubleThickness = 2 * thicknessSize.toFloat()
330 | if (width <= doubleThickness || height <= doubleThickness) return outer
331 | val inner = Rectangle2D.Float(0f + thicknessSize.toFloat(), y + thicknessSize.toFloat(), width - doubleThickness, height - doubleThickness)
332 | val path = Path2D.Float(Path2D.WIND_EVEN_ODD)
333 | path.append(outer, false)
334 | path.append(inner, false)
335 | return path
336 | }
337 | }
338 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/panel/vcs/MyVcsPanel.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.panel.vcs
2 |
3 | import com.intellij.openapi.editor.ScrollType
4 | import com.intellij.openapi.editor.VisualPosition
5 | import com.intellij.openapi.util.Disposer
6 | import com.intellij.util.ui.MouseEventAdapter
7 | import com.nasller.codeglance.listener.MyVcsListener
8 | import com.nasller.codeglance.panel.GlancePanel
9 | import com.nasller.codeglance.panel.GlancePanel.Companion.fitLineToEditor
10 | import java.awt.Cursor
11 | import java.awt.Dimension
12 | import java.awt.Graphics
13 | import java.awt.Graphics2D
14 | import java.awt.event.MouseAdapter
15 | import java.awt.event.MouseEvent
16 | import java.awt.event.MouseWheelEvent
17 | import javax.swing.JPanel
18 |
19 | class MyVcsPanel(val glancePanel: GlancePanel) : JPanel(){
20 | private val editor
21 | get() = glancePanel.editor
22 | init{
23 | Disposer.register(glancePanel, MyVcsListener(this))
24 | val mouseHandler = MouseHandler()
25 | addMouseListener(mouseHandler)
26 | addMouseWheelListener(mouseHandler)
27 | addMouseMotionListener(mouseHandler)
28 | addMouseListener(glancePanel.myPopHandler)
29 | preferredSize = Dimension(8,0)
30 | isOpaque = false
31 | }
32 |
33 | override fun paintComponent(gfx: Graphics) = glancePanel.run {
34 | super.paintComponent(gfx)
35 | if(glancePanel.isReleased) return
36 | with(gfx as Graphics2D){ paintVcs(getVisibleRangeOffset(),this@MyVcsPanel.width) }
37 | }
38 |
39 | private inner class MouseHandler : MouseAdapter() {
40 | private var hoverVcsLine: Int? = null
41 |
42 | override fun mouseClicked(e: MouseEvent) {
43 | hoverVcsLine?.let {
44 | editor.caretModel.moveToVisualPosition(VisualPosition(it,0))
45 | editor.scrollingModel.scrollToCaret(ScrollType.CENTER)
46 | }
47 | }
48 |
49 | override fun mouseMoved(e: MouseEvent) {
50 | if(glancePanel.config.showVcsHighlight.not()) return
51 | val rangeOffset = glancePanel.getVisibleRangeOffset()
52 | val process = editor.filteredDocumentMarkupModel.processRangeHighlightersOverlappingWith(rangeOffset.from,rangeOffset.to) {
53 | if (it.isThinErrorStripeMark) it.getErrorStripeMarkColor(editor.colorsScheme)?.apply {
54 | val visualLine = fitLineToEditor(editor,glancePanel.getMyRenderVisualLine(e.y + glancePanel.scrollState.visibleStart))
55 | val startVisual = editor.offsetToVisualLine(it.startOffset)
56 | if (visualLine in startVisual..editor.offsetToVisualLine(it.endOffset)) {
57 | cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
58 | hoverVcsLine = startVisual
59 | return@processRangeHighlightersOverlappingWith false
60 | }
61 | }
62 | return@processRangeHighlightersOverlappingWith true
63 | }
64 | if(process) {
65 | cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)
66 | hoverVcsLine = null
67 | }
68 | }
69 |
70 | override fun mouseExited(e: MouseEvent) {
71 | cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)
72 | hoverVcsLine = null
73 | }
74 |
75 | override fun mouseWheelMoved(e: MouseWheelEvent) = MouseEventAdapter.redispatch(e,editor.contentComponent)
76 | }
77 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/render/CharacterWeight.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.render
2 |
3 | fun getBottomWeight(c : Int) : Float {
4 | when (c) {
5 | 33 -> return 0.1779f // = '!'
6 | 34 -> return 0.0000f // = '"'
7 | 35 -> return 0.5714f // = '#'
8 | 36 -> return 0.6160f // = '$'
9 | 37 -> return 0.5423f // = '%'
10 | 38 -> return 0.7204f // = '&'
11 | 39 -> return 0.0000f // = '''
12 | 40 -> return 0.5134f // = '('
13 | 41 -> return 0.5089f // = ')'
14 | 42 -> return 0.1042f // = '*'
15 | 43 -> return 0.2286f // = '+'
16 | 44 -> return 0.2983f // = ','
17 | 45 -> return 0.0000f // = '-'
18 | 46 -> return 0.1777f // = '.'
19 | 47 -> return 0.5943f // = '/'
20 | 48 -> return 0.5831f // = '0'
21 | 49 -> return 0.5143f // = '1'
22 | 50 -> return 0.4724f // = '2'
23 | 51 -> return 0.4715f // = '3'
24 | 52 -> return 0.5712f // = '4'
25 | 53 -> return 0.4421f // = '5'
26 | 54 -> return 0.6080f // = '6'
27 | 55 -> return 0.3399f // = '7'
28 | 56 -> return 0.6481f // = '8'
29 | 57 -> return 0.4235f // = '9'
30 | 58 -> return 0.1777f // = ':'
31 | 59 -> return 0.2983f // = ';'
32 | 60 -> return 0.3509f // = '<'
33 | 61 -> return 0.2857f // = '='
34 | 62 -> return 0.3942f // = '>'
35 | 63 -> return 0.1766f // = '?'
36 | 64 -> return 0.7926f // = '@'
37 | 65 -> return 0.6156f // = 'A'
38 | 66 -> return 0.6830f // = 'B'
39 | 67 -> return 0.4296f // = 'C'
40 | 68 -> return 0.6364f // = 'D'
41 | 69 -> return 0.5143f // = 'E'
42 | 70 -> return 0.3429f // = 'F'
43 | 71 -> return 0.6476f // = 'G'
44 | 72 -> return 0.6857f // = 'H'
45 | 73 -> return 0.5714f // = 'I'
46 | 74 -> return 0.4083f // = 'J'
47 | 75 -> return 0.6617f // = 'K'
48 | 76 -> return 0.5143f // = 'L'
49 | 77 -> return 0.3139f // = 'M'
50 | 78 -> return 0.5416f // = 'N'
51 | 79 -> return 0.5759f // = 'O'
52 | 80 -> return 0.4576f // = 'P'
53 | 81 -> return 0.7689f // = 'Q'
54 | 82 -> return 0.6761f // = 'R'
55 | 83 -> return 0.4923f // = 'S'
56 | 84 -> return 0.3429f // = 'T'
57 | 85 -> return 0.6283f // = 'U'
58 | 86 -> return 0.3516f // = 'V'
59 | 87 -> return 0.5564f // = 'W'
60 | 88 -> return 0.5479f // = 'X'
61 | 89 -> return 0.3460f // = 'Y'
62 | 90 -> return 0.4715f // = 'Z'
63 | 91 -> return 0.6857f // = '['
64 | 92 -> return 0.5941f // = '\'
65 | 93 -> return 0.6857f // = ']'
66 | 94 -> return 0.0000f // = '^'
67 | 95 -> return 0.3429f // = '_'
68 | 96 -> return 0.0000f // = '`'
69 | 97 -> return 0.6853f // = 'a'
70 | 98 -> return 0.6431f // = 'b'
71 | 99 -> return 0.3966f // = 'c'
72 | 100 -> return 0.6521f // = 'd'
73 | 101 -> return 0.6844f // = 'e'
74 | 102 -> return 0.3429f // = 'f'
75 | 103 -> return 0.9927f // = 'g'
76 | 104 -> return 0.6857f // = 'h'
77 | 105 -> return 0.4052f // = 'i'
78 | 106 -> return 0.6463f // = 'j'
79 | 107 -> return 0.6015f // = 'k'
80 | 108 -> return 0.4013f // = 'l'
81 | 109 -> return 0.4000f // = 'm'
82 | 110 -> return 0.6857f // = 'n'
83 | 111 -> return 0.5799f // = 'o'
84 | 112 -> return 0.9109f // = 'p'
85 | 113 -> return 0.9154f // = 'q'
86 | 114 -> return 0.3429f // = 'r'
87 | 115 -> return 0.5450f // = 's'
88 | 116 -> return 0.4627f // = 't'
89 | 117 -> return 0.6678f // = 'u'
90 | 118 -> return 0.3538f // = 'v'
91 | 119 -> return 0.6884f // = 'w'
92 | 120 -> return 0.5461f // = 'x'
93 | 121 -> return 0.6521f // = 'y'
94 | 122 -> return 0.5183f // = 'z'
95 | 123 -> return 0.6649f // = '{'
96 | 124 -> return 0.5714f // = '|'
97 | 125 -> return 0.6136f // = '}'
98 | 126 -> return 0.1950f // = '~'
99 | else -> return 0.4f
100 | }
101 | }
102 |
103 | fun getTopWeight(c : Int) : Float {
104 | when (c) {
105 | 33 -> return 0.2816f // = '!'
106 | 34 -> return 0.4865f // = '"'
107 | 35 -> return 0.4769f // = '#'
108 | 36 -> return 0.5066f // = '$'
109 | 37 -> return 0.4510f // = '%'
110 | 38 -> return 0.2971f // = '&'
111 | 39 -> return 0.3274f // = '''
112 | 40 -> return 0.4275f // = '('
113 | 41 -> return 0.4273f // = ')'
114 | 42 -> return 0.3643f // = '*'
115 | 43 -> return 0.1905f // = '+'
116 | 44 -> return 0.0000f // = ','
117 | 45 -> return 0.0000f // = '-'
118 | 46 -> return 0.0000f // = '.'
119 | 47 -> return 0.3961f // = '/'
120 | 48 -> return 0.5221f // = '0'
121 | 49 -> return 0.4045f // = '1'
122 | 50 -> return 0.3419f // = '2'
123 | 51 -> return 0.3830f // = '3'
124 | 52 -> return 0.4157f // = '4'
125 | 53 -> return 0.4060f // = '5'
126 | 54 -> return 0.3501f // = '6'
127 | 55 -> return 0.4228f // = '7'
128 | 56 -> return 0.5798f // = '8'
129 | 57 -> return 0.5005f // = '9'
130 | 58 -> return 0.1481f // = ':'
131 | 59 -> return 0.1481f // = ';'
132 | 60 -> return 0.3989f // = '<'
133 | 61 -> return 0.2381f // = '='
134 | 62 -> return 0.4476f // = '>'
135 | 63 -> return 0.3414f // = '?'
136 | 64 -> return 0.3910f // = '@'
137 | 65 -> return 0.3343f // = 'A'
138 | 66 -> return 0.5707f // = 'B'
139 | 67 -> return 0.3498f // = 'C'
140 | 68 -> return 0.5326f // = 'D'
141 | 69 -> return 0.4286f // = 'E'
142 | 70 -> return 0.3810f // = 'F'
143 | 71 -> return 0.3488f // = 'G'
144 | 72 -> return 0.5714f // = 'H'
145 | 73 -> return 0.4762f // = 'I'
146 | 74 -> return 0.3810f // = 'J'
147 | 75 -> return 0.5376f // = 'K'
148 | 76 -> return 0.2857f // = 'L'
149 | 77 -> return 0.4928f // = 'M'
150 | 78 -> return 0.5173f // = 'N'
151 | 79 -> return 0.4781f // = 'O'
152 | 80 -> return 0.5619f // = 'P'
153 | 81 -> return 0.4775f // = 'Q'
154 | 82 -> return 0.5716f // = 'R'
155 | 83 -> return 0.3826f // = 'S'
156 | 84 -> return 0.4762f // = 'T'
157 | 85 -> return 0.5714f // = 'U'
158 | 86 -> return 0.3707f // = 'V'
159 | 87 -> return 0.3128f // = 'W'
160 | 88 -> return 0.4452f // = 'X'
161 | 89 -> return 0.4495f // = 'Y'
162 | 90 -> return 0.4129f // = 'Z'
163 | 91 -> return 0.4762f // = '['
164 | 92 -> return 0.3959f // = '\'
165 | 93 -> return 0.4762f // = ']'
166 | 94 -> return 0.3647f // = '^'
167 | 95 -> return 0.0000f // = '_'
168 | 96 -> return 0.1359f // = '`'
169 | 97 -> return 0.2439f // = 'a'
170 | 98 -> return 0.5729f // = 'b'
171 | 99 -> return 0.2607f // = 'c'
172 | 100 -> return 0.5729f // = 'd'
173 | 101 -> return 0.3935f // = 'e'
174 | 102 -> return 0.6232f // = 'f'
175 | 103 -> return 0.3488f // = 'g'
176 | 104 -> return 0.5744f // = 'h'
177 | 105 -> return 0.3399f // = 'i'
178 | 106 -> return 0.3399f // = 'j'
179 | 107 -> return 0.5328f // = 'k'
180 | 108 -> return 0.4762f // = 'l'
181 | 109 -> return 0.3447f // = 'm'
182 | 110 -> return 0.4062f // = 'n'
183 | 111 -> return 0.2965f // = 'o'
184 | 112 -> return 0.3511f // = 'p'
185 | 113 -> return 0.3500f // = 'q'
186 | 114 -> return 0.3057f // = 'r'
187 | 115 -> return 0.2893f // = 's'
188 | 116 -> return 0.5238f // = 't'
189 | 117 -> return 0.3810f // = 'u'
190 | 118 -> return 0.2458f // = 'v'
191 | 119 -> return 0.4218f // = 'w'
192 | 120 -> return 0.3266f // = 'x'
193 | 121 -> return 0.3613f // = 'y'
194 | 122 -> return 0.3417f // = 'z'
195 | 123 -> return 0.4424f // = '{'
196 | 124 -> return 0.3810f // = '|'
197 | 125 -> return 0.4006f // = '}'
198 | 126 -> return 0.0000f // = '~'
199 | else -> return 0.4f
200 | }
201 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/render/EmptyMinimap.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.render
2 |
3 | import com.intellij.openapi.application.invokeLater
4 | import com.intellij.openapi.editor.*
5 | import com.intellij.openapi.editor.event.DocumentEvent
6 | import com.intellij.openapi.editor.ex.EditorEx
7 | import com.intellij.openapi.editor.ex.FoldingListener
8 | import com.intellij.openapi.editor.ex.RangeHighlighterEx
9 | import com.intellij.openapi.editor.ex.util.EditorUtil
10 | import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter
11 | import com.intellij.util.DocumentUtil
12 | import com.intellij.util.Range
13 | import com.nasller.codeglance.panel.GlancePanel
14 | import com.nasller.codeglance.util.MySoftReference
15 | import com.nasller.codeglance.util.Util.isMarkAttributes
16 | import java.awt.RenderingHints
17 | import java.awt.image.BufferedImage
18 | import java.beans.PropertyChangeEvent
19 | import kotlin.math.roundToInt
20 |
21 | @Suppress("UnstableApiUsage")
22 | class EmptyMinimap (glancePanel: GlancePanel) : BaseMinimap(glancePanel) {
23 | override val rangeList: MutableList>> = mutableListOf()
24 | private var imgReference = lazy {
25 | MySoftReference.create(getBufferedImage(scrollState), editor.editorKind != EditorKind.MAIN_EDITOR)
26 | }
27 | init { makeListener() }
28 |
29 | override fun getImageOrUpdate(): BufferedImage? {
30 | val img = imgReference.value.get()
31 | if(img == null) updateMinimapImage()
32 | return img
33 | }
34 |
35 | override fun updateMinimapImage(canUpdate: Boolean){
36 | if (canUpdate && lock.compareAndSet(false,true)) {
37 | val action = Runnable {
38 | invokeLater(modalityState) {
39 | try {
40 | update()
41 | } finally {
42 | lock.set(false)
43 | glancePanel.repaint()
44 | }
45 | }
46 | }
47 | if(glancePanel.markState.hasMarkHighlight()){
48 | glancePanel.psiDocumentManager.performForCommittedDocument(editor.document, action)
49 | }else action.run()
50 | }
51 | }
52 |
53 | private fun getMinimapImage(): BufferedImage? {
54 | var curImg = imgReference.value.get()
55 | if (curImg == null || curImg.height < scrollState.documentHeight || curImg.width < glancePanel.width) {
56 | curImg?.flush()
57 | curImg = getBufferedImage(scrollState)
58 | imgReference = lazyOf(MySoftReference.create(curImg, editor.editorKind != EditorKind.MAIN_EDITOR))
59 | }
60 | return if(editor.isDisposed) return null else curImg
61 | }
62 |
63 | private fun update() {
64 | val curImg = getMinimapImage() ?: return
65 | if(rangeList.isNotEmpty()) rangeList.clear()
66 | val text = editor.document.immutableCharSequence
67 | val graphics = curImg.createGraphics().apply {
68 | setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
69 | composite = GlancePanel.CLEAR
70 | fillRect(0, 0, curImg.width, curImg.height)
71 | }
72 | if(text.isEmpty()) {
73 | graphics.dispose()
74 | return
75 | }
76 | val hlIter = editor.highlighter.createIterator(0).run {
77 | if(isLogFile) IdeLogFileHighlightDelegate(editor.document,this) else this
78 | }
79 | val softWrapEnable = editor.softWrapModel.isSoftWrappingEnabled
80 | val hasBlockInlay = editor.inlayModel.hasBlockElements()
81 | var y = 0.0
82 | var skipY = 0.0
83 | val moveCharIndex = moveCharIndex@{ code: Int, enterAction: (()->Unit)? ->
84 | if(code != 10) {
85 | return@moveCharIndex
86 | }
87 | y += scrollState.pixelsPerLine
88 | enterAction?.invoke()
89 | }
90 | val highlight = makeMarkHighlight(text, graphics)
91 | loop@ while (!hlIter.atEnd()) {
92 | val start = hlIter.start
93 | if(start > text.length) break@loop
94 | y = editor.document.getLineNumber(start) * scrollState.pixelsPerLine + skipY
95 | val region = editor.foldingModel.getCollapsedRegionAtOffset(start)
96 | if (region != null) {
97 | val startLineNumber = editor.document.getLineNumber(region.startOffset)
98 | val endOffset = region.endOffset
99 | val foldLine = editor.document.getLineNumber(endOffset) - startLineNumber
100 | if(region !is CustomFoldRegion){
101 | skipY -= foldLine * scrollState.pixelsPerLine
102 | do hlIter.advance() while (!hlIter.atEnd() && hlIter.start < endOffset)
103 | } else {
104 | //jump over the fold line
105 | val heightLine = region.heightInPixels * scrollState.scale
106 | skipY -= (foldLine + 1) * scrollState.pixelsPerLine - heightLine
107 | do hlIter.advance() while (!hlIter.atEnd() && hlIter.start < endOffset)
108 | rangeList.add(Pair(editor.offsetToVisualLine(endOffset),
109 | Range(y,editor.document.getLineNumber(hlIter.start) * scrollState.pixelsPerLine + skipY)))
110 | }
111 | } else {
112 | val commentData = highlight[start]
113 | if(commentData != null){
114 | graphics.font = commentData.font
115 | graphics.color = commentData.color
116 | graphics.drawString(commentData.comment,2,y.toInt() + (graphics.getFontMetrics(commentData.font).height / 1.5).roundToInt())
117 | if (softWrapEnable) {
118 | val softWraps = editor.softWrapModel.getSoftWrapsForRange(start, commentData.jumpEndOffset)
119 | softWraps.forEachIndexed { index, softWrap ->
120 | softWrap.chars.forEach {char -> moveCharIndex(char.code) { skipY += scrollState.pixelsPerLine } }
121 | if (index == softWraps.size - 1){
122 | val lineEndOffset = DocumentUtil.getLineEndOffset(softWrap.end, editor.document)
123 | if(lineEndOffset > commentData.jumpEndOffset){
124 | commentData.jumpEndOffset = lineEndOffset
125 | }
126 | }
127 | }
128 | }
129 | while (!hlIter.atEnd() && hlIter.start < commentData.jumpEndOffset) {
130 | for(offset in hlIter.start until hlIter.end) {
131 | moveCharIndex(text[offset].code) { if (hasBlockInlay) {
132 | val startOffset = offset + 1
133 | val lineEndOffset = DocumentUtil.getLineEndOffset(startOffset, editor.document)
134 | val sumBlock = editor.inlayModel.getBlockElementsInRange(startOffset, lineEndOffset)
135 | .filter { it.placement == Inlay.Placement.ABOVE_LINE }
136 | .sumOf { it.heightInPixels * scrollState.scale }
137 | if (sumBlock > 0) {
138 | rangeList.add(Pair(editor.offsetToVisualLine(startOffset) - 1, Range(y, y + sumBlock)))
139 | y += sumBlock
140 | skipY += sumBlock
141 | if(lineEndOffset > commentData.jumpEndOffset){
142 | commentData.jumpEndOffset = lineEndOffset
143 | }
144 | }
145 | } }
146 | }
147 | hlIter.advance()
148 | }
149 | } else {
150 | val end = hlIter.end
151 | for(offset in start until end) {
152 | // Watch out for tokens that extend past the document
153 | if (offset >= text.length) break@loop
154 | if (softWrapEnable) editor.softWrapModel.getSoftWrap(offset)?.let { softWrap ->
155 | softWrap.chars.forEach { moveCharIndex(it.code) { skipY += scrollState.pixelsPerLine } }
156 | }
157 | val charCode = text[offset].code
158 | moveCharIndex(charCode) { if (hasBlockInlay) {
159 | val startOffset = offset + 1
160 | val sumBlock = editor.inlayModel.getBlockElementsInRange(startOffset, DocumentUtil.getLineEndOffset(startOffset, editor.document))
161 | .filter { it.placement == Inlay.Placement.ABOVE_LINE }
162 | .sumOf { it.heightInPixels * scrollState.scale }
163 | if (sumBlock > 0) {
164 | rangeList.add(Pair(editor.offsetToVisualLine(startOffset) - 1, Range(y, y + sumBlock)))
165 | y += sumBlock
166 | skipY += sumBlock
167 | }
168 | } }
169 | }
170 | hlIter.advance()
171 | }
172 | }
173 | }
174 | graphics.dispose()
175 | }
176 |
177 | /** FoldingListener */
178 | override fun onFoldProcessingEnd() {
179 | if (editor.document.isInBulkUpdate) return
180 | repaintOrRequest()
181 | }
182 |
183 | override fun onCustomFoldRegionPropertiesChange(region: CustomFoldRegion, flags: Int) {
184 | if (flags and FoldingListener.ChangeFlags.HEIGHT_CHANGED == 0 || editor.document.isInBulkUpdate) return
185 | repaintOrRequest()
186 | }
187 |
188 | /** InlayModel.SimpleAdapter */
189 | override fun onAdded(inlay: Inlay<*>) = checkinInlayAndUpdate(inlay)
190 |
191 | override fun onRemoved(inlay: Inlay<*>) = checkinInlayAndUpdate(inlay)
192 |
193 | override fun onUpdated(inlay: Inlay<*>, changeFlags: Int) = checkinInlayAndUpdate(inlay, changeFlags)
194 |
195 | private fun checkinInlayAndUpdate(inlay: Inlay<*>, changeFlags: Int? = null) {
196 | if(editor.document.isInBulkUpdate || editor.inlayModel.isInBatchMode || inlay.placement != Inlay.Placement.ABOVE_LINE
197 | || (changeFlags != null && changeFlags and InlayModel.ChangeFlags.HEIGHT_CHANGED == 0)) return
198 | repaintOrRequest()
199 | }
200 |
201 | override fun onBatchModeFinish(editor: Editor) {
202 | if (editor.document.isInBulkUpdate) return
203 | repaintOrRequest()
204 | }
205 |
206 | /** SoftWrapChangeListener */
207 | override fun softWrapsChanged() {
208 | val enabled = editor.softWrapModel.isSoftWrappingEnabled
209 | if (enabled && !softWrapEnabled) {
210 | softWrapEnabled = true
211 | repaintOrRequest()
212 | } else if (!enabled && softWrapEnabled) {
213 | softWrapEnabled = false
214 | repaintOrRequest()
215 | }
216 | }
217 |
218 | /** MarkupModelListener & BookmarksListener */
219 | override fun updateRangeHighlight(highlighter: RangeMarker) {
220 | if (editor.document.isInBulkUpdate || editor.inlayModel.isInBatchMode || editor.foldingModel.isInBatchFoldingOperation) return
221 | when(highlighter){
222 | is MarkState.BookmarkHighlightDelegate -> repaintOrRequest()
223 | is RangeHighlighterEx -> {
224 | if(highlighter.isThinErrorStripeMark.not() && (highlighter.textAttributesKey?.isMarkAttributes() == true ||
225 | EditorUtil.attributesImpactForegroundColor(highlighter.getTextAttributes(editor.colorsScheme)))) {
226 | repaintOrRequest()
227 | } else if(highlighter.getErrorStripeMarkColor(editor.colorsScheme) != null){
228 | repaintOrRequest(false)
229 | }
230 | }
231 | }
232 | }
233 |
234 | /** PrioritizedDocumentListener */
235 | override fun documentChanged(event: DocumentEvent) {
236 | if (event.document.isInBulkUpdate) return
237 | repaintOrRequest()
238 | }
239 |
240 | /** PropertyChangeListener */
241 | override fun propertyChange(evt: PropertyChangeEvent) {
242 | if (EditorEx.PROP_HIGHLIGHTER != evt.propertyName || evt.newValue is EmptyEditorHighlighter) return
243 | repaintOrRequest()
244 | }
245 |
246 | override fun dispose() {
247 | rangeList.clear()
248 | if(imgReference.isInitialized()){
249 | imgReference.value.clear{ flush() }
250 | }
251 | }
252 |
253 | private fun repaintOrRequest(request: Boolean = true) {
254 | if (glancePanel.checkVisible()) {
255 | if (request) updateMinimapImage()
256 | else glancePanel.repaint()
257 | }
258 | }
259 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/render/MainMinimap.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.render
2 |
3 | import com.intellij.openapi.application.invokeLater
4 | import com.intellij.openapi.components.service
5 | import com.intellij.openapi.editor.*
6 | import com.intellij.openapi.editor.event.DocumentEvent
7 | import com.intellij.openapi.editor.ex.EditorEx
8 | import com.intellij.openapi.editor.ex.FoldingListener
9 | import com.intellij.openapi.editor.ex.RangeHighlighterEx
10 | import com.intellij.openapi.editor.ex.util.EditorUtil
11 | import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter
12 | import com.intellij.openapi.util.text.StringUtil
13 | import com.intellij.util.DocumentUtil
14 | import com.intellij.util.Range
15 | import com.intellij.util.SingleAlarm
16 | import com.nasller.codeglance.config.CodeGlanceConfigService
17 | import com.nasller.codeglance.panel.GlancePanel
18 | import com.nasller.codeglance.util.MySoftReference
19 | import com.nasller.codeglance.util.Util.isMarkAttributes
20 | import java.awt.RenderingHints
21 | import java.awt.image.BufferedImage
22 | import java.beans.PropertyChangeEvent
23 | import kotlin.math.roundToInt
24 |
25 | @Suppress("UnstableApiUsage")
26 | class MainMinimap(glancePanel: GlancePanel): BaseMinimap(glancePanel){
27 | private val alarm by lazy {
28 | SingleAlarm.singleAlarm(500, service().coroutineScope) { updateMinimapImage() }
29 | }
30 | private var imgReference = lazy {
31 | MySoftReference.create(getBufferedImage(scrollState), editor.editorKind != EditorKind.MAIN_EDITOR)
32 | }
33 | override val rangeList: MutableList>> = mutableListOf()
34 | init { makeListener() }
35 |
36 | override fun getImageOrUpdate(): BufferedImage? {
37 | val img = imgReference.value.get()
38 | if(img == null) updateMinimapImage()
39 | return img
40 | }
41 |
42 | override fun updateMinimapImage(canUpdate: Boolean){
43 | if (canUpdate && lock.compareAndSet(false,true)) {
44 | val action = Runnable {
45 | invokeLater(modalityState) {
46 | try {
47 | update()
48 | } finally {
49 | lock.set(false)
50 | glancePanel.repaint()
51 | }
52 | }
53 | }
54 | if(glancePanel.markState.hasMarkHighlight()){
55 | glancePanel.psiDocumentManager.performForCommittedDocument(editor.document, action)
56 | }else action.run()
57 | }
58 | }
59 |
60 | private fun getMinimapImage(): BufferedImage? {
61 | var curImg = imgReference.value.get()
62 | if (curImg == null || curImg.height < scrollState.documentHeight || curImg.width < glancePanel.width) {
63 | curImg?.flush()
64 | curImg = getBufferedImage(scrollState)
65 | imgReference = lazyOf(MySoftReference.create(curImg, editor.editorKind != EditorKind.MAIN_EDITOR))
66 | }
67 | return if(editor.isDisposed) return null else curImg
68 | }
69 |
70 | private fun update() {
71 | val curImg = getMinimapImage() ?: return
72 | if(rangeList.isNotEmpty()) rangeList.clear()
73 | val text = editor.document.immutableCharSequence
74 | val graphics = curImg.createGraphics().apply {
75 | setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
76 | composite = GlancePanel.CLEAR
77 | fillRect(0, 0, curImg.width, curImg.height)
78 | }
79 | if(text.isEmpty()) {
80 | graphics.dispose()
81 | return
82 | }
83 | val defaultColor = editor.colorsScheme.defaultForeground
84 | val hlIter = editor.highlighter.createIterator(0).run {
85 | if(isLogFile) IdeLogFileHighlightDelegate(editor.document,this) else this
86 | }
87 | val softWrapEnable = editor.softWrapModel.isSoftWrappingEnabled
88 | val hasBlockInlay = editor.inlayModel.hasBlockElements()
89 | val renderHeight = scrollState.getRenderHeight()
90 | var x = 0
91 | var y = 0.0
92 | var preSetPixelY = -1
93 | var skipY = 0.0
94 | val moveCharIndex = { code: Int,enterAction: (()->Unit)? ->
95 | when (code) {
96 | 9 -> x += 4//TAB
97 | 10 -> {//ENTER
98 | x = 0
99 | preSetPixelY = y.toInt()
100 | y += scrollState.pixelsPerLine
101 | enterAction?.invoke()
102 | }
103 | else -> x += 1
104 | }
105 | }
106 | val moveAndRenderChar = { it: Char ->
107 | moveCharIndex(it.code, null)
108 | val renderY = y.toInt()
109 | if(renderY != preSetPixelY) {
110 | curImg.renderImage(x, renderY, it.code, renderHeight)
111 | }
112 | }
113 | val highlight = makeMarkHighlight(text, graphics)
114 | loop@ while (!hlIter.atEnd()) {
115 | val start = hlIter.start
116 | if(start > text.length) break@loop
117 | y = editor.document.getLineNumber(start) * scrollState.pixelsPerLine + skipY
118 | val color by lazy(LazyThreadSafetyMode.NONE){ runCatching { hlIter.textAttributes.foregroundColor }.getOrNull() }
119 | val region = editor.foldingModel.getCollapsedRegionAtOffset(start)
120 | if (region != null) {
121 | val startOffset = region.startOffset
122 | val endOffset = region.endOffset
123 | val startLineNumber = editor.document.getLineNumber(startOffset)
124 | val foldLine = editor.document.getLineNumber(endOffset) - startLineNumber
125 | if(region !is CustomFoldRegion){
126 | if(region.placeholderText.isNotBlank()) {
127 | (editor.foldingModel.placeholderAttributes?.foregroundColor ?: defaultColor).setColorRgb()
128 | StringUtil.replace(region.placeholderText, "\n", " ").toCharArray().forEach(moveAndRenderChar)
129 | }
130 | skipY -= foldLine * scrollState.pixelsPerLine
131 | do hlIter.advance() while (!hlIter.atEnd() && hlIter.start < endOffset)
132 | } else {
133 | (color ?: defaultColor).setColorRgb()
134 | //jump over the fold line
135 | val heightLine = region.heightInPixels * scrollState.scale
136 | skipY -= (foldLine + 1) * scrollState.pixelsPerLine - heightLine
137 | do hlIter.advance() while (!hlIter.atEnd() && hlIter.start < endOffset)
138 | rangeList.add(Pair(editor.offsetToVisualLine(endOffset),
139 | Range(y, editor.document.getLineNumber(hlIter.start) * scrollState.pixelsPerLine + skipY)))
140 | //this is render document
141 | val line = startLineNumber - 1 + (heightLine / scrollState.pixelsPerLine).toInt()
142 | text.subSequence(start, if(DocumentUtil.isValidLine(line, editor.document)){
143 | val lineEndOffset = editor.document.getLineEndOffset(line)
144 | if(endOffset < lineEndOffset || startOffset > lineEndOffset) endOffset
145 | else lineEndOffset
146 | }else endOffset).forEach(moveAndRenderChar)
147 | }
148 | } else {
149 | val commentData = highlight[start]
150 | if(commentData != null){
151 | graphics.font = commentData.font
152 | graphics.color = commentData.color
153 | graphics.drawString(commentData.comment,2,y.toInt() + (graphics.getFontMetrics(commentData.font).height / 1.5).roundToInt())
154 | if (softWrapEnable) {
155 | val softWraps = editor.softWrapModel.getSoftWrapsForRange(start, commentData.jumpEndOffset)
156 | softWraps.forEachIndexed { index, softWrap ->
157 | softWrap.chars.forEach {char -> moveCharIndex(char.code) { skipY += scrollState.pixelsPerLine } }
158 | if (index == softWraps.size - 1){
159 | val lineEndOffset = DocumentUtil.getLineEndOffset(softWrap.end, editor.document)
160 | if(lineEndOffset > commentData.jumpEndOffset){
161 | commentData.jumpEndOffset = lineEndOffset
162 | }
163 | }
164 | }
165 | }
166 | while (!hlIter.atEnd() && hlIter.start < commentData.jumpEndOffset) {
167 | for(offset in hlIter.start until hlIter.end) {
168 | moveCharIndex(text[offset].code) { if (hasBlockInlay) {
169 | val startOffset = offset + 1
170 | val lineEndOffset = DocumentUtil.getLineEndOffset(startOffset, editor.document)
171 | val sumBlock = editor.inlayModel.getBlockElementsInRange(startOffset, lineEndOffset)
172 | .filter { it.placement == Inlay.Placement.ABOVE_LINE }
173 | .sumOf { it.heightInPixels * scrollState.scale}
174 | if (sumBlock > 0) {
175 | rangeList.add(Pair(editor.offsetToVisualLine(startOffset) - 1, Range(y, y + sumBlock)))
176 | y += sumBlock
177 | skipY += sumBlock
178 | if(lineEndOffset > commentData.jumpEndOffset){
179 | commentData.jumpEndOffset = lineEndOffset
180 | }
181 | }
182 | } }
183 | }
184 | hlIter.advance()
185 | }
186 | } else {
187 | val end = hlIter.end
188 | val highlightList = getHighlightColor(start, end)
189 | for(offset in start until end) {
190 | // Watch out for tokens that extend past the document
191 | if (offset >= text.length) break@loop
192 | if (softWrapEnable) editor.softWrapModel.getSoftWrap(offset)?.let { softWrap ->
193 | softWrap.chars.forEach { moveCharIndex(it.code) { skipY += scrollState.pixelsPerLine } }
194 | }
195 | val charCode = text[offset].code
196 | moveCharIndex(charCode) { if (hasBlockInlay) {
197 | val startOffset = offset + 1
198 | val sumBlock = editor.inlayModel.getBlockElementsInRange(startOffset, DocumentUtil.getLineEndOffset(startOffset, editor.document))
199 | .filter { it.placement == Inlay.Placement.ABOVE_LINE }
200 | .sumOf { it.heightInPixels * scrollState.scale }
201 | if (sumBlock > 0) {
202 | rangeList.add(Pair(editor.offsetToVisualLine(startOffset) - 1, Range(y, y + sumBlock)))
203 | y += sumBlock
204 | skipY += sumBlock
205 | }
206 | } }
207 | val renderY = y.toInt()
208 | if(renderY != preSetPixelY) {
209 | curImg.renderImage(x, renderY, charCode, renderHeight) {
210 | (highlightList.firstOrNull { offset >= it.startOffset && offset < it.endOffset }?.foregroundColor ?:
211 | color ?: defaultColor).setColorRgb()
212 | }
213 | }
214 | }
215 | hlIter.advance()
216 | }
217 | }
218 | }
219 | graphics.dispose()
220 | }
221 |
222 | /** FoldingListener */
223 | override fun onFoldProcessingEnd() {
224 | if (editor.document.isInBulkUpdate) return
225 | updateMinimapImage()
226 | }
227 |
228 | override fun onCustomFoldRegionPropertiesChange(region: CustomFoldRegion, flags: Int) {
229 | if (flags and FoldingListener.ChangeFlags.HEIGHT_CHANGED == 0 || editor.document.isInBulkUpdate) return
230 | repaintOrRequest()
231 | }
232 |
233 | /** InlayModel.SimpleAdapter */
234 | override fun onAdded(inlay: Inlay<*>) = checkinInlayAndUpdate(inlay)
235 |
236 | override fun onRemoved(inlay: Inlay<*>) = checkinInlayAndUpdate(inlay)
237 |
238 | override fun onUpdated(inlay: Inlay<*>, changeFlags: Int) = checkinInlayAndUpdate(inlay, changeFlags)
239 |
240 | private fun checkinInlayAndUpdate(inlay: Inlay<*>, changeFlags: Int? = null) {
241 | if(editor.document.isInBulkUpdate || editor.inlayModel.isInBatchMode || inlay.placement != Inlay.Placement.ABOVE_LINE
242 | || (changeFlags != null && changeFlags and InlayModel.ChangeFlags.HEIGHT_CHANGED == 0)) return
243 | repaintOrRequest()
244 | }
245 |
246 | override fun onBatchModeFinish(editor: Editor) {
247 | if (editor.document.isInBulkUpdate) return
248 | updateMinimapImage()
249 | }
250 |
251 | /** SoftWrapChangeListener */
252 | override fun softWrapsChanged() {
253 | val enabled = editor.softWrapModel.isSoftWrappingEnabled
254 | if (enabled && !softWrapEnabled) {
255 | softWrapEnabled = true
256 | updateMinimapImage()
257 | } else if (!enabled && softWrapEnabled) {
258 | softWrapEnabled = false
259 | updateMinimapImage()
260 | }
261 | }
262 |
263 | /** MarkupModelListener & BookmarksListener */
264 | override fun updateRangeHighlight(highlighter: RangeMarker) {
265 | if (editor.document.isInBulkUpdate || editor.inlayModel.isInBatchMode || editor.foldingModel.isInBatchFoldingOperation) return
266 | when(highlighter){
267 | is MarkState.BookmarkHighlightDelegate -> repaintOrRequest()
268 | is RangeHighlighterEx -> {
269 | if(highlighter.isThinErrorStripeMark.not() && (highlighter.textAttributesKey?.isMarkAttributes() == true ||
270 | EditorUtil.attributesImpactForegroundColor(highlighter.getTextAttributes(editor.colorsScheme)))) {
271 | repaintOrRequest()
272 | } else if(highlighter.getErrorStripeMarkColor(editor.colorsScheme) != null){
273 | repaintOrRequest(false)
274 | }
275 | }
276 | }
277 | }
278 |
279 | /** PrioritizedDocumentListener */
280 | override fun documentChanged(event: DocumentEvent) {
281 | if (event.document.isInBulkUpdate) return
282 | if (event.document.lineCount > 3000) {
283 | repaintOrRequest()
284 | } else updateMinimapImage()
285 | }
286 |
287 | /** PropertyChangeListener */
288 | override fun propertyChange(evt: PropertyChangeEvent) {
289 | if (EditorEx.PROP_HIGHLIGHTER != evt.propertyName || evt.newValue is EmptyEditorHighlighter) return
290 | updateMinimapImage()
291 | }
292 |
293 | override fun dispose() {
294 | rangeList.clear()
295 | if(imgReference.isInitialized()){
296 | imgReference.value.clear{ flush() }
297 | }
298 | }
299 |
300 | private fun repaintOrRequest(request: Boolean = true) {
301 | if (glancePanel.checkVisible()) {
302 | if (request) alarm.cancelAndRequest()
303 | else glancePanel.repaint()
304 | }
305 | }
306 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/render/MarkState.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.render
2 |
3 | import com.intellij.ide.bookmark.BookmarkGroup
4 | import com.intellij.ide.bookmark.BookmarksManager
5 | import com.intellij.ide.bookmark.LineBookmark
6 | import com.intellij.openapi.editor.Document
7 | import com.intellij.openapi.editor.colors.CodeInsightColors
8 | import com.intellij.openapi.editor.colors.EditorColorsManager
9 | import com.intellij.openapi.editor.colors.EditorColorsScheme
10 | import com.intellij.openapi.editor.colors.TextAttributesKey
11 | import com.intellij.openapi.editor.ex.RangeHighlighterEx
12 | import com.intellij.openapi.editor.impl.EditorImpl
13 | import com.intellij.openapi.editor.markup.*
14 | import com.intellij.openapi.util.Key
15 | import com.intellij.openapi.util.UserDataHolderBase
16 | import com.intellij.util.DocumentUtil
17 | import com.nasller.codeglance.panel.GlancePanel
18 | import com.nasller.codeglance.util.Util
19 | import com.nasller.codeglance.util.Util.isMarkAttributes
20 | import io.ktor.util.collections.*
21 | import java.awt.Color
22 |
23 | class MarkState(val glancePanel: GlancePanel) {
24 | private val markSet = lazy { ConcurrentSet() }
25 |
26 | fun getAllMarkHighlight(): Collection = if(hasMarkHighlight()) markSet.value else emptyList()
27 |
28 | fun hasMarkHighlight(): Boolean = markSet.isInitialized() && markSet.value.isNotEmpty()
29 |
30 | fun markHighlightChange(group: BookmarkGroup, bookmark: LineBookmark, remove: Boolean): RangeHighlighterEx? {
31 | if(glancePanel.config.enableBookmarksMark){
32 | val highlighter = BookmarkHighlightDelegate(group, bookmark)
33 | if(markHighlightChange(highlighter, remove)){
34 | return highlighter
35 | }
36 | }
37 | return null
38 | }
39 |
40 | fun markHighlightChange(highlighter: RangeHighlighterEx, remove: Boolean): Boolean {
41 | if(highlighter is BookmarkHighlightDelegate||
42 | highlighter.textAttributesKey?.isMarkAttributes() == true){
43 | if(highlighter.getErrorStripeMarkColor(glancePanel.editor.colorsScheme) == null || remove){
44 | markSet.value.remove(highlighter)
45 | } else markSet.value.add(highlighter)
46 | return true
47 | }
48 | return false
49 | }
50 |
51 | fun refreshMarkCommentHighlight(editor: EditorImpl) {
52 | if(glancePanel.config.enableMarker) {
53 | editor.filteredDocumentMarkupModel.processRangeHighlightersOverlappingWith(0, editor.document.textLength) {
54 | markHighlightChange(it, false)
55 | return@processRangeHighlightersOverlappingWith true
56 | }
57 | }
58 | if(glancePanel.config.enableBookmarksMark){
59 | val manager = BookmarksManager.getInstance(editor.project)
60 | manager?.bookmarks?.filterIsInstance()?.filter { it.file == editor.virtualFile }?.forEach {
61 | markHighlightChange(BookmarkHighlightDelegate(manager.getGroups(it).singleOrNull(), it), false)
62 | }
63 | }
64 | }
65 |
66 | fun contains(it: RangeHighlighterEx) = (glancePanel.config.enableBookmarksMark &&
67 | CodeInsightColors.BOOKMARKS_ATTRIBUTES == it.textAttributesKey &&
68 | glancePanel.editor.colorsScheme.getAttributes(Util.MARK_COMMENT_ATTRIBUTES).errorStripeColor != null) ||
69 | it.textAttributesKey?.isMarkAttributes() == true
70 |
71 | fun clear() {
72 | if(markSet.isInitialized()) {
73 | markSet.value.clear()
74 | }
75 | }
76 |
77 | @Suppress("UNCHECKED_CAST")
78 | inner class BookmarkHighlightDelegate(private val group: BookmarkGroup?, private val bookmark: LineBookmark): UserDataHolderBase(), RangeHighlighterEx{
79 | override fun getUserData(key: Key): T? {
80 | if(key == BOOK_MARK_DESC_KEY){
81 | return group?.getDescription(bookmark) as T
82 | }
83 | return super.getUserData(key)
84 | }
85 |
86 | override fun getStartOffset(): Int {
87 | val document = glancePanel.editor.document
88 | if(DocumentUtil.isValidLine(bookmark.line, document)){
89 | return document.getLineStartOffset(bookmark.line)
90 | }
91 | return -1
92 | }
93 |
94 | override fun getEndOffset(): Int {
95 | val document = glancePanel.editor.document
96 | if(DocumentUtil.isValidLine(bookmark.line, document)){
97 | return document.getLineEndOffset(bookmark.line)
98 | }
99 | return -1
100 | }
101 |
102 | override fun getTextAttributesKey() = Util.MARK_COMMENT_ATTRIBUTES
103 |
104 | override fun getTextAttributes(scheme: EditorColorsScheme?): TextAttributes? {
105 | val colorScheme = scheme ?: EditorColorsManager.getInstance().globalScheme
106 | return colorScheme.getAttributes(Util.MARK_COMMENT_ATTRIBUTES)
107 | }
108 |
109 | override fun getErrorStripeMarkColor(scheme: EditorColorsScheme?): Color? {
110 | val colorScheme = scheme ?: EditorColorsManager.getInstance().globalScheme
111 | return colorScheme.getAttributes(Util.MARK_COMMENT_ATTRIBUTES)?.errorStripeColor
112 | }
113 |
114 | override fun getDocument(): Document = throw UnsupportedOperationException()
115 |
116 | override fun isValid(): Boolean = throw UnsupportedOperationException()
117 |
118 | override fun setGreedyToLeft(greedy: Boolean) = throw UnsupportedOperationException()
119 |
120 | override fun setGreedyToRight(greedy: Boolean) = throw UnsupportedOperationException()
121 |
122 | override fun isGreedyToRight() = throw UnsupportedOperationException()
123 |
124 | override fun isGreedyToLeft() = throw UnsupportedOperationException()
125 |
126 | override fun dispose() = throw UnsupportedOperationException()
127 |
128 | override fun getLayer()= throw UnsupportedOperationException()
129 |
130 | override fun getTargetArea() = throw UnsupportedOperationException()
131 |
132 | override fun setTextAttributesKey(textAttributesKey: TextAttributesKey) = throw UnsupportedOperationException()
133 |
134 | override fun getLineMarkerRenderer() = throw UnsupportedOperationException()
135 |
136 | override fun setLineMarkerRenderer(renderer: LineMarkerRenderer?) = throw UnsupportedOperationException()
137 |
138 | override fun getCustomRenderer() = throw UnsupportedOperationException()
139 |
140 | override fun setCustomRenderer(renderer: CustomHighlighterRenderer?) = throw UnsupportedOperationException()
141 |
142 | override fun getGutterIconRenderer() = throw UnsupportedOperationException()
143 |
144 | override fun setGutterIconRenderer(renderer: GutterIconRenderer?) = throw UnsupportedOperationException()
145 |
146 | override fun setErrorStripeMarkColor(color: Color?) = throw UnsupportedOperationException()
147 |
148 | override fun getErrorStripeTooltip() = throw UnsupportedOperationException()
149 |
150 | override fun setErrorStripeTooltip(tooltipObject: Any?) = throw UnsupportedOperationException()
151 |
152 | override fun isThinErrorStripeMark() = throw UnsupportedOperationException()
153 |
154 | override fun setThinErrorStripeMark(value: Boolean) = throw UnsupportedOperationException()
155 |
156 | override fun getLineSeparatorColor() = throw UnsupportedOperationException()
157 |
158 | override fun setLineSeparatorColor(color: Color?) = throw UnsupportedOperationException()
159 |
160 | override fun setLineSeparatorRenderer(renderer: LineSeparatorRenderer?) = throw UnsupportedOperationException()
161 |
162 | override fun getLineSeparatorRenderer() = throw UnsupportedOperationException()
163 |
164 | override fun getLineSeparatorPlacement() = throw UnsupportedOperationException()
165 |
166 | override fun setLineSeparatorPlacement(placement: SeparatorPlacement?) = throw UnsupportedOperationException()
167 |
168 | override fun setEditorFilter(filter: MarkupEditorFilter) = throw UnsupportedOperationException()
169 |
170 | override fun getEditorFilter() = throw UnsupportedOperationException()
171 |
172 | override fun getId() = throw UnsupportedOperationException()
173 |
174 | override fun isAfterEndOfLine() = throw UnsupportedOperationException()
175 |
176 | override fun setAfterEndOfLine(value: Boolean) = throw UnsupportedOperationException()
177 |
178 | override fun getAffectedAreaStartOffset() = throw UnsupportedOperationException()
179 |
180 | override fun getAffectedAreaEndOffset() = throw UnsupportedOperationException()
181 |
182 | override fun setTextAttributes(textAttributes: TextAttributes?) = throw UnsupportedOperationException()
183 |
184 | override fun setVisibleIfFolded(value: Boolean) = throw UnsupportedOperationException()
185 |
186 | override fun isVisibleIfFolded() = throw UnsupportedOperationException()
187 |
188 | override fun equals(other: Any?): Boolean {
189 | if (this === other) return true
190 | if (javaClass != other?.javaClass) return false
191 | other as BookmarkHighlightDelegate
192 | return bookmark == other.bookmark
193 | }
194 |
195 | override fun hashCode(): Int {
196 | return bookmark.hashCode()
197 | }
198 | }
199 |
200 | companion object{
201 | val BOOK_MARK_DESC_KEY: Key = Key.create("bookmark.desc.key")
202 | }
203 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/nasller/codeglance/render/ScrollState.kt:
--------------------------------------------------------------------------------
1 | package com.nasller.codeglance.render
2 |
3 | import com.nasller.codeglance.config.enums.EditorSizeEnum
4 | import com.nasller.codeglance.panel.GlancePanel
5 | import java.awt.Rectangle
6 | import kotlin.math.max
7 | import kotlin.math.min
8 |
9 | class ScrollState : Cloneable{
10 | var pixelsPerLine = 0.0
11 | private set
12 | var scale = Double.NaN
13 | private set
14 | var documentHeight = 0
15 | private set
16 | var visibleStart = 0
17 | private set
18 | var visibleEnd = 0
19 | private set
20 | var visibleHeight = 0
21 | private set
22 | //当前图片高度
23 | var drawHeight = 0
24 | private set
25 | var viewportStart = 0
26 | private set
27 | //矩形框高度
28 | var viewportHeight = 0
29 | private set
30 | private var initialized = false
31 |
32 | fun GlancePanel.computeDimensions(visibleArea: Rectangle, visibleChange: Boolean): Boolean {
33 | val lineHeight = editor.lineHeight
34 | val contentHeight = editor.contentComponent.height
35 | val newScale = config.pixelsPerLine.toDouble() / lineHeight
36 | val curDocumentHeight = (contentHeight * newScale).toInt()
37 | if(config.editorSize == EditorSizeEnum.Fit && curDocumentHeight > visibleArea.height) {
38 | if(visibleArea.height < 1 && initialized) {
39 | return true
40 | }
41 | val oldDocumentHeight = documentHeight.apply { documentHeight = visibleArea.height }
42 | scale = documentHeight.toDouble() / contentHeight
43 | pixelsPerLine = scale * lineHeight
44 | if((oldDocumentHeight > 0 || !initialized) && oldDocumentHeight != documentHeight) {
45 | val oldInitialized = initialized.apply { initialized = true }
46 | if(visibleChange && documentHeight > 0 && pixelsPerLine > 0) {
47 | if(oldInitialized) {
48 | refreshImage()
49 | }else {
50 | refreshDataAndImage()
51 | }
52 | return false
53 | }
54 | }
55 | }else {
56 | pixelsPerLine = config.pixelsPerLine.toDouble()
57 | documentHeight = curDocumentHeight
58 | val oldScale = scale.apply { scale = newScale }
59 | if(visibleChange && !oldScale.isNaN() && oldScale != scale) {
60 | if(initialized.apply { initialized = true }) {
61 | refreshImage()
62 | }else {
63 | refreshDataAndImage()
64 | }
65 | return false
66 | }
67 | }
68 | return true
69 | }
70 |
71 | fun recomputeVisible(visibleArea: Rectangle) {
72 | visibleHeight = visibleArea.height
73 | drawHeight = min(visibleHeight, documentHeight)
74 |
75 | viewportStart = (visibleArea.y * scale).toInt()
76 | viewportHeight = (visibleArea.height * scale).toInt()
77 |
78 | visibleStart = ((viewportStart.toFloat() / (documentHeight - viewportHeight + 1)) * (documentHeight - visibleHeight + 1)).toInt().coerceAtLeast(0)
79 | visibleEnd = visibleStart + drawHeight
80 | }
81 |
82 | fun getRenderHeight() = max(1.0, pixelsPerLine).toInt()
83 |
84 | public override fun clone(): ScrollState {
85 | return super.clone() as ScrollState
86 | }
87 | }
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin-dart.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin-java.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin-kotlin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin-scala.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | com.nasller.CodeGlancePro
3 | CodeGlance Pro
4 | Nasller
5 |
6 | messages.CodeGlanceBundle
7 |
8 |
13 | Hide original scrollbar.
14 | Right click to quick config.
15 | Support markup highlights.
16 | Support error stripes highlights.
17 | Support Vcs line highlights.
18 | Support caret line highlights.
19 | Support language ColorScheme.
20 | Quick view code on Glance.
21 | Automatically calculate width in splitter mode.
22 | Ctrl-Shift-G to toggle Glance.
23 |
24 | ]]>
25 |
26 | 1.9.9
28 |
31 | 1.9.8
32 |
35 | 1.9.7
36 |
37 | - Clion #prama mark support
38 |
39 | 1.9.6
40 |
43 | 1.9.5
44 |
45 | - Fix disable marks need re-open file
46 |
47 | ]]>
48 |
49 | com.intellij.modules.lang
50 | com.intellij.modules.java
51 | com.intellij.modules.rider
52 | com.intellij.modules.cidr.lang
53 | com.intellij.modules.rider.cpp.core
54 | org.jetbrains.kotlin
55 | org.intellij.scala
56 | Dart
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/main/resources/icons/glanceHide.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/glanceHide_dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/glanceShow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/icons/glanceShow_dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------