├── .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 | 16 | 21 | 23 | true 24 | true 25 | false 26 | 27 | 28 | -------------------------------------------------------------------------------- /.run/clean.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | false 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.run/runIde.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | false 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.run/runRider.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 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 | [![Version](https://img.shields.io/jetbrains/plugin/v/18824-codeglance-pro.svg)](https://plugins.jetbrains.com/plugin/18824-codeglance-pro) 4 | [![Downloads](https://img.shields.io/jetbrains/plugin/d/18824-codeglance-pro.svg)](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 | 9 | 14 | 19 | 24 | -------------------------------------------------------------------------------- /core/src/main/resources/colorSchemes/color-default.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 14 | 19 | 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 |
      29 |
    • 30 |
    31 |

    1.9.8

    32 |
      33 |
    • 2025.2-EAP
    • 34 |
    35 |

    1.9.7

    36 |
      37 |
    • Clion #prama mark support
    • 38 |
    39 |

    1.9.6

    40 |
      41 |
    • 2025.1-EAP
    • 42 |
    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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/icons/glanceHide_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/icons/glanceShow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/icons/glanceShow_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | --------------------------------------------------------------------------------