mirror of
				https://github.com/chylex/IntelliJ-Inspection-Lens.git
				synced 2025-10-31 20:17:15 +01:00 
			
		
		
		
	Compare commits
	
		
			62 Commits
		
	
	
		
			06c81654e8
			...
			test-snaps
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ed0b37b86c | |||
| cde4d81afe | |||
| b1d6ed4d30 | |||
| 2a4764fa15 | |||
| 4bd0931d71 | |||
| 0f41b22872 | |||
| 603b35abdb | |||
| 08ed1aadea | |||
| 8936f0e5be | |||
| 89e71d5301 | |||
| 4c80573375 | |||
| 816440a150 | |||
| 4899498522 | |||
| 8ee14ff55e | |||
| 632a052ff9 | |||
| f2ec3c3d9b | |||
| 624254fba3 | |||
| c1b52ec3a5 | |||
| a84bc72fd4 | |||
| e28804ad5d | |||
| 043c02e432 | |||
| 223fceb6b9 | |||
| 640d95cddc | |||
| fcd4d6588d | |||
| 1c92cf000f | |||
| 83c179574a | |||
| 536650510d | |||
| 9f395476df | |||
| 182ad049d9 | |||
| 29cf88cc4b | |||
| a05a97274b | |||
| 9fd6f1b4fa | |||
| c8cacbe252 | |||
| 13f3c86afa | |||
|   | 94602bd8f9 | ||
| 97422e1d42 | |||
| beab4af5ca | |||
| 443b2b9a2d | |||
| e96961e10a | |||
| 7432c57e95 | |||
| 0f49339ca2 | |||
| 13f3002366 | |||
| 44f2fa5c16 | |||
| eb2faa2518 | |||
| c993b4f203 | |||
| eb2d60f22d | |||
| da47687696 | |||
| 86a3a87da2 | |||
| 797efb48a2 | |||
| 33bb46d0be | |||
| 87f6eda572 | |||
| ca4fb0484a | |||
| 0aff0f49ae | |||
| e2384a98a8 | |||
| 7c3910854d | |||
| ce85aa130d | |||
| 0e380a4658 | |||
| fd50ca90b6 | |||
| e6be154f88 | |||
|   | 963c49a3c8 | ||
| cd465ebc0e | |||
| 10461d2927 | 
							
								
								
									
										2
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1 @@ | |||||||
| github: chylex |  | ||||||
| patreon: chylex |  | ||||||
| ko_fi: chylex | ko_fi: chylex | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								.github/readme/intellij.png
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.github/readme/intellij.png
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,8 +1,7 @@ | |||||||
| /.idea/* | /.idea/* | ||||||
| !/.idea/compiler.xml | !/.idea/dictionaries | ||||||
| !/.idea/encodings.xml | !/.idea/runConfigurations | ||||||
| !/.idea/gradle.xml |  | ||||||
| !/.idea/vcs.xml |  | ||||||
|  |  | ||||||
| /.gradle/ | /.gradle/ | ||||||
|  | /.intellijPlatform/ | ||||||
| /build/ | /build/ | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,5 +0,0 @@ | |||||||
| # Default ignored files |  | ||||||
| /shelf/ |  | ||||||
| /workspace.xml |  | ||||||
| # Editor-based HTTP Client requests |  | ||||||
| /httpRequests/ |  | ||||||
							
								
								
									
										6
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <project version="4"> |  | ||||||
|   <component name="CompilerConfiguration"> |  | ||||||
|     <bytecodeTargetLevel target="11" /> |  | ||||||
|   </component> |  | ||||||
| </project> |  | ||||||
							
								
								
									
										7
									
								
								.idea/dictionaries/default_user.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.idea/dictionaries/default_user.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | <component name="ProjectDictionaryState"> | ||||||
|  |   <dictionary name="default.user"> | ||||||
|  |     <words> | ||||||
|  |       <w>inspectionlens</w> | ||||||
|  |     </words> | ||||||
|  |   </dictionary> | ||||||
|  | </component> | ||||||
							
								
								
									
										6
									
								
								.idea/encodings.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								.idea/encodings.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <project version="4"> |  | ||||||
|   <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8"> |  | ||||||
|     <file url="PROJECT" charset="UTF-8" /> |  | ||||||
|   </component> |  | ||||||
| </project> |  | ||||||
							
								
								
									
										19
									
								
								.idea/gradle.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										19
									
								
								.idea/gradle.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,19 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <project version="4"> |  | ||||||
|   <component name="GradleMigrationSettings" migrationVersion="1" /> |  | ||||||
|   <component name="GradleSettings"> |  | ||||||
|     <option name="linkedExternalProjectsSettings"> |  | ||||||
|       <GradleProjectSettings> |  | ||||||
|         <option name="delegatedBuild" value="true" /> |  | ||||||
|         <option name="testRunner" value="GRADLE" /> |  | ||||||
|         <option name="distributionType" value="DEFAULT_WRAPPED" /> |  | ||||||
|         <option name="externalProjectPath" value="$PROJECT_DIR$" /> |  | ||||||
|         <option name="modules"> |  | ||||||
|           <set> |  | ||||||
|             <option value="$PROJECT_DIR$" /> |  | ||||||
|           </set> |  | ||||||
|         </option> |  | ||||||
|       </GradleProjectSettings> |  | ||||||
|     </option> |  | ||||||
|   </component> |  | ||||||
| </project> |  | ||||||
							
								
								
									
										11
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,11 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <project version="4"> |  | ||||||
|   <component name="GitSharedSettings"> |  | ||||||
|     <option name="FORCE_PUSH_PROHIBITED_PATTERNS"> |  | ||||||
|       <list /> |  | ||||||
|     </option> |  | ||||||
|   </component> |  | ||||||
|   <component name="VcsDirectoryMappings"> |  | ||||||
|     <mapping directory="$PROJECT_DIR$" vcs="Git" /> |  | ||||||
|   </component> |  | ||||||
| </project> |  | ||||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,9 +1,11 @@ | |||||||
| IntelliJ plugin that shows errors, warnings, and other inspection highlights inline. | # Inspection Lens <img align="right" src="logo.png" alt="Plugin Logo"> | ||||||
|  |  | ||||||
| Simply install the plugin and inspection descriptions will appear on the right side of the lines. Shown inspection severities are **Errors**, **Warnings**, **Weak Warnings**, **Server Problems**, **Typos**, and other inspections from plugins or future IntelliJ versions that have a high enough severity level. Each severity has a different color, with support for both light and dark themes. | Displays errors, warnings, and other inspections inline. Highlights the background of lines with inspections. Supports light and dark themes out of the box. | ||||||
|  |  | ||||||
| The plugin is not customizable outside of the ability to disable/enable the plugin without restarting the IDE. If the defaults don't work for you, I would recommend either trying the [Inline Error](https://plugins.jetbrains.com/plugin/17302-inlineerror) plugin which can be customized, or proposing your change in the [issue tracker](https://github.com/chylex/IntelliJ-Inspection-Lens/issues). | By default, the plugin shows **Errors**, **Warnings**, **Weak Warnings**, **Server Problems**, **Grammar Errors**, **Typos**, and other inspections with a high enough severity level. Left-click an inspection to show quick fixes. Middle-click an inspection to navigate to the relevant code in the editor. | ||||||
|  |  | ||||||
|  | Configure appearance, behavior of clicking on inspections, and visible severities in **Settings | Tools | Inspection Lens**. | ||||||
|  |  | ||||||
| Inspired by [Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) for Visual Studio Code, and [Inline Error](https://plugins.jetbrains.com/plugin/17302-inlineerror) for IntelliJ Platform. | Inspired by [Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) for Visual Studio Code, and [Inline Error](https://plugins.jetbrains.com/plugin/17302-inlineerror) for IntelliJ Platform. | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,35 +1,57 @@ | |||||||
| @file:Suppress("ConvertLambdaToReference") | @file:Suppress("ConvertLambdaToReference") | ||||||
|  |  | ||||||
| import org.jetbrains.kotlin.gradle.tasks.KotlinCompile |  | ||||||
|  |  | ||||||
| plugins { | plugins { | ||||||
| 	kotlin("jvm") version "1.6.21" | 	kotlin("jvm") | ||||||
| 	id("org.jetbrains.intellij") version "1.7.0" | 	id("org.jetbrains.intellij.platform") | ||||||
| } | } | ||||||
|  |  | ||||||
| group = "com.chylex.intellij.inspectionlens" | group = "com.chylex.intellij.inspectionlens" | ||||||
| version = "0.0.1" | version = "1.5.1" | ||||||
|  |  | ||||||
| repositories { | repositories { | ||||||
| 	mavenCentral() | 	mavenCentral() | ||||||
|  | 	 | ||||||
|  | 	intellijPlatform { | ||||||
|  | 		defaultRepositories() | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| intellij { | dependencies { | ||||||
| 	version.set("2022.2") | 	intellijPlatform { | ||||||
| 	updateSinceUntilBuild.set(false) | 		intellijIdeaUltimate("2023.3.3") | ||||||
|  | 		bundledPlugin("tanvd.grazi") | ||||||
|  | 		 | ||||||
|  | 		// https://plugins.jetbrains.com/plugin/12175-grazie-lite/versions | ||||||
|  | 		// plugin("tanvd.grazi", "233.13135.14") | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") | ||||||
| } | } | ||||||
|  |  | ||||||
| tasks.patchPluginXml { | intellijPlatform { | ||||||
| 	sinceBuild.set("222") | 	pluginConfiguration { | ||||||
|  | 		ideaVersion { | ||||||
|  | 			sinceBuild.set("233.11361.10") | ||||||
|  | 			untilBuild.set(provider { null }) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| tasks.buildSearchableOptions { | kotlin { | ||||||
| 	enabled = false | 	jvmToolchain(17) | ||||||
|  | 	 | ||||||
|  | 	compilerOptions { | ||||||
|  | 		freeCompilerArgs = listOf( | ||||||
|  | 			"-X" + "jvm-default=all" | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| tasks.withType<KotlinCompile> { | tasks.withType<Test>().configureEach { | ||||||
| 	kotlinOptions.jvmTarget = "11" | 	useJUnitPlatform() | ||||||
| 	kotlinOptions.freeCompilerArgs = listOf( | } | ||||||
| 		"-Xjvm-default=enable" |  | ||||||
| 	) | val testSnapshot by intellijPlatformTesting.testIde.registering { | ||||||
|  | 	version = "LATEST-EAP-SNAPSHOT" | ||||||
|  | 	useInstaller = false | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,7 @@ | |||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip | ||||||
|  | networkTimeout=10000 | ||||||
|  | validateDistributionUrl=true | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
|   | |||||||
							
								
								
									
										296
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										296
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| #!/usr/bin/env sh | #!/bin/sh | ||||||
|  |  | ||||||
| # | # | ||||||
| # Copyright 2015 the original author or authors. | # Copyright © 2015-2021 the original authors. | ||||||
| # | # | ||||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
| # you may not use this file except in compliance with the License. | # you may not use this file except in compliance with the License. | ||||||
| @@ -15,69 +15,103 @@ | |||||||
| # See the License for the specific language governing permissions and | # See the License for the specific language governing permissions and | ||||||
| # limitations under the License. | # limitations under the License. | ||||||
| # | # | ||||||
|  | # SPDX-License-Identifier: Apache-2.0 | ||||||
|  | # | ||||||
|  |  | ||||||
| ############################################################################## | ############################################################################## | ||||||
| ## | # | ||||||
| ##  Gradle start up script for UN*X | #   Gradle start up script for POSIX generated by Gradle. | ||||||
| ## | # | ||||||
|  | #   Important for running: | ||||||
|  | # | ||||||
|  | #   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is | ||||||
|  | #       noncompliant, but you have some other compliant shell such as ksh or | ||||||
|  | #       bash, then to run this script, type that shell name before the whole | ||||||
|  | #       command line, like: | ||||||
|  | # | ||||||
|  | #           ksh Gradle | ||||||
|  | # | ||||||
|  | #       Busybox and similar reduced shells will NOT work, because this script | ||||||
|  | #       requires all of these POSIX shell features: | ||||||
|  | #         * functions; | ||||||
|  | #         * expansions «$var», «${var}», «${var:-default}», «${var+SET}», | ||||||
|  | #           «${var#prefix}», «${var%suffix}», and «$( cmd )»; | ||||||
|  | #         * compound commands having a testable exit status, especially «case»; | ||||||
|  | #         * various built-in commands including «command», «set», and «ulimit». | ||||||
|  | # | ||||||
|  | #   Important for patching: | ||||||
|  | # | ||||||
|  | #   (2) This script targets any POSIX shell, so it avoids extensions provided | ||||||
|  | #       by Bash, Ksh, etc; in particular arrays are avoided. | ||||||
|  | # | ||||||
|  | #       The "traditional" practice of packing multiple parameters into a | ||||||
|  | #       space-separated string is a well documented source of bugs and security | ||||||
|  | #       problems, so this is (mostly) avoided, by progressively accumulating | ||||||
|  | #       options in "$@", and eventually passing that to Java. | ||||||
|  | # | ||||||
|  | #       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, | ||||||
|  | #       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; | ||||||
|  | #       see the in-line comments for details. | ||||||
|  | # | ||||||
|  | #       There are tweaks for specific operating systems such as AIX, CygWin, | ||||||
|  | #       Darwin, MinGW, and NonStop. | ||||||
|  | # | ||||||
|  | #   (3) This script is generated from the Groovy template | ||||||
|  | #       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt | ||||||
|  | #       within the Gradle project. | ||||||
|  | # | ||||||
|  | #       You can find Gradle at https://github.com/gradle/gradle/. | ||||||
|  | # | ||||||
| ############################################################################## | ############################################################################## | ||||||
|  |  | ||||||
| # Attempt to set APP_HOME | # Attempt to set APP_HOME | ||||||
|  |  | ||||||
| # Resolve links: $0 may be a link | # Resolve links: $0 may be a link | ||||||
| PRG="$0" | app_path=$0 | ||||||
| # Need this for relative symlinks. |  | ||||||
| while [ -h "$PRG" ] ; do | # Need this for daisy-chained symlinks. | ||||||
|     ls=`ls -ld "$PRG"` | while | ||||||
|     link=`expr "$ls" : '.*-> \(.*\)$'` |     APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path | ||||||
|     if expr "$link" : '/.*' > /dev/null; then |     [ -h "$app_path" ] | ||||||
|         PRG="$link" | do | ||||||
|     else |     ls=$( ls -ld "$app_path" ) | ||||||
|         PRG=`dirname "$PRG"`"/$link" |     link=${ls#*' -> '} | ||||||
|     fi |     case $link in             #( | ||||||
|  |       /*)   app_path=$link ;; #( | ||||||
|  |       *)    app_path=$APP_HOME$link ;; | ||||||
|  |     esac | ||||||
| done | done | ||||||
| SAVED="`pwd`" |  | ||||||
| cd "`dirname \"$PRG\"`/" >/dev/null |  | ||||||
| APP_HOME="`pwd -P`" |  | ||||||
| cd "$SAVED" >/dev/null |  | ||||||
|  |  | ||||||
| APP_NAME="Gradle" | # This is normally unused | ||||||
| APP_BASE_NAME=`basename "$0"` | # shellcheck disable=SC2034 | ||||||
|  | APP_BASE_NAME=${0##*/} | ||||||
| # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) | ||||||
| DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit | ||||||
|  |  | ||||||
| # Use the maximum available, or set MAX_FD != -1 to use that value. | # Use the maximum available, or set MAX_FD != -1 to use that value. | ||||||
| MAX_FD="maximum" | MAX_FD=maximum | ||||||
|  |  | ||||||
| warn () { | warn () { | ||||||
|     echo "$*" |     echo "$*" | ||||||
| } | } >&2 | ||||||
|  |  | ||||||
| die () { | die () { | ||||||
|     echo |     echo | ||||||
|     echo "$*" |     echo "$*" | ||||||
|     echo |     echo | ||||||
|     exit 1 |     exit 1 | ||||||
| } | } >&2 | ||||||
|  |  | ||||||
| # OS specific support (must be 'true' or 'false'). | # OS specific support (must be 'true' or 'false'). | ||||||
| cygwin=false | cygwin=false | ||||||
| msys=false | msys=false | ||||||
| darwin=false | darwin=false | ||||||
| nonstop=false | nonstop=false | ||||||
| case "`uname`" in | case "$( uname )" in                #( | ||||||
|   CYGWIN* ) |   CYGWIN* )         cygwin=true  ;; #( | ||||||
|     cygwin=true |   Darwin* )         darwin=true  ;; #( | ||||||
|     ;; |   MSYS* | MINGW* )  msys=true    ;; #( | ||||||
|   Darwin* ) |   NONSTOP* )        nonstop=true ;; | ||||||
|     darwin=true |  | ||||||
|     ;; |  | ||||||
|   MSYS* | MINGW* ) |  | ||||||
|     msys=true |  | ||||||
|     ;; |  | ||||||
|   NONSTOP* ) |  | ||||||
|     nonstop=true |  | ||||||
|     ;; |  | ||||||
| esac | esac | ||||||
|  |  | ||||||
| CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||||||
| @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | |||||||
| if [ -n "$JAVA_HOME" ] ; then | if [ -n "$JAVA_HOME" ] ; then | ||||||
|     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||||||
|         # IBM's JDK on AIX uses strange locations for the executables |         # IBM's JDK on AIX uses strange locations for the executables | ||||||
|         JAVACMD="$JAVA_HOME/jre/sh/java" |         JAVACMD=$JAVA_HOME/jre/sh/java | ||||||
|     else |     else | ||||||
|         JAVACMD="$JAVA_HOME/bin/java" |         JAVACMD=$JAVA_HOME/bin/java | ||||||
|     fi |     fi | ||||||
|     if [ ! -x "$JAVACMD" ] ; then |     if [ ! -x "$JAVACMD" ] ; then | ||||||
|         die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME |         die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||||||
| @@ -98,88 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the | |||||||
| location of your Java installation." | location of your Java installation." | ||||||
|     fi |     fi | ||||||
| else | else | ||||||
|     JAVACMD="java" |     JAVACMD=java | ||||||
|     which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |     if ! command -v java >/dev/null 2>&1 | ||||||
|  |     then | ||||||
|  |         die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||||||
|  |  | ||||||
| Please set the JAVA_HOME variable in your environment to match the | Please set the JAVA_HOME variable in your environment to match the | ||||||
| location of your Java installation." | location of your Java installation." | ||||||
|  |     fi | ||||||
| fi | fi | ||||||
|  |  | ||||||
| # Increase the maximum file descriptors if we can. | # Increase the maximum file descriptors if we can. | ||||||
| if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then | ||||||
|     MAX_FD_LIMIT=`ulimit -H -n` |     case $MAX_FD in #( | ||||||
|     if [ $? -eq 0 ] ; then |       max*) | ||||||
|         if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |         # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. | ||||||
|             MAX_FD="$MAX_FD_LIMIT" |         # shellcheck disable=SC2039,SC3045 | ||||||
|         fi |         MAX_FD=$( ulimit -H -n ) || | ||||||
|         ulimit -n $MAX_FD |             warn "Could not query maximum file descriptor limit" | ||||||
|         if [ $? -ne 0 ] ; then |     esac | ||||||
|             warn "Could not set maximum file descriptor limit: $MAX_FD" |     case $MAX_FD in  #( | ||||||
|         fi |       '' | soft) :;; #( | ||||||
|     else |       *) | ||||||
|         warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |         # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. | ||||||
|     fi |         # shellcheck disable=SC2039,SC3045 | ||||||
| fi |         ulimit -n "$MAX_FD" || | ||||||
|  |             warn "Could not set maximum file descriptor limit to $MAX_FD" | ||||||
| # For Darwin, add options to specify how the application appears in the dock |  | ||||||
| if $darwin; then |  | ||||||
|     GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| # For Cygwin or MSYS, switch paths to Windows format before running java |  | ||||||
| if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then |  | ||||||
|     APP_HOME=`cygpath --path --mixed "$APP_HOME"` |  | ||||||
|     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |  | ||||||
|  |  | ||||||
|     JAVACMD=`cygpath --unix "$JAVACMD"` |  | ||||||
|  |  | ||||||
|     # We build the pattern for arguments to be converted via cygpath |  | ||||||
|     ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |  | ||||||
|     SEP="" |  | ||||||
|     for dir in $ROOTDIRSRAW ; do |  | ||||||
|         ROOTDIRS="$ROOTDIRS$SEP$dir" |  | ||||||
|         SEP="|" |  | ||||||
|     done |  | ||||||
|     OURCYGPATTERN="(^($ROOTDIRS))" |  | ||||||
|     # Add a user-defined pattern to the cygpath arguments |  | ||||||
|     if [ "$GRADLE_CYGPATTERN" != "" ] ; then |  | ||||||
|         OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |  | ||||||
|     fi |  | ||||||
|     # Now convert the arguments - kludge to limit ourselves to /bin/sh |  | ||||||
|     i=0 |  | ||||||
|     for arg in "$@" ; do |  | ||||||
|         CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |  | ||||||
|         CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option |  | ||||||
|  |  | ||||||
|         if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition |  | ||||||
|             eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |  | ||||||
|         else |  | ||||||
|             eval `echo args$i`="\"$arg\"" |  | ||||||
|         fi |  | ||||||
|         i=`expr $i + 1` |  | ||||||
|     done |  | ||||||
|     case $i in |  | ||||||
|         0) set -- ;; |  | ||||||
|         1) set -- "$args0" ;; |  | ||||||
|         2) set -- "$args0" "$args1" ;; |  | ||||||
|         3) set -- "$args0" "$args1" "$args2" ;; |  | ||||||
|         4) set -- "$args0" "$args1" "$args2" "$args3" ;; |  | ||||||
|         5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |  | ||||||
|         6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |  | ||||||
|         7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |  | ||||||
|         8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |  | ||||||
|         9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |  | ||||||
|     esac |     esac | ||||||
| fi | fi | ||||||
|  |  | ||||||
| # Escape application args | # Collect all arguments for the java command, stacking in reverse order: | ||||||
| save () { | #   * args from the command line | ||||||
|     for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done | #   * the main class name | ||||||
|     echo " " | #   * -classpath | ||||||
| } | #   * -D...appname settings | ||||||
| APP_ARGS=`save "$@"` | #   * --module-path (only if needed) | ||||||
|  | #   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. | ||||||
|  |  | ||||||
| # Collect all arguments for the java command, following the shell quoting and substitution rules | # For Cygwin or MSYS, switch paths to Windows format before running java | ||||||
| eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" | if "$cygwin" || "$msys" ; then | ||||||
|  |     APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) | ||||||
|  |     CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) | ||||||
|  |  | ||||||
|  |     JAVACMD=$( cygpath --unix "$JAVACMD" ) | ||||||
|  |  | ||||||
|  |     # Now convert the arguments - kludge to limit ourselves to /bin/sh | ||||||
|  |     for arg do | ||||||
|  |         if | ||||||
|  |             case $arg in                                #( | ||||||
|  |               -*)   false ;;                            # don't mess with options #( | ||||||
|  |               /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath | ||||||
|  |                     [ -e "$t" ] ;;                      #( | ||||||
|  |               *)    false ;; | ||||||
|  |             esac | ||||||
|  |         then | ||||||
|  |             arg=$( cygpath --path --ignore --mixed "$arg" ) | ||||||
|  |         fi | ||||||
|  |         # Roll the args list around exactly as many times as the number of | ||||||
|  |         # args, so each arg winds up back in the position where it started, but | ||||||
|  |         # possibly modified. | ||||||
|  |         # | ||||||
|  |         # NB: a `for` loop captures its iteration list before it begins, so | ||||||
|  |         # changing the positional parameters here affects neither the number of | ||||||
|  |         # iterations, nor the values presented in `arg`. | ||||||
|  |         shift                   # remove old arg | ||||||
|  |         set -- "$@" "$arg"      # push replacement arg | ||||||
|  |     done | ||||||
|  | fi | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||||
|  | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | ||||||
|  |  | ||||||
|  | # Collect all arguments for the java command: | ||||||
|  | #   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, | ||||||
|  | #     and any embedded shellness will be escaped. | ||||||
|  | #   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be | ||||||
|  | #     treated as '${Hostname}' itself on the command line. | ||||||
|  |  | ||||||
|  | set -- \ | ||||||
|  |         "-Dorg.gradle.appname=$APP_BASE_NAME" \ | ||||||
|  |         -classpath "$CLASSPATH" \ | ||||||
|  |         org.gradle.wrapper.GradleWrapperMain \ | ||||||
|  |         "$@" | ||||||
|  |  | ||||||
|  | # Stop when "xargs" is not available. | ||||||
|  | if ! command -v xargs >/dev/null 2>&1 | ||||||
|  | then | ||||||
|  |     die "xargs is not available" | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # Use "xargs" to parse quoted args. | ||||||
|  | # | ||||||
|  | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. | ||||||
|  | # | ||||||
|  | # In Bash we could simply go: | ||||||
|  | # | ||||||
|  | #   readarray ARGS < <( xargs -n1 <<<"$var" ) && | ||||||
|  | #   set -- "${ARGS[@]}" "$@" | ||||||
|  | # | ||||||
|  | # but POSIX shell has neither arrays nor command substitution, so instead we | ||||||
|  | # post-process each arg (as a line of input to sed) to backslash-escape any | ||||||
|  | # character that might be a shell metacharacter, then use eval to reverse | ||||||
|  | # that process (while maintaining the separation between arguments), and wrap | ||||||
|  | # the whole thing up as a single "set" statement. | ||||||
|  | # | ||||||
|  | # This will of course break if any of these variables contains a newline or | ||||||
|  | # an unmatched quote. | ||||||
|  | # | ||||||
|  |  | ||||||
|  | eval "set -- $( | ||||||
|  |         printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | | ||||||
|  |         xargs -n1 | | ||||||
|  |         sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | | ||||||
|  |         tr '\n' ' ' | ||||||
|  |     )" '"$@"' | ||||||
|  |  | ||||||
| exec "$JAVACMD" "$@" | exec "$JAVACMD" "$@" | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										37
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
								
							| @@ -13,8 +13,10 @@ | |||||||
| @rem See the License for the specific language governing permissions and | @rem See the License for the specific language governing permissions and | ||||||
| @rem limitations under the License. | @rem limitations under the License. | ||||||
| @rem | @rem | ||||||
|  | @rem SPDX-License-Identifier: Apache-2.0 | ||||||
|  | @rem | ||||||
|  |  | ||||||
| @if "%DEBUG%" == "" @echo off | @if "%DEBUG%"=="" @echo off | ||||||
| @rem ########################################################################## | @rem ########################################################################## | ||||||
| @rem | @rem | ||||||
| @rem  Gradle startup script for Windows | @rem  Gradle startup script for Windows | ||||||
| @@ -25,7 +27,8 @@ | |||||||
| if "%OS%"=="Windows_NT" setlocal | if "%OS%"=="Windows_NT" setlocal | ||||||
|  |  | ||||||
| set DIRNAME=%~dp0 | set DIRNAME=%~dp0 | ||||||
| if "%DIRNAME%" == "" set DIRNAME=. | if "%DIRNAME%"=="" set DIRNAME=. | ||||||
|  | @rem This is normally unused | ||||||
| set APP_BASE_NAME=%~n0 | set APP_BASE_NAME=%~n0 | ||||||
| set APP_HOME=%DIRNAME% | set APP_HOME=%DIRNAME% | ||||||
|  |  | ||||||
| @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome | |||||||
|  |  | ||||||
| set JAVA_EXE=java.exe | set JAVA_EXE=java.exe | ||||||
| %JAVA_EXE% -version >NUL 2>&1 | %JAVA_EXE% -version >NUL 2>&1 | ||||||
| if "%ERRORLEVEL%" == "0" goto execute | if %ERRORLEVEL% equ 0 goto execute | ||||||
|  |  | ||||||
| echo. | echo. 1>&2 | ||||||
| echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 | ||||||
| echo. | echo. 1>&2 | ||||||
| echo Please set the JAVA_HOME variable in your environment to match the | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 | ||||||
| echo location of your Java installation. | echo location of your Java installation. 1>&2 | ||||||
|  |  | ||||||
| goto fail | goto fail | ||||||
|  |  | ||||||
| @@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe | |||||||
|  |  | ||||||
| if exist "%JAVA_EXE%" goto execute | if exist "%JAVA_EXE%" goto execute | ||||||
|  |  | ||||||
| echo. | echo. 1>&2 | ||||||
| echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 | ||||||
| echo. | echo. 1>&2 | ||||||
| echo Please set the JAVA_HOME variable in your environment to match the | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 | ||||||
| echo location of your Java installation. | echo location of your Java installation. 1>&2 | ||||||
|  |  | ||||||
| goto fail | goto fail | ||||||
|  |  | ||||||
| @@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | |||||||
|  |  | ||||||
| :end | :end | ||||||
| @rem End local scope for the variables with windows NT shell | @rem End local scope for the variables with windows NT shell | ||||||
| if "%ERRORLEVEL%"=="0" goto mainEnd | if %ERRORLEVEL% equ 0 goto mainEnd | ||||||
|  |  | ||||||
| :fail | :fail | ||||||
| rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | ||||||
| rem the _cmd.exe /c_ return code! | rem the _cmd.exe /c_ return code! | ||||||
| if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | set EXIT_CODE=%ERRORLEVEL% | ||||||
| exit /b 1 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 | ||||||
|  | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% | ||||||
|  | exit /b %EXIT_CODE% | ||||||
|  |  | ||||||
| :mainEnd | :mainEnd | ||||||
| if "%OS%"=="Windows_NT" endlocal | if "%OS%"=="Windows_NT" endlocal | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								logo.afdesign
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								logo.afdesign
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -1 +1,8 @@ | |||||||
| rootProject.name = "InspectionLens" | rootProject.name = "InspectionLens" | ||||||
|  |  | ||||||
|  | pluginManagement { | ||||||
|  | 	plugins { | ||||||
|  | 		kotlin("jvm") version "1.9.21" | ||||||
|  | 		id("org.jetbrains.intellij.platform") version "2.2.1" | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,71 +0,0 @@ | |||||||
| package com.chylex.intellij.inspectionlens |  | ||||||
|  |  | ||||||
| import com.intellij.codeInsight.daemon.impl.HighlightInfo |  | ||||||
| import com.intellij.openapi.editor.Editor |  | ||||||
| import com.intellij.openapi.editor.Inlay |  | ||||||
| import com.intellij.openapi.editor.InlayProperties |  | ||||||
| import com.intellij.openapi.editor.markup.RangeHighlighter |  | ||||||
| import com.intellij.openapi.util.Key |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Manages visible inspection lenses for an [Editor]. |  | ||||||
|  */ |  | ||||||
| class EditorInlayLensManager private constructor(private val editor: Editor) { |  | ||||||
| 	companion object { |  | ||||||
| 		private val KEY = Key<EditorInlayLensManager>(EditorInlayLensManager::class.java.name) |  | ||||||
| 		 |  | ||||||
| 		fun getOrCreate(editor: Editor): EditorInlayLensManager { |  | ||||||
| 			return editor.getUserData(KEY) ?: EditorInlayLensManager(editor).also { editor.putUserData(KEY, it) } |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		fun remove(editor: Editor) { |  | ||||||
| 			val manager = editor.getUserData(KEY) |  | ||||||
| 			if (manager != null) { |  | ||||||
| 				manager.hideAll() |  | ||||||
| 				editor.putUserData(KEY, null) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		private fun getInlayHintOffset(info: HighlightInfo): Int { |  | ||||||
| 			// Ensures a highlight at the end of a line does not overflow to the next line. |  | ||||||
| 			return info.actualEndOffset - 1 |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	private val inlays = mutableMapOf<RangeHighlighter, Inlay<LensRenderer>>() |  | ||||||
| 	 |  | ||||||
| 	fun show(highlighterWithInfo: HighlighterWithInfo) { |  | ||||||
| 		val (highlighter, info) = highlighterWithInfo |  | ||||||
| 		val currentInlay = inlays[highlighter] |  | ||||||
| 		if (currentInlay != null && currentInlay.isValid) { |  | ||||||
| 			currentInlay.renderer.setPropertiesFrom(info) |  | ||||||
| 			currentInlay.update() |  | ||||||
| 		} |  | ||||||
| 		else { |  | ||||||
| 			val offset = getInlayHintOffset(info) |  | ||||||
| 			val renderer = LensRenderer(info) |  | ||||||
| 			val properties = InlayProperties().relatesToPrecedingText(true).disableSoftWrapping(true).priority(-offset) |  | ||||||
| 			 |  | ||||||
| 			editor.inlayModel.addAfterLineEndElement(offset, properties, renderer)?.let { |  | ||||||
| 				inlays[highlighter] = it |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	fun showAll(highlightersWithInfo: Collection<HighlighterWithInfo>) { |  | ||||||
| 		executeInInlayBatchMode(highlightersWithInfo.size) { highlightersWithInfo.forEach(::show) } |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	fun hide(highlighter: RangeHighlighter) { |  | ||||||
| 		inlays.remove(highlighter)?.dispose() |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	fun hideAll() { |  | ||||||
| 		executeInInlayBatchMode(inlays.size) { inlays.values.forEach(Inlay<*>::dispose) } |  | ||||||
| 		inlays.clear() |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	private fun executeInInlayBatchMode(operations: Int, block: () -> Unit) { |  | ||||||
| 		editor.inlayModel.execute(operations > 1000, block) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,80 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens | ||||||
|  |  | ||||||
|  | import com.chylex.intellij.inspectionlens.editor.EditorLensFeatures | ||||||
|  | import com.intellij.openapi.application.ApplicationManager | ||||||
|  | import com.intellij.openapi.components.service | ||||||
|  | import com.intellij.openapi.fileEditor.FileEditorManager | ||||||
|  | import com.intellij.openapi.fileEditor.TextEditor | ||||||
|  | import com.intellij.openapi.project.ProjectManager | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Handles installation and uninstallation of plugin features in editors. | ||||||
|  |  */ | ||||||
|  | internal object InspectionLens { | ||||||
|  | 	const val PLUGIN_ID = "com.chylex.intellij.inspectionlens" | ||||||
|  | 	 | ||||||
|  | 	/** | ||||||
|  | 	 * Installs lenses into [editor]. | ||||||
|  | 	 */ | ||||||
|  | 	fun install(editor: TextEditor) { | ||||||
|  | 		EditorLensFeatures.install(editor.editor, service<InspectionLensPluginDisposableService>().intersect(editor)) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	/** | ||||||
|  | 	 * Installs lenses into all open editors. | ||||||
|  | 	 */ | ||||||
|  | 	fun install() { | ||||||
|  | 		forEachOpenEditor(::install) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	/** | ||||||
|  | 	 * Refreshes lenses in all open editors. | ||||||
|  | 	 */ | ||||||
|  | 	private fun refresh() { | ||||||
|  | 		forEachOpenEditor { | ||||||
|  | 			EditorLensFeatures.refresh(it.editor) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	/** | ||||||
|  | 	 * Schedules a refresh of lenses in all open editors. | ||||||
|  | 	 */ | ||||||
|  | 	fun scheduleRefresh() { | ||||||
|  | 		Refresh.schedule() | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	/** | ||||||
|  | 	 * Executes [action] on all open editors. | ||||||
|  | 	 */ | ||||||
|  | 	private inline fun forEachOpenEditor(action: (TextEditor) -> Unit) { | ||||||
|  | 		val projectManager = ProjectManager.getInstanceIfCreated() ?: return | ||||||
|  | 		 | ||||||
|  | 		for (project in projectManager.openProjects.filterNot { it.isDisposed }) { | ||||||
|  | 			for (editor in FileEditorManager.getInstance(project).allEditors.filterIsInstance<TextEditor>()) { | ||||||
|  | 				action(editor) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private object Refresh { | ||||||
|  | 		private var needsRefresh = false | ||||||
|  | 		 | ||||||
|  | 		fun schedule() { | ||||||
|  | 			synchronized(this) { | ||||||
|  | 				if (!needsRefresh) { | ||||||
|  | 					needsRefresh = true | ||||||
|  | 					ApplicationManager.getApplication().invokeLater(this::run) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		private fun run() { | ||||||
|  | 			synchronized(this) { | ||||||
|  | 				if (needsRefresh) { | ||||||
|  | 					needsRefresh = false | ||||||
|  | 					refresh() | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,20 +1,20 @@ | |||||||
| package com.chylex.intellij.inspectionlens | package com.chylex.intellij.inspectionlens | ||||||
| 
 | 
 | ||||||
| import com.intellij.openapi.fileEditor.FileEditorManager | import com.intellij.openapi.fileEditor.FileEditorManager | ||||||
| import com.intellij.openapi.fileEditor.FileEditorManagerListener | import com.intellij.openapi.fileEditor.FileOpenedSyncListener | ||||||
| import com.intellij.openapi.fileEditor.TextEditor | import com.intellij.openapi.fileEditor.TextEditor | ||||||
| import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider | import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider | ||||||
| import com.intellij.openapi.vfs.VirtualFile | import com.intellij.openapi.vfs.VirtualFile | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Listens for newly opened editors, and installs a [LensMarkupModelListener] on them. |  * Installs [InspectionLens] in newly opened editors. | ||||||
|  */ |  */ | ||||||
| class LensFileEditorListener : FileEditorManagerListener { | class InspectionLensFileOpenedListener : FileOpenedSyncListener { | ||||||
| 	override fun fileOpenedSync(source: FileEditorManager, file: VirtualFile, editorsWithProviders: MutableList<FileEditorWithProvider>) { | 	override fun fileOpenedSync(source: FileEditorManager, file: VirtualFile, editorsWithProviders: List<FileEditorWithProvider>) { | ||||||
| 		for (editorWrapper in editorsWithProviders) { | 		for (editorWrapper in editorsWithProviders) { | ||||||
| 			val fileEditor = editorWrapper.fileEditor | 			val fileEditor = editorWrapper.fileEditor | ||||||
| 			if (fileEditor is TextEditor) { | 			if (fileEditor is TextEditor) { | ||||||
| 				LensMarkupModelListener.install(fileEditor) | 				InspectionLens.install(fileEditor) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -1,10 +1,22 @@ | |||||||
| package com.chylex.intellij.inspectionlens | package com.chylex.intellij.inspectionlens | ||||||
|  |  | ||||||
| import com.intellij.openapi.Disposable | import com.intellij.openapi.Disposable | ||||||
|  | import com.intellij.openapi.components.Service | ||||||
|  | import com.intellij.openapi.rd.createLifetime | ||||||
|  | import com.intellij.openapi.rd.createNestedDisposable | ||||||
|  | import com.jetbrains.rd.util.lifetime.Lifetime | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Gets automatically disposed when the plugin is unloaded. |  * Gets automatically disposed when the plugin is unloaded. | ||||||
|  */ |  */ | ||||||
|  | @Service | ||||||
| class InspectionLensPluginDisposableService : Disposable { | class InspectionLensPluginDisposableService : Disposable { | ||||||
|  | 	/** | ||||||
|  | 	 * Creates a [Disposable] that will be disposed when either plugin is unloaded, or the [other] [Disposable] is disposed. | ||||||
|  | 	 */ | ||||||
|  | 	fun intersect(other: Disposable): Disposable { | ||||||
|  | 		return Lifetime.intersect(createLifetime(), other.createLifetime()).createNestedDisposable("InspectionLensIntersectedLifetime") | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
| 	override fun dispose() {} | 	override fun dispose() {} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,42 +2,14 @@ package com.chylex.intellij.inspectionlens | |||||||
|  |  | ||||||
| import com.intellij.ide.plugins.DynamicPluginListener | import com.intellij.ide.plugins.DynamicPluginListener | ||||||
| import com.intellij.ide.plugins.IdeaPluginDescriptor | import com.intellij.ide.plugins.IdeaPluginDescriptor | ||||||
| import com.intellij.openapi.fileEditor.FileEditorManager |  | ||||||
| import com.intellij.openapi.fileEditor.TextEditor |  | ||||||
| import com.intellij.openapi.project.ProjectManager |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Handles dynamic plugin loading. |  * Installs [InspectionLens] in open editors when the plugin is loaded. | ||||||
|  *  |  | ||||||
|  * On load, it installs the [LensMarkupModelListener] to all open editors. |  | ||||||
|  * On unload, it removes all lenses from all open editors. |  | ||||||
|  */ |  */ | ||||||
| class InspectionLensPluginListener : DynamicPluginListener { | class InspectionLensPluginListener : DynamicPluginListener { | ||||||
| 	companion object { |  | ||||||
| 		private const val PLUGIN_ID = "com.chylex.intellij.inspectionlens" |  | ||||||
| 		 |  | ||||||
| 		private inline fun ProjectManager.forEachEditor(action: (TextEditor) -> Unit) { |  | ||||||
| 			for (project in this.openProjects.filterNot { it.isDisposed }) { |  | ||||||
| 				val fileEditorManager = FileEditorManager.getInstance(project) |  | ||||||
| 				 |  | ||||||
| 				for (editor in fileEditorManager.allEditors.filterIsInstance<TextEditor>()) { |  | ||||||
| 					action(editor) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	override fun pluginLoaded(pluginDescriptor: IdeaPluginDescriptor) { | 	override fun pluginLoaded(pluginDescriptor: IdeaPluginDescriptor) { | ||||||
| 		if (pluginDescriptor.pluginId.idString == PLUGIN_ID) { | 		if (pluginDescriptor.pluginId.idString == InspectionLens.PLUGIN_ID) { | ||||||
| 			ProjectManager.getInstanceIfCreated()?.forEachEditor(LensMarkupModelListener.Companion::install) | 			InspectionLens.install() | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	override fun beforePluginUnload(pluginDescriptor: IdeaPluginDescriptor, isUpdate: Boolean) { |  | ||||||
| 		if (pluginDescriptor.pluginId.idString == PLUGIN_ID) { |  | ||||||
| 			ProjectManager.getInstanceIfCreated()?.forEachEditor { |  | ||||||
| 				EditorInlayLensManager.remove(it.editor) |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,101 +0,0 @@ | |||||||
| package com.chylex.intellij.inspectionlens |  | ||||||
|  |  | ||||||
| import com.chylex.intellij.inspectionlens.util.MultiParentDisposable |  | ||||||
| import com.intellij.codeInsight.daemon.impl.HighlightInfo |  | ||||||
| import com.intellij.lang.annotation.HighlightSeverity |  | ||||||
| import com.intellij.openapi.application.ApplicationManager |  | ||||||
| import com.intellij.openapi.editor.Editor |  | ||||||
| import com.intellij.openapi.editor.ex.MarkupModelEx |  | ||||||
| import com.intellij.openapi.editor.ex.RangeHighlighterEx |  | ||||||
| import com.intellij.openapi.editor.impl.DocumentMarkupModel |  | ||||||
| import com.intellij.openapi.editor.impl.event.MarkupModelListener |  | ||||||
| import com.intellij.openapi.editor.markup.RangeHighlighter |  | ||||||
| import com.intellij.openapi.fileEditor.TextEditor |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Listens for inspection highlights and reports them to [EditorInlayLensManager]. |  | ||||||
|  */ |  | ||||||
| class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelListener { |  | ||||||
| 	private val lens = EditorInlayLensManager.getOrCreate(editor) |  | ||||||
| 	 |  | ||||||
| 	override fun afterAdded(highlighter: RangeHighlighterEx) { |  | ||||||
| 		showIfValid(highlighter) |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	override fun attributesChanged(highlighter: RangeHighlighterEx, renderersChanged: Boolean, fontStyleOrColorChanged: Boolean) { |  | ||||||
| 		showIfValid(highlighter) |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	override fun beforeRemoved(highlighter: RangeHighlighterEx) { |  | ||||||
| 		lens.hide(highlighter) |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	private fun showIfValid(highlighter: RangeHighlighter) { |  | ||||||
| 		runWithHighlighterIfValid(highlighter, lens::show, ::showAsynchronously) |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	private fun showAllValid(highlighters: Array<RangeHighlighter>) { |  | ||||||
| 		val immediateHighlighters = mutableListOf<HighlighterWithInfo>() |  | ||||||
| 		 |  | ||||||
| 		for (highlighter in highlighters) { |  | ||||||
| 			runWithHighlighterIfValid(highlighter, immediateHighlighters::add, ::showAsynchronously) |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		lens.showAll(immediateHighlighters) |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	private fun showAsynchronously(highlighterWithInfo: HighlighterWithInfo.Async) { |  | ||||||
| 		highlighterWithInfo.requestDescription { |  | ||||||
| 			if (highlighterWithInfo.highlighter.isValid && highlighterWithInfo.hasDescription) { |  | ||||||
| 				lens.show(highlighterWithInfo) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	companion object { |  | ||||||
| 		private val MINIMUM_SEVERITY = HighlightSeverity.TEXT_ATTRIBUTES.myVal + 1 |  | ||||||
| 		 |  | ||||||
| 		private inline fun runWithHighlighterIfValid(highlighter: RangeHighlighter, actionForImmediate: (HighlighterWithInfo) -> Unit, actionForAsync: (HighlighterWithInfo.Async) -> Unit) { |  | ||||||
| 			if (!highlighter.isValid) { |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			 |  | ||||||
| 			val info = HighlightInfo.fromRangeHighlighter(highlighter) |  | ||||||
| 			if (info == null || info.severity.myVal < MINIMUM_SEVERITY) { |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			 |  | ||||||
| 			processHighlighterWithInfo(HighlighterWithInfo.from(highlighter, info), actionForImmediate, actionForAsync) |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		private inline fun processHighlighterWithInfo(highlighterWithInfo: HighlighterWithInfo, actionForImmediate: (HighlighterWithInfo) -> Unit, actionForAsync: (HighlighterWithInfo.Async) -> Unit) { |  | ||||||
| 			if (highlighterWithInfo is HighlighterWithInfo.Async) { |  | ||||||
| 				actionForAsync(highlighterWithInfo) |  | ||||||
| 			} |  | ||||||
| 			else if (highlighterWithInfo.hasDescription) { |  | ||||||
| 				actionForImmediate(highlighterWithInfo) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		/** |  | ||||||
| 		 * Attaches a new [LensMarkupModelListener] to the document model of the provided [TextEditor], and reports all existing inspection highlights to [EditorInlayLensManager]. |  | ||||||
| 		 *  |  | ||||||
| 		 * The [LensMarkupModelListener] will be disposed when either the [TextEditor] is disposed, or via [InspectionLensPluginDisposableService] when the plugin is unloaded. |  | ||||||
| 		 */ |  | ||||||
| 		fun install(textEditor: TextEditor) { |  | ||||||
| 			val editor = textEditor.editor |  | ||||||
| 			val markupModel = DocumentMarkupModel.forDocument(editor.document, editor.project, false) |  | ||||||
| 			if (markupModel is MarkupModelEx) { |  | ||||||
| 				val pluginDisposable = ApplicationManager.getApplication().getService(InspectionLensPluginDisposableService::class.java) |  | ||||||
| 				 |  | ||||||
| 				val listenerDisposable = MultiParentDisposable() |  | ||||||
| 				listenerDisposable.registerWithParent(textEditor) |  | ||||||
| 				listenerDisposable.registerWithParent(pluginDisposable) |  | ||||||
| 				 |  | ||||||
| 				val listener = LensMarkupModelListener(editor) |  | ||||||
| 				markupModel.addMarkupModelListener(listenerDisposable.self, listener) |  | ||||||
| 				listener.showAllValid(markupModel.allHighlighters) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,55 +0,0 @@ | |||||||
| package com.chylex.intellij.inspectionlens |  | ||||||
|  |  | ||||||
| import com.intellij.codeInsight.daemon.impl.HighlightInfo |  | ||||||
| import com.intellij.codeInsight.daemon.impl.HintRenderer |  | ||||||
| import com.intellij.openapi.editor.Editor |  | ||||||
| import com.intellij.openapi.editor.Inlay |  | ||||||
| import com.intellij.openapi.editor.markup.TextAttributes |  | ||||||
| import java.awt.Font |  | ||||||
| import java.awt.Graphics |  | ||||||
| import java.awt.Rectangle |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Renders the text of an inspection lens. |  | ||||||
|  */ |  | ||||||
| class LensRenderer(info: HighlightInfo) : HintRenderer(null) { |  | ||||||
| 	private lateinit var severity: LensSeverity |  | ||||||
| 	 |  | ||||||
| 	init { |  | ||||||
| 		setPropertiesFrom(info) |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	fun setPropertiesFrom(info: HighlightInfo) { |  | ||||||
| 		text = getValidDescriptionText(info.description) |  | ||||||
| 		severity = LensSeverity.from(info.severity) |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	override fun paint(inlay: Inlay<*>, g: Graphics, r: Rectangle, textAttributes: TextAttributes) { |  | ||||||
| 		fixBaselineForTextRendering(r) |  | ||||||
| 		super.paint(inlay, g, r, textAttributes) |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	override fun getTextAttributes(editor: Editor): TextAttributes { |  | ||||||
| 		return ATTRIBUTES_SINGLETON.also { it.foregroundColor = severity.getColor(editor) } |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	override fun useEditorFont(): Boolean { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	private companion object { |  | ||||||
| 		private val ATTRIBUTES_SINGLETON = TextAttributes(null, null, null, null, Font.ITALIC) |  | ||||||
| 		 |  | ||||||
| 		private fun getValidDescriptionText(text: String?): String { |  | ||||||
| 			return if (text.isNullOrBlank()) " " else addMissingPeriod(text) |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		private fun addMissingPeriod(text: String): String { |  | ||||||
| 			return if (text.endsWith('.')) text else "$text." |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		private fun fixBaselineForTextRendering(rect: Rectangle) { |  | ||||||
| 			rect.y += 1 |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,59 +0,0 @@ | |||||||
| package com.chylex.intellij.inspectionlens |  | ||||||
|  |  | ||||||
| import com.intellij.lang.annotation.HighlightSeverity |  | ||||||
| import com.intellij.openapi.editor.Editor |  | ||||||
| import com.intellij.ui.ColorUtil |  | ||||||
| import java.awt.Color |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Determines properties of inspection lenses based on severity. |  | ||||||
|  */ |  | ||||||
| enum class LensSeverity(color: Color, darkThemeBrightening: Int, lightThemeDarkening: Int) { |  | ||||||
| 	ERROR( |  | ||||||
| 		Color(158, 41, 39), |  | ||||||
| 		darkThemeBrightening = 4, |  | ||||||
| 		lightThemeDarkening = 1, |  | ||||||
| 	), |  | ||||||
| 	 |  | ||||||
| 	WARNING( |  | ||||||
| 		Color(190, 145, 23), |  | ||||||
| 		darkThemeBrightening = 1, |  | ||||||
| 		lightThemeDarkening = 4, |  | ||||||
| 	), |  | ||||||
| 	 |  | ||||||
| 	WEAK_WARNING( |  | ||||||
| 		Color(117, 109, 86), |  | ||||||
| 		darkThemeBrightening = 3, |  | ||||||
| 		lightThemeDarkening = 3, |  | ||||||
| 	), |  | ||||||
| 	 |  | ||||||
| 	SERVER_PROBLEM( |  | ||||||
| 		Color(176, 97, 0), |  | ||||||
| 		darkThemeBrightening = 2, |  | ||||||
| 		lightThemeDarkening = 4, |  | ||||||
| 	), |  | ||||||
| 	 |  | ||||||
| 	OTHER( |  | ||||||
| 		Color(128, 128, 128), |  | ||||||
| 		darkThemeBrightening = 2, |  | ||||||
| 		lightThemeDarkening = 1, |  | ||||||
| 	); |  | ||||||
| 	 |  | ||||||
| 	private val darkThemeColor = ColorUtil.desaturate(ColorUtil.brighter(color, darkThemeBrightening), 2) |  | ||||||
| 	private val lightThemeColor = ColorUtil.saturate(ColorUtil.darker(color, lightThemeDarkening), 1) |  | ||||||
| 	 |  | ||||||
| 	fun getColor(editor: Editor): Color { |  | ||||||
| 		val isDarkTheme = ColorUtil.isDark(editor.colorsScheme.defaultBackground) |  | ||||||
| 		return if (isDarkTheme) darkThemeColor else lightThemeColor |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	companion object { |  | ||||||
| 		fun from(severity: HighlightSeverity) = when (severity) { |  | ||||||
| 			HighlightSeverity.ERROR                           -> ERROR |  | ||||||
| 			HighlightSeverity.WARNING                         -> WARNING |  | ||||||
| 			HighlightSeverity.WEAK_WARNING                    -> WEAK_WARNING |  | ||||||
| 			HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING -> SERVER_PROBLEM |  | ||||||
| 			else                                              -> OTHER |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.compatibility | ||||||
|  |  | ||||||
|  | import com.chylex.intellij.inspectionlens.editor.lens.LensSeverity | ||||||
|  | import com.intellij.grazie.ide.TextProblemSeverities | ||||||
|  | import com.intellij.openapi.project.Project | ||||||
|  | import com.intellij.openapi.startup.ProjectActivity | ||||||
|  |  | ||||||
|  | class GrazieSupport : ProjectActivity { | ||||||
|  | 	override suspend fun execute(project: Project) { | ||||||
|  | 		LensSeverity.registerMapping(TextProblemSeverities.GRAMMAR_ERROR, LensSeverity.ERROR) | ||||||
|  | 		LensSeverity.registerMapping(TextProblemSeverities.STYLE_ERROR, LensSeverity.GRAZIE) | ||||||
|  | 		LensSeverity.registerMapping(TextProblemSeverities.STYLE_WARNING, LensSeverity.GRAZIE) | ||||||
|  | 		LensSeverity.registerMapping(TextProblemSeverities.STYLE_SUGGESTION, LensSeverity.GRAZIE) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.compatibility | ||||||
|  |  | ||||||
|  | import com.chylex.intellij.inspectionlens.editor.lens.LensSeverity | ||||||
|  | import com.intellij.lang.annotation.HighlightSeverity | ||||||
|  | import com.intellij.openapi.diagnostic.logger | ||||||
|  | import com.intellij.profile.codeInspection.InspectionProfileManager | ||||||
|  | import com.intellij.spellchecker.SpellCheckerSeveritiesProvider | ||||||
|  |  | ||||||
|  | internal object SpellCheckerSupport { | ||||||
|  | 	private val log = logger<SpellCheckerSupport>() | ||||||
|  | 	 | ||||||
|  | 	fun load() { | ||||||
|  | 		typoSeverity?.let { LensSeverity.registerMapping(it, LensSeverity.TYPO) } | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private val typoSeverity: HighlightSeverity? | ||||||
|  | 		get() = try { | ||||||
|  | 			SpellCheckerSeveritiesProvider.TYPO | ||||||
|  | 		} catch (e: NoClassDefFoundError) { | ||||||
|  | 			log.warn("Falling back to registered severity search due to ${e.javaClass.simpleName}: ${e.message}") | ||||||
|  | 			InspectionProfileManager.getInstance().severityRegistrar.getSeverity("TYPO") | ||||||
|  | 		} | ||||||
|  | } | ||||||
| @@ -0,0 +1,56 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor | ||||||
|  |  | ||||||
|  | import com.intellij.openapi.Disposable | ||||||
|  | import com.intellij.openapi.editor.Editor | ||||||
|  | import com.intellij.openapi.editor.ex.FoldingModelEx | ||||||
|  | import com.intellij.openapi.editor.ex.MarkupModelEx | ||||||
|  | import com.intellij.openapi.editor.impl.DocumentMarkupModel | ||||||
|  | import com.intellij.openapi.util.Disposer | ||||||
|  | import com.intellij.openapi.util.Key | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Manages Inspection Lens features for a single [Editor]. | ||||||
|  |  */ | ||||||
|  | internal class EditorLensFeatures private constructor( | ||||||
|  | 	editor: Editor, | ||||||
|  | 	private val markupModel: MarkupModelEx, | ||||||
|  | 	foldingModel: FoldingModelEx?, | ||||||
|  | 	disposable: Disposable | ||||||
|  | ) { | ||||||
|  | 	private val lensManager = EditorLensManager(editor) | ||||||
|  | 	private val lensManagerDispatcher = EditorLensManagerDispatcher(lensManager) | ||||||
|  | 	private val markupModelListener = LensMarkupModelListener(lensManagerDispatcher) | ||||||
|  | 	 | ||||||
|  | 	init { | ||||||
|  | 		markupModel.addMarkupModelListener(disposable, markupModelListener) | ||||||
|  | 		markupModelListener.showAllValid(markupModel.allHighlighters) | ||||||
|  | 		 | ||||||
|  | 		foldingModel?.addListener(LensFoldingModelListener(lensManager), disposable) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun refresh() { | ||||||
|  | 		markupModelListener.hideAll() | ||||||
|  | 		markupModelListener.showAllValid(markupModel.allHighlighters) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	companion object { | ||||||
|  | 		private val EDITOR_KEY = Key<EditorLensFeatures>(EditorLensFeatures::class.java.name) | ||||||
|  | 		 | ||||||
|  | 		fun install(editor: Editor, disposable: Disposable) { | ||||||
|  | 			if (editor.getUserData(EDITOR_KEY) != null) { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			val markupModel = DocumentMarkupModel.forDocument(editor.document, editor.project, false) as? MarkupModelEx ?: return | ||||||
|  | 			val foldingModel = editor.foldingModel as? FoldingModelEx | ||||||
|  | 			val features = EditorLensFeatures(editor, markupModel, foldingModel, disposable) | ||||||
|  | 			 | ||||||
|  | 			editor.putUserData(EDITOR_KEY, features) | ||||||
|  | 			Disposer.register(disposable) { editor.putUserData(EDITOR_KEY, null) } | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		fun refresh(editor: Editor) { | ||||||
|  | 			editor.getUserData(EDITOR_KEY)?.refresh() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,97 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor | ||||||
|  |  | ||||||
|  | import com.chylex.intellij.inspectionlens.editor.lens.EditorLens | ||||||
|  | import com.chylex.intellij.inspectionlens.settings.LensSettingsState | ||||||
|  | import com.intellij.openapi.components.service | ||||||
|  | import com.intellij.openapi.editor.Editor | ||||||
|  | import com.intellij.openapi.editor.markup.RangeHighlighter | ||||||
|  | import java.util.IdentityHashMap | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Manages visible inspection lenses for an [Editor]. | ||||||
|  |  */ | ||||||
|  | internal class EditorLensManager(private val editor: Editor) { | ||||||
|  | 	private val lenses = IdentityHashMap<RangeHighlighter, EditorLens>() | ||||||
|  | 	private val settings = service<LensSettingsState>() | ||||||
|  | 	 | ||||||
|  | 	private fun show(highlighterWithInfo: HighlighterWithInfo) { | ||||||
|  | 		val (highlighter, info) = highlighterWithInfo | ||||||
|  | 		 | ||||||
|  | 		if (!highlighter.isValid) { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		val existingLens = lenses[highlighter] | ||||||
|  | 		if (existingLens != null) { | ||||||
|  | 			if (existingLens.update(info, settings)) { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			existingLens.hide() | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		val newLens = EditorLens.show(editor, info, settings) | ||||||
|  | 		if (newLens != null) { | ||||||
|  | 			lenses[highlighter] = newLens | ||||||
|  | 		} | ||||||
|  | 		else if (existingLens != null) { | ||||||
|  | 			lenses.remove(highlighter) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun hide(highlighter: RangeHighlighter) { | ||||||
|  | 		lenses.remove(highlighter)?.hide() | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun hideAll() { | ||||||
|  | 		executeInBatchMode(lenses.size) { | ||||||
|  | 			lenses.values.forEach(EditorLens::hide) | ||||||
|  | 			lenses.clear() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun execute(commands: Collection<Command>) { | ||||||
|  | 		executeInBatchMode(commands.size) { | ||||||
|  | 			commands.forEach { it.apply(this) } | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun onFoldRegionsChanged() { | ||||||
|  | 		lenses.values.forEach(EditorLens::onFoldRegionsChanged) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	sealed interface Command { | ||||||
|  | 		fun apply(lensManager: EditorLensManager) | ||||||
|  | 		 | ||||||
|  | 		class Show(private val highlighter: HighlighterWithInfo) : Command { | ||||||
|  | 			override fun apply(lensManager: EditorLensManager) { | ||||||
|  | 				lensManager.show(highlighter) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		class Hide(private val highlighter: RangeHighlighter) : Command { | ||||||
|  | 			override fun apply(lensManager: EditorLensManager) { | ||||||
|  | 				lensManager.hide(highlighter) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		object HideAll : Command { | ||||||
|  | 			override fun apply(lensManager: EditorLensManager) { | ||||||
|  | 				lensManager.hideAll() | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	/** | ||||||
|  | 	 * Batch mode affects both inlays and highlighters used for line colors. | ||||||
|  | 	 */ | ||||||
|  | 	@Suppress("ConvertLambdaToReference") | ||||||
|  | 	private inline fun executeInBatchMode(operations: Int, crossinline action: () -> Unit) { | ||||||
|  | 		if (operations > 1000) { | ||||||
|  | 			editor.inlayModel.execute(true) { action() } | ||||||
|  | 		} | ||||||
|  | 		else { | ||||||
|  | 			action() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,45 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor | ||||||
|  |  | ||||||
|  | import com.intellij.openapi.application.ApplicationManager | ||||||
|  | import com.intellij.openapi.editor.markup.RangeHighlighter | ||||||
|  |  | ||||||
|  | internal class EditorLensManagerDispatcher(private val lensManager: EditorLensManager) { | ||||||
|  | 	private var queuedItems = mutableListOf<EditorLensManager.Command>() | ||||||
|  | 	private var isEnqueued = false | ||||||
|  | 	 | ||||||
|  | 	fun show(highlighterWithInfo: HighlighterWithInfo) { | ||||||
|  | 		enqueue(EditorLensManager.Command.Show(highlighterWithInfo)) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun hide(highlighter: RangeHighlighter) { | ||||||
|  | 		enqueue(EditorLensManager.Command.Hide(highlighter)) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun hideAll() { | ||||||
|  | 		enqueue(EditorLensManager.Command.HideAll) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun enqueue(item: EditorLensManager.Command) { | ||||||
|  | 		synchronized(this) { | ||||||
|  | 			queuedItems.add(item) | ||||||
|  | 			 | ||||||
|  | 			// Enqueue even if already on dispatch thread to debounce consecutive calls. | ||||||
|  | 			if (!isEnqueued) { | ||||||
|  | 				isEnqueued = true | ||||||
|  | 				ApplicationManager.getApplication().invokeLater(::process) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun process() { | ||||||
|  | 		var itemsToProcess: List<EditorLensManager.Command> | ||||||
|  | 		 | ||||||
|  | 		synchronized(this) { | ||||||
|  | 			itemsToProcess = queuedItems | ||||||
|  | 			queuedItems = mutableListOf() | ||||||
|  | 			isEnqueued = false | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		lensManager.execute(itemsToProcess) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package com.chylex.intellij.inspectionlens | package com.chylex.intellij.inspectionlens.editor | ||||||
| 
 | 
 | ||||||
| import com.intellij.codeInsight.daemon.impl.AsyncDescriptionSupplier | import com.intellij.codeInsight.daemon.impl.AsyncDescriptionSupplier | ||||||
| import com.intellij.codeInsight.daemon.impl.HighlightInfo | import com.intellij.codeInsight.daemon.impl.HighlightInfo | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor | ||||||
|  |  | ||||||
|  | import com.intellij.openapi.editor.ex.FoldingListener | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Listens for code folding events and reports them to [EditorLensManager]. | ||||||
|  |  */ | ||||||
|  | internal class LensFoldingModelListener(private val lensManager: EditorLensManager) : FoldingListener { | ||||||
|  | 	override fun onFoldProcessingEnd() { | ||||||
|  | 		lensManager.onFoldRegionsChanged() | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,69 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor | ||||||
|  |  | ||||||
|  | import com.chylex.intellij.inspectionlens.settings.LensSettingsState | ||||||
|  | import com.intellij.codeInsight.daemon.impl.HighlightInfo | ||||||
|  | import com.intellij.openapi.components.service | ||||||
|  | import com.intellij.openapi.editor.ex.RangeHighlighterEx | ||||||
|  | import com.intellij.openapi.editor.impl.event.MarkupModelListener | ||||||
|  | import com.intellij.openapi.editor.markup.RangeHighlighter | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Listens for inspection highlights and reports them to [EditorLensManager]. | ||||||
|  |  */ | ||||||
|  | internal class LensMarkupModelListener(private val lensManagerDispatcher: EditorLensManagerDispatcher) : MarkupModelListener { | ||||||
|  | 	private val settings = service<LensSettingsState>() | ||||||
|  | 	 | ||||||
|  | 	override fun afterAdded(highlighter: RangeHighlighterEx) { | ||||||
|  | 		showIfValid(highlighter) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun attributesChanged(highlighter: RangeHighlighterEx, renderersChanged: Boolean, fontStyleOrColorChanged: Boolean) { | ||||||
|  | 		showIfValid(highlighter) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun beforeRemoved(highlighter: RangeHighlighterEx) { | ||||||
|  | 		if (getFilteredHighlightInfo(highlighter) != null) { | ||||||
|  | 			lensManagerDispatcher.hide(highlighter) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun showIfValid(highlighter: RangeHighlighter) { | ||||||
|  | 		runWithHighlighterIfValid(highlighter, lensManagerDispatcher::show, ::showAsynchronously) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun showAsynchronously(highlighterWithInfo: HighlighterWithInfo.Async) { | ||||||
|  | 		highlighterWithInfo.requestDescription { | ||||||
|  | 			if (highlighterWithInfo.highlighter.isValid && highlighterWithInfo.hasDescription) { | ||||||
|  | 				lensManagerDispatcher.show(highlighterWithInfo) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun showAllValid(highlighters: Array<RangeHighlighter>) { | ||||||
|  | 		highlighters.forEach(::showIfValid) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun hideAll() { | ||||||
|  | 		lensManagerDispatcher.hideAll() | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun getFilteredHighlightInfo(highlighter: RangeHighlighter): HighlightInfo? { | ||||||
|  | 		return HighlightInfo.fromRangeHighlighter(highlighter)?.takeIf { settings.severityFilter.test(it.severity) } | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private inline fun runWithHighlighterIfValid(highlighter: RangeHighlighter, actionForImmediate: (HighlighterWithInfo) -> Unit, actionForAsync: (HighlighterWithInfo.Async) -> Unit) { | ||||||
|  | 		val info = highlighter.takeIf { it.isValid }?.let(::getFilteredHighlightInfo) | ||||||
|  | 		if (info != null) { | ||||||
|  | 			processHighlighterWithInfo(HighlighterWithInfo.from(highlighter, info), actionForImmediate, actionForAsync) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private inline fun processHighlighterWithInfo(highlighterWithInfo: HighlighterWithInfo, actionForImmediate: (HighlighterWithInfo) -> Unit, actionForAsync: (HighlighterWithInfo.Async) -> Unit) { | ||||||
|  | 		if (highlighterWithInfo is HighlighterWithInfo.Async) { | ||||||
|  | 			actionForAsync(highlighterWithInfo) | ||||||
|  | 		} | ||||||
|  | 		else if (highlighterWithInfo.hasDescription) { | ||||||
|  | 			actionForImmediate(highlighterWithInfo) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor.lens | ||||||
|  |  | ||||||
|  | import com.chylex.intellij.inspectionlens.settings.LensSettingsState | ||||||
|  | import com.intellij.codeInsight.daemon.impl.HighlightInfo | ||||||
|  | import com.intellij.openapi.editor.Editor | ||||||
|  |  | ||||||
|  | internal class EditorLens private constructor(private var inlay: EditorLensInlay, private var lineBackground: EditorLensLineBackground, private var severity: LensSeverity) { | ||||||
|  | 	fun update(info: HighlightInfo, settings: LensSettingsState): Boolean { | ||||||
|  | 		val editor = inlay.editor | ||||||
|  | 		val oldSeverity = severity | ||||||
|  | 		 | ||||||
|  | 		severity = LensSeverity.from(info.severity) | ||||||
|  | 		 | ||||||
|  | 		if (!inlay.tryUpdate(info)) { | ||||||
|  | 			inlay = EditorLensInlay.show(editor, info, settings) ?: return false | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		if (lineBackground.isInvalid || oldSeverity != severity) { | ||||||
|  | 			lineBackground.hide(editor) | ||||||
|  | 			lineBackground = EditorLensLineBackground.show(editor, info) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun onFoldRegionsChanged() { | ||||||
|  | 		lineBackground.onFoldRegionsChanged(inlay.editor, severity) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun hide() { | ||||||
|  | 		inlay.hide() | ||||||
|  | 		lineBackground.hide(inlay.editor) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	companion object { | ||||||
|  | 		fun show(editor: Editor, info: HighlightInfo, settings: LensSettingsState): EditorLens? { | ||||||
|  | 			val inlay = EditorLensInlay.show(editor, info, settings) ?: return null | ||||||
|  | 			val lineBackground = EditorLensLineBackground.show(editor, info) | ||||||
|  | 			val severity = LensSeverity.from(info.severity) | ||||||
|  | 			return EditorLens(inlay, lineBackground, severity) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,76 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor.lens | ||||||
|  |  | ||||||
|  | import com.chylex.intellij.inspectionlens.settings.LensSettingsState | ||||||
|  | import com.intellij.codeInsight.daemon.impl.HighlightInfo | ||||||
|  | import com.intellij.openapi.editor.Editor | ||||||
|  | import com.intellij.openapi.editor.Inlay | ||||||
|  | import com.intellij.openapi.editor.InlayProperties | ||||||
|  |  | ||||||
|  | @JvmInline | ||||||
|  | internal value class EditorLensInlay(private val inlay: Inlay<LensRenderer>) { | ||||||
|  | 	val editor | ||||||
|  | 		get() = inlay.editor | ||||||
|  | 	 | ||||||
|  | 	fun tryUpdate(info: HighlightInfo): Boolean { | ||||||
|  | 		if (!inlay.isValid) { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		inlay.renderer.setPropertiesFrom(info) | ||||||
|  | 		inlay.update() | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun hide() { | ||||||
|  | 		inlay.dispose() | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	companion object { | ||||||
|  | 		fun show(editor: Editor, info: HighlightInfo, settings: LensSettingsState): EditorLensInlay? { | ||||||
|  | 			val offset = getInlayHintOffset(info) | ||||||
|  | 			val priority = getInlayHintPriority(editor, info) | ||||||
|  | 			val renderer = LensRenderer(info, settings) | ||||||
|  | 			 | ||||||
|  | 			val properties = InlayProperties() | ||||||
|  | 				.relatesToPrecedingText(true) | ||||||
|  | 				.disableSoftWrapping(true) | ||||||
|  | 				.priority(priority) | ||||||
|  | 			 | ||||||
|  | 			return editor.inlayModel.addAfterLineEndElement(offset, properties, renderer) | ||||||
|  | 				?.also(renderer::setInlay) | ||||||
|  | 				?.let(::EditorLensInlay) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		/** | ||||||
|  | 		 * Highest allowed severity for the purposes of sorting multiple highlights at the same offset. | ||||||
|  | 		 * The value is a little higher than the highest [com.intellij.lang.annotation.HighlightSeverity], in case severities with higher values are introduced in the future. | ||||||
|  | 		 */ | ||||||
|  | 		private const val MAXIMUM_SEVERITY = 500 | ||||||
|  | 		private const val MAXIMUM_POSITION = ((Int.MAX_VALUE / MAXIMUM_SEVERITY) * 2) - 1 | ||||||
|  | 		 | ||||||
|  | 		private fun getInlayHintOffset(info: HighlightInfo): Int { | ||||||
|  | 			// Ensures a highlight at the end of a line does not overflow to the next line. | ||||||
|  | 			return info.actualEndOffset - 1 | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		fun getInlayHintPriority(position: Int, severity: Int): Int { | ||||||
|  | 			// Sorts highlights first by position on the line, then by severity. | ||||||
|  | 			val positionBucket = position.coerceIn(0, MAXIMUM_POSITION) * MAXIMUM_SEVERITY | ||||||
|  | 			// The multiplication can overflow, but subtracting overflowed result from Int.MAX_VALUE does not break continuity. | ||||||
|  | 			val positionFactor = Integer.MAX_VALUE - positionBucket | ||||||
|  | 			val severityFactor = severity.coerceIn(0, MAXIMUM_SEVERITY) - MAXIMUM_SEVERITY | ||||||
|  | 			return positionFactor + severityFactor | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		private fun getInlayHintPriority(editor: Editor, info: HighlightInfo): Int { | ||||||
|  | 			val startOffset = info.actualStartOffset | ||||||
|  | 			val positionOnLine = startOffset - getLineStartOffset(editor, startOffset) | ||||||
|  | 			return getInlayHintPriority(positionOnLine, info.severity.myVal) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		private fun getLineStartOffset(editor: Editor, offset: Int): Int { | ||||||
|  | 			val position = editor.offsetToLogicalPosition(offset) | ||||||
|  | 			return editor.document.getLineStartOffset(position.line) | ||||||
|  | 		}	 | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,49 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor.lens | ||||||
|  |  | ||||||
|  | import com.intellij.codeInsight.daemon.impl.HighlightInfo | ||||||
|  | import com.intellij.openapi.editor.Editor | ||||||
|  | import com.intellij.openapi.editor.ex.RangeHighlighterEx | ||||||
|  | import com.intellij.openapi.editor.markup.HighlighterLayer | ||||||
|  | import com.intellij.openapi.editor.markup.HighlighterTargetArea.LINES_IN_RANGE | ||||||
|  | import com.intellij.openapi.editor.markup.RangeHighlighter | ||||||
|  | import com.intellij.openapi.editor.markup.TextAttributes | ||||||
|  |  | ||||||
|  | @JvmInline | ||||||
|  | internal value class EditorLensLineBackground(private val highlighter: RangeHighlighter) { | ||||||
|  | 	val isInvalid | ||||||
|  | 		get() = !highlighter.isValid | ||||||
|  | 	 | ||||||
|  | 	fun onFoldRegionsChanged(editor: Editor, severity: LensSeverity) { | ||||||
|  | 		if (highlighter is RangeHighlighterEx) { | ||||||
|  | 			highlighter.textAttributes = getAttributes(editor, highlighter.startOffset, highlighter.endOffset, severity) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun hide(editor: Editor) { | ||||||
|  | 		editor.markupModel.removeHighlighter(highlighter) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	companion object { | ||||||
|  | 		fun show(editor: Editor, info: HighlightInfo): EditorLensLineBackground { | ||||||
|  | 			val startOffset = info.actualStartOffset | ||||||
|  | 			val endOffset = (info.actualEndOffset - 1).coerceAtLeast(startOffset) | ||||||
|  | 			 | ||||||
|  | 			val severity = LensSeverity.from(info.severity) | ||||||
|  | 			val layer = getHighlightLayer(severity) | ||||||
|  | 			val attributes = getAttributes(editor, startOffset, endOffset, severity) | ||||||
|  | 			 | ||||||
|  | 			return EditorLensLineBackground(editor.markupModel.addRangeHighlighter(startOffset, endOffset, layer, attributes, LINES_IN_RANGE)) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		private fun getHighlightLayer(severity: LensSeverity): Int { | ||||||
|  | 			return HighlighterLayer.CARET_ROW - 100 - severity.ordinal | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		private fun getAttributes(editor: Editor, startOffset: Int, endOffset: Int, severity: LensSeverity): TextAttributes? { | ||||||
|  | 			return if (editor.foldingModel.let { it.isOffsetCollapsed(startOffset) || it.isOffsetCollapsed(endOffset) }) | ||||||
|  | 				null | ||||||
|  | 			else | ||||||
|  | 				severity.lineAttributes | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,54 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor.lens | ||||||
|  |  | ||||||
|  | import com.intellij.codeInsight.daemon.impl.IntentionsUI | ||||||
|  | import com.intellij.codeInsight.hint.HintManager | ||||||
|  | import com.intellij.codeInsight.intention.actions.ShowIntentionActionsAction | ||||||
|  | import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler | ||||||
|  | import com.intellij.lang.LangBundle | ||||||
|  | import com.intellij.openapi.actionSystem.ActionManager | ||||||
|  | import com.intellij.openapi.actionSystem.ActionPlaces | ||||||
|  | import com.intellij.openapi.actionSystem.IdeActions | ||||||
|  | import com.intellij.openapi.actionSystem.ex.ActionUtil | ||||||
|  | import com.intellij.openapi.editor.Editor | ||||||
|  | import com.intellij.openapi.project.Project | ||||||
|  | import com.intellij.psi.PsiDocumentManager | ||||||
|  | import com.intellij.psi.PsiFile | ||||||
|  | import com.intellij.psi.util.PsiUtilBase | ||||||
|  |  | ||||||
|  | internal object IntentionsPopup { | ||||||
|  | 	fun show(editor: Editor) { | ||||||
|  | 		if (!tryShow(editor)) { | ||||||
|  | 			HintManager.getInstance().showInformationHint(editor, LangBundle.message("hint.text.no.context.actions.available.at.this.location")) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun tryShow(editor: Editor): Boolean { | ||||||
|  | 		// If the IDE uses the default Show Intentions action and handler, | ||||||
|  | 		// use the handler directly to bypass additional logic from the action. | ||||||
|  | 		val action = ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS) | ||||||
|  | 		if (action.javaClass === ShowIntentionActionsAction::class.java) { | ||||||
|  | 			return tryShowWithDefaultHandler(editor) | ||||||
|  | 		} | ||||||
|  | 		else { | ||||||
|  | 			ActionUtil.invokeAction(action, editor.component, ActionPlaces.EDITOR_INLAY, null, null) | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun tryShowWithDefaultHandler(editor: Editor): Boolean { | ||||||
|  | 		val project = editor.project ?: return false | ||||||
|  | 		val file = PsiUtilBase.getPsiFileInEditor(editor, project) ?: return false | ||||||
|  | 		 | ||||||
|  | 		PsiDocumentManager.getInstance(project).commitAllDocuments() | ||||||
|  | 		IntentionsUI.getInstance(project).hide() | ||||||
|  | 		 | ||||||
|  | 		HANDLER.showIntentionHint(project, editor, file, showFeedbackOnEmptyMenu = true) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private val HANDLER = object : ShowIntentionActionsHandler() { | ||||||
|  | 		public override fun showIntentionHint(project: Project, editor: Editor, file: PsiFile, showFeedbackOnEmptyMenu: Boolean) { | ||||||
|  | 			super.showIntentionHint(project, editor, file, showFeedbackOnEmptyMenu) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,201 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor.lens | ||||||
|  |  | ||||||
|  | import com.chylex.intellij.inspectionlens.settings.LensHoverMode | ||||||
|  | import com.chylex.intellij.inspectionlens.settings.LensSettingsState | ||||||
|  | import com.intellij.codeInsight.daemon.impl.HighlightInfo | ||||||
|  | import com.intellij.codeInsight.daemon.impl.HintRenderer | ||||||
|  | import com.intellij.codeInsight.hints.presentation.InputHandler | ||||||
|  | import com.intellij.openapi.editor.Editor | ||||||
|  | import com.intellij.openapi.editor.Inlay | ||||||
|  | import com.intellij.openapi.editor.ScrollType | ||||||
|  | import com.intellij.openapi.editor.colors.EditorFontType | ||||||
|  | import com.intellij.openapi.editor.ex.EditorEx | ||||||
|  | import com.intellij.openapi.editor.impl.EditorImpl | ||||||
|  | import com.intellij.openapi.editor.markup.TextAttributes | ||||||
|  | import com.intellij.openapi.util.text.StringUtil | ||||||
|  | import com.intellij.ui.paint.EffectPainter | ||||||
|  | import java.awt.Cursor | ||||||
|  | import java.awt.Graphics | ||||||
|  | import java.awt.Graphics2D | ||||||
|  | import java.awt.MouseInfo | ||||||
|  | import java.awt.Point | ||||||
|  | import java.awt.Rectangle | ||||||
|  | import java.awt.event.MouseEvent | ||||||
|  | import java.util.regex.Pattern | ||||||
|  | import javax.swing.SwingUtilities | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Renders the text of an inspection lens. | ||||||
|  |  */ | ||||||
|  | class LensRenderer(private var info: HighlightInfo, private val settings: LensSettingsState) : HintRenderer(null), InputHandler { | ||||||
|  | 	private lateinit var inlay: Inlay<*> | ||||||
|  | 	private lateinit var attributes: LensSeverityTextAttributes | ||||||
|  | 	private var extraRightPadding = 0 | ||||||
|  | 	private var hovered = false | ||||||
|  | 	 | ||||||
|  | 	init { | ||||||
|  | 		setPropertiesFrom(info) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun setInlay(inlay: Inlay<*>) { | ||||||
|  | 		check(!this::inlay.isInitialized) { "Inlay already set" } | ||||||
|  | 		this.inlay = inlay | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun setPropertiesFrom(info: HighlightInfo) { | ||||||
|  | 		this.info = info | ||||||
|  | 		val description = getValidDescriptionText(info.description) | ||||||
|  | 		 | ||||||
|  | 		text = description | ||||||
|  | 		attributes = LensSeverity.from(info.severity).textAttributes | ||||||
|  | 		extraRightPadding = if (description.lastOrNull() == '.') 2 else 0 | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun paint(inlay: Inlay<*>, g: Graphics, r: Rectangle, textAttributes: TextAttributes) { | ||||||
|  | 		fixBaselineForTextRendering(r) | ||||||
|  | 		super.paint(inlay, g, r, textAttributes) | ||||||
|  | 		 | ||||||
|  | 		if (hovered && isHoveringText()) { | ||||||
|  | 			paintHoverEffect(inlay, g, r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun paintHoverEffect(inlay: Inlay<*>, g: Graphics, r: Rectangle) { | ||||||
|  | 		val editor = inlay.editor | ||||||
|  | 		if (editor !is EditorImpl) { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		val font = editor.colorsScheme.getFont(EditorFontType.PLAIN) | ||||||
|  | 		val x = r.x + TEXT_HORIZONTAL_PADDING | ||||||
|  | 		val y = r.y + editor.ascent | ||||||
|  | 		val w = inlay.widthInPixels - UNDERLINE_WIDTH_REDUCTION - extraRightPadding | ||||||
|  | 		val h = editor.descent | ||||||
|  | 		 | ||||||
|  | 		g.color = attributes.foregroundColor | ||||||
|  | 		EffectPainter.LINE_UNDERSCORE.paint(g as Graphics2D, x, y, w, h, font) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun getTextAttributes(editor: Editor): TextAttributes { | ||||||
|  | 		return attributes | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun useEditorFont(): Boolean { | ||||||
|  | 		return settings.useEditorFont | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun mouseMoved(event: MouseEvent, translated: Point) { | ||||||
|  | 		setHovered(isHoveringText(translated)) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun mouseExited() { | ||||||
|  | 		setHovered(false) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun setHovered(hovered: Boolean) { | ||||||
|  | 		if (hovered && settings.lensHoverMode == LensHoverMode.DISABLED) { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		if (this.hovered == hovered) { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		this.hovered = hovered | ||||||
|  | 		 | ||||||
|  | 		val editor = inlay.editor | ||||||
|  | 		if (editor is EditorEx) { | ||||||
|  | 			val cursor = if (hovered) Cursor.getPredefinedCursor(Cursor.HAND_CURSOR) else null | ||||||
|  | 			editor.setCustomCursor(this::class.java, cursor) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		inlay.repaint() | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun mousePressed(event: MouseEvent, translated: Point) { | ||||||
|  | 		val hoverMode = settings.lensHoverMode | ||||||
|  | 		if (hoverMode == LensHoverMode.DISABLED || !isHoveringText(translated)) { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		if (event.button.let { it == MouseEvent.BUTTON1 || it == MouseEvent.BUTTON2 }) { | ||||||
|  | 			event.consume() | ||||||
|  | 			 | ||||||
|  | 			val editor = inlay.editor | ||||||
|  | 			moveToOffset(editor, info.actualStartOffset) | ||||||
|  | 			 | ||||||
|  | 			if ((event.button == MouseEvent.BUTTON1) xor (hoverMode != LensHoverMode.DEFAULT)) { | ||||||
|  | 				IntentionsPopup.show(editor) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun isHoveringText(): Boolean { | ||||||
|  | 		val bounds = inlay.bounds ?: return false | ||||||
|  | 		val translatedPoint = MouseInfo.getPointerInfo().location.apply { | ||||||
|  | 			SwingUtilities.convertPointFromScreen(this, inlay.editor.contentComponent) | ||||||
|  | 			translate(-bounds.x, -bounds.y) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		return isHoveringText(translatedPoint) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun isHoveringText(point: Point): Boolean { | ||||||
|  | 		return point.x >= HOVER_HORIZONTAL_PADDING | ||||||
|  | 			&& point.y >= 4 | ||||||
|  | 			&& point.x < inlay.widthInPixels - HOVER_HORIZONTAL_PADDING - extraRightPadding | ||||||
|  | 			&& point.y < inlay.heightInPixels - 1 | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private companion object { | ||||||
|  | 		/** | ||||||
|  | 		 * [HintRenderer.paintHint] renders padding around text, but not around effects. | ||||||
|  | 		 */ | ||||||
|  | 		private const val TEXT_HORIZONTAL_PADDING = 7 | ||||||
|  | 		private const val HOVER_HORIZONTAL_PADDING = TEXT_HORIZONTAL_PADDING - 2 | ||||||
|  | 		private const val UNDERLINE_WIDTH_REDUCTION = (TEXT_HORIZONTAL_PADDING * 2) - 1 | ||||||
|  | 		 | ||||||
|  | 		private const val MAX_DESCRIPTION_LENGTH = 120 | ||||||
|  | 		 | ||||||
|  | 		/** | ||||||
|  | 		 * Kotlin compiler inspections have an `[UPPERCASE_TAG]` at the beginning. | ||||||
|  | 		 */ | ||||||
|  | 		private val UPPERCASE_TAG_REGEX = Pattern.compile("^\\[[A-Z_]+] ") | ||||||
|  | 		 | ||||||
|  | 		private fun getValidDescriptionText(text: String?): String { | ||||||
|  | 			return if (text.isNullOrBlank()) " " else addEllipsisOrMissingPeriod(unescapeHtmlEntities(stripUppercaseTag(text))) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		private fun stripUppercaseTag(text: String): String { | ||||||
|  | 			if (text.startsWith('[')) { | ||||||
|  | 				val matcher = UPPERCASE_TAG_REGEX.matcher(text) | ||||||
|  | 				if (matcher.find()) { | ||||||
|  | 					return text.substring(matcher.end()) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			return text | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		private fun unescapeHtmlEntities(text: String): String { | ||||||
|  | 			return if (text.contains('&')) StringUtil.unescapeXmlEntities(text) else text | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		private fun addEllipsisOrMissingPeriod(text: String): String { | ||||||
|  | 			return when { | ||||||
|  | 				text.length > MAX_DESCRIPTION_LENGTH -> text.take(MAX_DESCRIPTION_LENGTH).trimEnd { it.isWhitespace() || it == '.' } + "…" | ||||||
|  | 				!text.endsWith('.')                  -> "$text." | ||||||
|  | 				else                                 -> text | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		private fun fixBaselineForTextRendering(rect: Rectangle) { | ||||||
|  | 			rect.y += 1 | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		private fun moveToOffset(editor: Editor, offset: Int) { | ||||||
|  | 			editor.caretModel.moveToOffset(offset) | ||||||
|  | 			editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,68 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor.lens | ||||||
|  |  | ||||||
|  | import com.chylex.intellij.inspectionlens.InspectionLens | ||||||
|  | import com.chylex.intellij.inspectionlens.compatibility.SpellCheckerSupport | ||||||
|  | import com.intellij.lang.annotation.HighlightSeverity | ||||||
|  | import com.intellij.ui.ColorUtil | ||||||
|  | import com.intellij.ui.ColorUtil.toAlpha | ||||||
|  | import com.intellij.ui.JBColor | ||||||
|  | import java.awt.Color | ||||||
|  | import java.awt.Font | ||||||
|  | import java.util.Collections | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Determines properties of inspection lenses based on severity. | ||||||
|  |  */ | ||||||
|  | @Suppress("UseJBColor", "InspectionUsingGrayColors") | ||||||
|  | enum class LensSeverity(baseColor: Color, lightThemeDarkening: Int, darkThemeBrightening: Int) { | ||||||
|  | 	ERROR          (Color(158,  41,  39), lightThemeDarkening = 2, darkThemeBrightening = 4), | ||||||
|  | 	WARNING        (Color(190, 145,  23), lightThemeDarkening = 5, darkThemeBrightening = 1), | ||||||
|  | 	WEAK_WARNING   (Color(117, 109,  86), lightThemeDarkening = 4, darkThemeBrightening = 4), | ||||||
|  | 	SERVER_PROBLEM (Color(176,  97,   0), lightThemeDarkening = 5, darkThemeBrightening = 2), | ||||||
|  | 	GRAZIE         (Color( 53, 146, 196), lightThemeDarkening = 3, darkThemeBrightening = 1), | ||||||
|  | 	TYPO           (Color( 73, 156,  84), lightThemeDarkening = 4, darkThemeBrightening = 1), | ||||||
|  | 	OTHER          (Color(128, 128, 128), lightThemeDarkening = 2, darkThemeBrightening = 2); | ||||||
|  | 	 | ||||||
|  | 	val textAttributes: LensSeverityTextAttributes | ||||||
|  | 	val lineAttributes: LensSeverityTextAttributes | ||||||
|  | 	 | ||||||
|  | 	init { | ||||||
|  | 		val lightThemeColor = ColorUtil.saturate(ColorUtil.darker(baseColor, lightThemeDarkening), 1) | ||||||
|  | 		val darkThemeColor = ColorUtil.desaturate(ColorUtil.brighter(baseColor, darkThemeBrightening), 2) | ||||||
|  | 		 | ||||||
|  | 		val textColor = JBColor(lightThemeColor, darkThemeColor) | ||||||
|  | 		val lineColor = JBColor(toAlpha(lightThemeColor, 10), toAlpha(darkThemeColor, 13)) | ||||||
|  | 		 | ||||||
|  | 		textAttributes = LensSeverityTextAttributes(foregroundColor = textColor, fontStyle = Font.ITALIC) | ||||||
|  | 		lineAttributes = LensSeverityTextAttributes(backgroundColor = lineColor) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	companion object { | ||||||
|  | 		private val mapping = Collections.synchronizedMap(mapOf( | ||||||
|  | 			HighlightSeverity.ERROR                           to ERROR, | ||||||
|  | 			HighlightSeverity.WARNING                         to WARNING, | ||||||
|  | 			HighlightSeverity.WEAK_WARNING                    to WEAK_WARNING, | ||||||
|  | 			HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING to SERVER_PROBLEM, | ||||||
|  | 		)) | ||||||
|  | 		 | ||||||
|  | 		init { | ||||||
|  | 			SpellCheckerSupport.load() | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		/** | ||||||
|  | 		 * Registers a mapping from a [HighlightSeverity] to a [LensSeverity], and refreshes all open editors. | ||||||
|  | 		 */ | ||||||
|  | 		internal fun registerMapping(severity: HighlightSeverity, lensSeverity: LensSeverity) { | ||||||
|  | 			if (mapping.put(severity, lensSeverity) != lensSeverity) { | ||||||
|  | 				InspectionLens.scheduleRefresh() | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		/** | ||||||
|  | 		 * Returns the [LensSeverity] associated with the [HighlightSeverity], or [OTHER] if no explicit mapping is found. | ||||||
|  | 		 */ | ||||||
|  | 		fun from(severity: HighlightSeverity): LensSeverity { | ||||||
|  | 			return mapping.getOrDefault(severity, OTHER) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,31 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor.lens | ||||||
|  |  | ||||||
|  | import com.intellij.codeInsight.daemon.impl.SeverityRegistrar | ||||||
|  | import com.intellij.lang.annotation.HighlightSeverity | ||||||
|  | import java.util.function.Predicate | ||||||
|  |  | ||||||
|  | class LensSeverityFilter(private val hiddenSeverityIds: Set<String>, private val showUnknownSeverities: Boolean) : Predicate<HighlightSeverity> { | ||||||
|  | 	private val knownSeverityIds = getSupportedSeverities().mapTo(HashSet(), HighlightSeverity::getName) | ||||||
|  | 	 | ||||||
|  | 	override fun test(severity: HighlightSeverity): Boolean { | ||||||
|  | 		if (!isSupported(severity)) { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		return if (severity.name in knownSeverityIds) | ||||||
|  | 			severity.name !in hiddenSeverityIds | ||||||
|  | 		else | ||||||
|  | 			showUnknownSeverities | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	companion object { | ||||||
|  | 		@Suppress("DEPRECATION") | ||||||
|  | 		private fun isSupported(severity: HighlightSeverity): Boolean { | ||||||
|  | 			return severity > HighlightSeverity.TEXT_ATTRIBUTES && severity !== HighlightSeverity.INFO | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		fun getSupportedSeverities(registrar: SeverityRegistrar = SeverityRegistrar.getSeverityRegistrar(null)): List<HighlightSeverity> { | ||||||
|  | 			return registrar.allSeverities.filter(::isSupported) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor.lens | ||||||
|  |  | ||||||
|  | import com.intellij.openapi.editor.markup.UnmodifiableTextAttributes | ||||||
|  | import com.intellij.ui.JBColor | ||||||
|  | import java.awt.Font | ||||||
|  |  | ||||||
|  | class LensSeverityTextAttributes( | ||||||
|  | 	private val foregroundColor: JBColor? = null, | ||||||
|  | 	private val backgroundColor: JBColor? = null, | ||||||
|  | 	private val fontStyle: Int = Font.PLAIN, | ||||||
|  | ) : UnmodifiableTextAttributes() { | ||||||
|  | 	override fun getForegroundColor(): JBColor? { | ||||||
|  | 		return foregroundColor | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun getBackgroundColor(): JBColor? { | ||||||
|  | 		return backgroundColor | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun getFontType(): Int { | ||||||
|  | 		return fontStyle | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,139 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.settings | ||||||
|  |  | ||||||
|  | import com.chylex.intellij.inspectionlens.editor.lens.LensSeverityFilter | ||||||
|  | import com.intellij.codeInsight.daemon.impl.SeverityRegistrar | ||||||
|  | import com.intellij.lang.annotation.HighlightSeverity | ||||||
|  | import com.intellij.openapi.components.service | ||||||
|  | import com.intellij.openapi.editor.event.SelectionEvent | ||||||
|  | import com.intellij.openapi.editor.event.SelectionListener | ||||||
|  | import com.intellij.openapi.editor.markup.TextAttributes | ||||||
|  | import com.intellij.openapi.options.BoundConfigurable | ||||||
|  | import com.intellij.openapi.options.ConfigurableWithId | ||||||
|  | import com.intellij.openapi.ui.DialogPanel | ||||||
|  | import com.intellij.openapi.util.Disposer | ||||||
|  | import com.intellij.ui.DisabledTraversalPolicy | ||||||
|  | import com.intellij.ui.EditorTextFieldCellRenderer.SimpleRendererComponent | ||||||
|  | import com.intellij.ui.SimpleListCellRenderer | ||||||
|  | import com.intellij.ui.components.JBCheckBox | ||||||
|  | import com.intellij.ui.dsl.builder.Cell | ||||||
|  | import com.intellij.ui.dsl.builder.RightGap | ||||||
|  | import com.intellij.ui.dsl.builder.Row | ||||||
|  | import com.intellij.ui.dsl.builder.RowLayout | ||||||
|  | import com.intellij.ui.dsl.builder.bindItem | ||||||
|  | import com.intellij.ui.dsl.builder.bindSelected | ||||||
|  | import com.intellij.ui.dsl.builder.panel | ||||||
|  | import java.awt.Cursor | ||||||
|  |  | ||||||
|  | class LensApplicationConfigurable : BoundConfigurable("Inspection Lens"), ConfigurableWithId { | ||||||
|  | 	companion object { | ||||||
|  | 		const val ID = "InspectionLens" | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private data class DisplayedSeverity( | ||||||
|  | 		val id: String, | ||||||
|  | 		val severity: StoredSeverity, | ||||||
|  | 		val textAttributes: TextAttributes? = null, | ||||||
|  | 	) { | ||||||
|  | 		constructor( | ||||||
|  | 			severity: HighlightSeverity, | ||||||
|  | 			registrar: SeverityRegistrar, | ||||||
|  | 		) : this( | ||||||
|  | 			id = severity.name, | ||||||
|  | 			severity = StoredSeverity(severity), | ||||||
|  | 			textAttributes = registrar.getHighlightInfoTypeBySeverity(severity).attributesKey.defaultAttributes | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private val settingsService = service<LensSettingsState>() | ||||||
|  | 	 | ||||||
|  | 	private val allSeverities by lazy(LazyThreadSafetyMode.NONE) { | ||||||
|  | 		val settings = settingsService.state | ||||||
|  | 		val registrar = SeverityRegistrar.getSeverityRegistrar(null) | ||||||
|  | 		 | ||||||
|  | 		val knownSeverities = LensSeverityFilter.getSupportedSeverities(registrar).map { DisplayedSeverity(it, registrar) } | ||||||
|  | 		val knownSeverityIds = knownSeverities.mapTo(HashSet(), DisplayedSeverity::id) | ||||||
|  | 		 | ||||||
|  | 		// Update names and priorities of stored severities. | ||||||
|  | 		for ((id, knownSeverity, _) in knownSeverities) { | ||||||
|  | 			val storedSeverity = settings.hiddenSeverities[id] | ||||||
|  | 			if (storedSeverity != null && storedSeverity != knownSeverity) { | ||||||
|  | 				settings.hiddenSeverities[id] = knownSeverity | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		val unknownSeverities = settings.hiddenSeverities.entries | ||||||
|  | 			.filterNot { it.key in knownSeverityIds } | ||||||
|  | 			.map { DisplayedSeverity(it.key, it.value) } | ||||||
|  | 		 | ||||||
|  | 		(knownSeverities + unknownSeverities).sortedByDescending { it.severity.priority } | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun getId(): String { | ||||||
|  | 		return ID | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun createPanel(): DialogPanel { | ||||||
|  | 		val settings = settingsService.state | ||||||
|  | 		 | ||||||
|  | 		return panel { | ||||||
|  | 			group("Appearance") { | ||||||
|  | 				row { | ||||||
|  | 					checkBox("Use editor font").bindSelected(settings::useEditorFont) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			group("Behavior") { | ||||||
|  | 				row("Hover mode:") { | ||||||
|  | 					val items = LensHoverMode.values().toList() | ||||||
|  | 					val renderer = SimpleListCellRenderer.create("", LensHoverMode::description) | ||||||
|  | 					comboBox(items, renderer).bindItem(settings::lensHoverMode) { settings.lensHoverMode = it ?: LensHoverMode.DEFAULT } | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			group("Shown Severities") { | ||||||
|  | 				for ((id, severity, textAttributes) in allSeverities) { | ||||||
|  | 					row { | ||||||
|  | 						checkBox(severity.name) | ||||||
|  | 							.bindSelectedToNotIn(settings.hiddenSeverities, id, severity) | ||||||
|  | 							.gap(RightGap.COLUMNS) | ||||||
|  | 						 | ||||||
|  | 						labelWithAttributes("Example", textAttributes) | ||||||
|  | 					}.layout(RowLayout.PARENT_GRID) | ||||||
|  | 				} | ||||||
|  | 				 | ||||||
|  | 				row { | ||||||
|  | 					checkBox("Other").bindSelected(settings::showUnknownSeverities) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun <K, V> Cell<JBCheckBox>.bindSelectedToNotIn(collection: MutableMap<K, V>, key: K, value: V): Cell<JBCheckBox> { | ||||||
|  | 		return bindSelected({ key !in collection }, { if (it) collection.remove(key) else collection[key] = value }) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun Row.labelWithAttributes(text: String, textAttributes: TextAttributes?): Cell<SimpleRendererComponent> { | ||||||
|  | 		val label = SimpleRendererComponent(null, null, true) | ||||||
|  | 		label.setText(text, textAttributes, false) | ||||||
|  | 		label.focusTraversalPolicy = DisabledTraversalPolicy() | ||||||
|  | 		 | ||||||
|  | 		val editor = label.editor | ||||||
|  | 		editor.setCustomCursor(this, Cursor.getDefaultCursor()) | ||||||
|  | 		editor.contentComponent.setOpaque(false) | ||||||
|  | 		editor.selectionModel.addSelectionListener(object : SelectionListener { | ||||||
|  | 			override fun selectionChanged(e: SelectionEvent) { | ||||||
|  | 				if (!e.newRange.isEmpty) { | ||||||
|  | 					editor.selectionModel.removeSelection(true) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 		 | ||||||
|  | 		Disposer.register(disposable!!, label) | ||||||
|  | 		return cell(label) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	override fun apply() { | ||||||
|  | 		super.apply() | ||||||
|  | 		settingsService.update() | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.settings | ||||||
|  |  | ||||||
|  | enum class LensHoverMode(val description: String) { | ||||||
|  | 	DISABLED("Disabled"), | ||||||
|  | 	DEFAULT("Left click shows intentions, middle click jumps to highlight"), | ||||||
|  | 	SWAPPED("Left click jumps to highlight, middle click shows intentions") | ||||||
|  | } | ||||||
| @@ -0,0 +1,52 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.settings | ||||||
|  |  | ||||||
|  | import com.chylex.intellij.inspectionlens.InspectionLens | ||||||
|  | import com.chylex.intellij.inspectionlens.editor.lens.LensSeverityFilter | ||||||
|  | import com.intellij.openapi.components.BaseState | ||||||
|  | import com.intellij.openapi.components.SettingsCategory | ||||||
|  | import com.intellij.openapi.components.SimplePersistentStateComponent | ||||||
|  | import com.intellij.openapi.components.State | ||||||
|  | import com.intellij.openapi.components.Storage | ||||||
|  | import com.intellij.util.xmlb.annotations.XMap | ||||||
|  |  | ||||||
|  | @State( | ||||||
|  | 	name = LensApplicationConfigurable.ID, | ||||||
|  | 	storages = [ Storage("chylex.inspectionLens.xml") ], | ||||||
|  | 	category = SettingsCategory.UI | ||||||
|  | ) | ||||||
|  | class LensSettingsState : SimplePersistentStateComponent<LensSettingsState.State>(State()) { | ||||||
|  | 	class State : BaseState() { | ||||||
|  | 		@get:XMap | ||||||
|  | 		val hiddenSeverities by map<String, StoredSeverity>() | ||||||
|  | 		 | ||||||
|  | 		var showUnknownSeverities by property(true) | ||||||
|  | 		var useEditorFont by property(true) | ||||||
|  | 		var lensHoverMode by enum(LensHoverMode.DEFAULT) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@get:Synchronized | ||||||
|  | 	@set:Synchronized | ||||||
|  | 	var severityFilter = createSeverityFilter() | ||||||
|  | 		private set | ||||||
|  | 	 | ||||||
|  | 	val useEditorFont | ||||||
|  | 		get() = state.useEditorFont | ||||||
|  | 	 | ||||||
|  | 	val lensHoverMode | ||||||
|  | 		get() = state.lensHoverMode | ||||||
|  | 	 | ||||||
|  | 	override fun loadState(state: State) { | ||||||
|  | 		super.loadState(state) | ||||||
|  | 		update() | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	fun update() { | ||||||
|  | 		severityFilter = createSeverityFilter() | ||||||
|  | 		InspectionLens.scheduleRefresh() | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	private fun createSeverityFilter(): LensSeverityFilter { | ||||||
|  | 		val state = state | ||||||
|  | 		return LensSeverityFilter(state.hiddenSeverities.keys, state.showUnknownSeverities) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.settings | ||||||
|  |  | ||||||
|  | import com.intellij.lang.annotation.HighlightSeverity | ||||||
|  | import com.intellij.util.xmlb.annotations.Tag | ||||||
|  |  | ||||||
|  | @Tag("severity") | ||||||
|  | data class StoredSeverity(var name: String = "", var priority: Int = 0) { | ||||||
|  | 	constructor(severity: HighlightSeverity) : this(severity.displayCapitalizedName, severity.myVal) | ||||||
|  | } | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| package com.chylex.intellij.inspectionlens.util |  | ||||||
|  |  | ||||||
| import com.intellij.openapi.Disposable |  | ||||||
| import com.intellij.openapi.util.Disposer |  | ||||||
| import java.lang.ref.WeakReference |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * A [Disposable] that can have multiple parents, and will be disposed when any parent is disposed. |  | ||||||
|  * A [WeakReference] and a lambda will remain in memory for every parent that is not disposed. |  | ||||||
|  */ |  | ||||||
| class MultiParentDisposable { |  | ||||||
| 	val self = Disposer.newDisposable() |  | ||||||
| 	 |  | ||||||
| 	fun registerWithParent(parent: Disposable) { |  | ||||||
| 		val weakSelfReference = WeakReference(self) |  | ||||||
| 		Disposer.register(parent) { weakSelfReference.get()?.let(Disposer::dispose) } |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | <idea-plugin> | ||||||
|  |   <extensions defaultExtensionNs="com.intellij"> | ||||||
|  |     <postStartupActivity implementation="com.chylex.intellij.inspectionlens.compatibility.GrazieSupport" /> | ||||||
|  |   </extensions> | ||||||
|  | </idea-plugin> | ||||||
| @@ -4,19 +4,76 @@ | |||||||
|   <vendor url="https://chylex.com">chylex</vendor> |   <vendor url="https://chylex.com">chylex</vendor> | ||||||
|    |    | ||||||
|   <description><![CDATA[ |   <description><![CDATA[ | ||||||
|     Shows errors, warnings, and other inspection highlights inline. |     Displays errors, warnings, and other inspections inline. Highlights the background of lines with inspections. Supports light and dark themes out of the box. | ||||||
|     <br><br> |     <br><br> | ||||||
|     Simply install the plugin and inspection descriptions will appear on the right side of the lines. |     By default, the plugin shows <b>Errors</b>, <b>Warnings</b>, <b>Weak Warnings</b>, <b>Server Problems</b>, <b>Grammar Errors</b>, <b>Typos</b>, and other inspections with a high enough severity level. Left-click an inspection to show quick fixes. Middle-click an inspection to navigate to the relevant code in the editor. | ||||||
|     Shown inspection severities are <b>Errors</b>, <b>Warnings</b>, <b>Weak Warnings</b>, <b>Server Problems</b>, <b>Typos</b>, and other inspections from plugins or future IntelliJ versions that have a high enough severity level. |  | ||||||
|     Each severity has a different color, with support for both light and dark themes. |  | ||||||
|     <br><br> |     <br><br> | ||||||
|     The plugin is not customizable outside of the ability to disable/enable the plugin without restarting the IDE. |     Configure appearance, behavior of clicking on inspections, and visible severities in <b>Settings | Tools | Inspection Lens</b>. | ||||||
|     If the defaults don't work for you, I would recommend either trying the <a href="https://plugins.jetbrains.com/plugin/17302-inlineerror">Inline Error</a> plugin which can be customized, or proposing your change in the <a href="https://github.com/chylex/IntelliJ-Inspection-Lens/issues">issue tracker</a>. |  | ||||||
|     <br><br> |     <br><br> | ||||||
|     Inspired by <a href="https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens">Error Lens</a> for VS Code, and <a href="https://plugins.jetbrains.com/plugin/17302-inlineerror">Inline Error</a> for IntelliJ Platform. |     Inspired by <a href="https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens">Error Lens</a> for VS Code, and <a href="https://plugins.jetbrains.com/plugin/17302-inlineerror">Inline Error</a> for IntelliJ Platform. | ||||||
|   ]]></description> |   ]]></description> | ||||||
|    |    | ||||||
|   <change-notes><![CDATA[ |   <change-notes><![CDATA[ | ||||||
|  |     <b>Version 1.5.1</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Added option to change the behavior of clicking on inspections.</li> | ||||||
|  |       <li>Fixed broken quick fixes in Rider and CLion Nova.</li> | ||||||
|  |       <li>Fixed hover underline not rendering correctly with some combinations of high DPI and line height settings.</li> | ||||||
|  |     </ul> | ||||||
|  |     <b>Version 1.5</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Added possibility to left-click an inspection to show quick fixes.</li> | ||||||
|  |       <li>Added possibility to middle-click an inspection to navigate to relevant code in the editor.</li> | ||||||
|  |       <li>Added option to use UI font instead of editor font.</li> | ||||||
|  |       <li>Long inspection descriptions are now truncated to 120 characters.</li> | ||||||
|  |       <li>Improved descriptions of Kotlin compiler inspections.</li> | ||||||
|  |       <li>Fixed visual artifacts in Rendered Doc comments.</li> | ||||||
|  |     </ul> | ||||||
|  |     <b>Version 1.4.1</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Fixed warnings in usage of IntelliJ SDK.</li> | ||||||
|  |     </ul> | ||||||
|  |     <b>Version 1.4</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Added configuration of visible severities to <b>Settings | Tools | Inspection Lens</b>.</li> | ||||||
|  |     </ul> | ||||||
|  |     <b>Version 1.3.3</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Partially reverted fix for inspections that include HTML in their description due to breaking inspections with angled brackets.</li> | ||||||
|  |       <li>Fixed plugin not working when installed on JetBrains Gateway Client.</li> | ||||||
|  |     </ul> | ||||||
|  |     <b>Version 1.3.2</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Fixed inspections randomly not disappearing.</li> | ||||||
|  |     </ul> | ||||||
|  |     <b>Version 1.3.1</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Updated minimum version to IntelliJ 2023.3 due to breaking API changes.</li> | ||||||
|  |     </ul> | ||||||
|  |     <b>Version 1.3.0</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Added background colors to lines containing inspections. (<a href="https://github.com/chylex/IntelliJ-Inspection-Lens/pull/15">PR #15</a> by <a href="https://github.com/synopss">synopss</a>)</li> | ||||||
|  |     </ul> | ||||||
|  |     <b>Version 1.2.0</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Support for IntelliJ 2023.2 EAP.</li> | ||||||
|  |       <li>Added distinct colors for typos and Grazie inspections.</li> | ||||||
|  |     </ul> | ||||||
|  |     <b>Version 1.1.2</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Added plugin icon.</li> | ||||||
|  |       <li>Updated minimum version to IntelliJ 2023.1 due to deprecated APIs.</li> | ||||||
|  |     </ul> | ||||||
|  |     <b>Version 1.1.1</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Multiple inspections at the same place in the document are now ordered by severity.</li> | ||||||
|  |       <li>Improved performance of processing inspections which do not contain HTML.</li> | ||||||
|  |     </ul> | ||||||
|  |     <b>Version 1.1.0</b> | ||||||
|  |     <ul> | ||||||
|  |       <li>Fixed showing inspections that include HTML in their description. (<a href="https://github.com/chylex/IntelliJ-Inspection-Lens/pull/3">PR #3</a> by <a href="https://github.com/KostkaBrukowa">KostkaBrukowa</a>)</li> | ||||||
|  |       <li>Fixed exception when asynchronous inspections run on a non-EDT thread.</li> | ||||||
|  |     </ul> | ||||||
|     <b>Version 1.0.0</b> |     <b>Version 1.0.0</b> | ||||||
|     <ul> |     <ul> | ||||||
|       <li>Initial version with support for IntelliJ 2022.2 and newer.</li> |       <li>Initial version with support for IntelliJ 2022.2 and newer.</li> | ||||||
| @@ -24,9 +81,14 @@ | |||||||
|   ]]></change-notes> |   ]]></change-notes> | ||||||
|    |    | ||||||
|   <depends>com.intellij.modules.platform</depends> |   <depends>com.intellij.modules.platform</depends> | ||||||
|  |   <depends optional="true" config-file="compatibility/InspectionLens-Grazie.xml">tanvd.grazi</depends> | ||||||
|    |    | ||||||
|   <extensions defaultExtensionNs="com.intellij"> |   <extensions defaultExtensionNs="com.intellij"> | ||||||
|     <applicationService serviceImplementation="com.chylex.intellij.inspectionlens.InspectionLensPluginDisposableService" /> |     <applicationService serviceImplementation="com.chylex.intellij.inspectionlens.settings.LensSettingsState" /> | ||||||
|  |     <applicationConfigurable id="com.chylex.intellij.inspectionlens.settings.LensApplicationConfigurable" | ||||||
|  |                              instance="com.chylex.intellij.inspectionlens.settings.LensApplicationConfigurable" | ||||||
|  |                              displayName="Inspection Lens" | ||||||
|  |                              parentId="tools" /> | ||||||
|   </extensions> |   </extensions> | ||||||
|    |    | ||||||
|   <applicationListeners> |   <applicationListeners> | ||||||
| @@ -34,6 +96,6 @@ | |||||||
|   </applicationListeners> |   </applicationListeners> | ||||||
|    |    | ||||||
|   <projectListeners> |   <projectListeners> | ||||||
|     <listener class="com.chylex.intellij.inspectionlens.LensFileEditorListener" topic="com.intellij.openapi.fileEditor.FileEditorManagerListener" /> |     <listener class="com.chylex.intellij.inspectionlens.InspectionLensFileOpenedListener" topic="com.intellij.openapi.fileEditor.FileOpenedSyncListener" /> | ||||||
|   </projectListeners> |   </projectListeners> | ||||||
| </idea-plugin> | </idea-plugin> | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								src/main/resources/META-INF/pluginIcon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/main/resources/META-INF/pluginIcon.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||||||
|  | <svg width="100%" height="100%" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:square;stroke-miterlimit:1.5;"> | ||||||
|  |     <g> | ||||||
|  |         <path d="M13.275,24.427c0.685,0.842 1.456,1.613 2.298,2.298l-11.01,11.01l-2.298,-2.298l11.01,-11.01Z" style="fill:#767676;"/> | ||||||
|  |         <path d="M25.5,2c6.899,0 12.5,5.601 12.5,12.5c-0,6.899 -5.601,12.5 -12.5,12.5c-6.899,-0 -12.5,-5.601 -12.5,-12.5c-0,-6.899 5.601,-12.5 12.5,-12.5Zm-0,3c-5.243,0 -9.5,4.257 -9.5,9.5c-0,5.243 4.257,9.5 9.5,9.5c5.243,-0 9.5,-4.257 9.5,-9.5c-0,-5.243 -4.257,-9.5 -9.5,-9.5Z" style="fill:#8e8e8e;"/> | ||||||
|  |     </g> | ||||||
|  |     <g> | ||||||
|  |         <path d="M19.5,18.5l2,-2l2,2l2,-2l2,2l2,-2l2,2" style="fill:none;stroke:#d2524f;stroke-width:1.15px;"/> | ||||||
|  |         <path d="M19.5,12.5l2,-2l2,2l2,-2l2,2l2,-2l2,2" style="fill:none;stroke:#be9139;stroke-width:1.15px;"/> | ||||||
|  |     </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.0 KiB | 
| @@ -0,0 +1,65 @@ | |||||||
|  | package com.chylex.intellij.inspectionlens.editor.lens | ||||||
|  |  | ||||||
|  | import com.intellij.lang.annotation.HighlightSeverity | ||||||
|  | import org.junit.jupiter.api.Assertions.assertEquals | ||||||
|  | import org.junit.jupiter.api.Nested | ||||||
|  | import org.junit.jupiter.api.Test | ||||||
|  | import org.junit.jupiter.params.ParameterizedTest | ||||||
|  | import org.junit.jupiter.params.provider.ValueSource | ||||||
|  |  | ||||||
|  | class EditorLensTest { | ||||||
|  | 	@Nested | ||||||
|  | 	inner class Priority { | ||||||
|  | 		@ParameterizedTest(name = "positionAndSeverity = {0}") | ||||||
|  | 		@ValueSource(ints = [0, -1, Int.MIN_VALUE]) | ||||||
|  | 		fun minimumOffset(positionAndSeverity: Int) { | ||||||
|  | 			assertEquals(Int.MAX_VALUE, EditorLensInlay.getInlayHintPriority(positionAndSeverity, Int.MAX_VALUE)) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		@ParameterizedTest(name = "position = {0}") | ||||||
|  | 		@ValueSource(ints = [8_589_933, Int.MAX_VALUE]) | ||||||
|  | 		fun maximumOffset(position: Int) { | ||||||
|  | 			assertEquals(Int.MIN_VALUE + 295, EditorLensInlay.getInlayHintPriority(position, Int.MIN_VALUE)) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		@ParameterizedTest(name = "severity = {0}") | ||||||
|  | 		@ValueSource(ints = [0, 1, 250, 499, 500]) | ||||||
|  | 		fun firstPriorityBucket(severity: Int) { | ||||||
|  | 			assertEquals(Int.MAX_VALUE - 500 + severity, EditorLensInlay.getInlayHintPriority(0, severity)) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		@ParameterizedTest(name = "severity = {0}") | ||||||
|  | 		@ValueSource(ints = [0, 1, 250, 499, 500]) | ||||||
|  | 		fun secondPriorityBucket(severity: Int) { | ||||||
|  | 			assertEquals(Int.MAX_VALUE - 1000 + severity, EditorLensInlay.getInlayHintPriority(1, severity)) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		@ParameterizedTest(name = "severity = {0}") | ||||||
|  | 		@ValueSource(ints = [0, 1, 250, 499, 500]) | ||||||
|  | 		fun middlePriorityBucket(severity: Int) { | ||||||
|  | 			assertEquals(-353 + severity, EditorLensInlay.getInlayHintPriority(4294967, severity)) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		@ParameterizedTest(name = "severity = {0}") | ||||||
|  | 		@ValueSource(ints = [0, 1, 250, 499, 500]) | ||||||
|  | 		fun penultimatePriorityBucket(severity: Int) { | ||||||
|  | 			assertEquals(Int.MIN_VALUE + 295 + 500 + severity, EditorLensInlay.getInlayHintPriority(8_589_932, severity)) | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		/** | ||||||
|  | 		 * If any of these changes, re-evaluate [EditorLensInlay.MAXIMUM_SEVERITY] and the priority calculations. | ||||||
|  | 		 */ | ||||||
|  | 		@Nested | ||||||
|  | 		inner class IdeaHighlightSeverityAssumptions { | ||||||
|  | 			@Test | ||||||
|  | 			fun smallestSeverityHasNotChanged() { | ||||||
|  | 				assertEquals(10, HighlightSeverity.DEFAULT_SEVERITIES.minOf(HighlightSeverity::myVal)) | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			@Test | ||||||
|  | 			fun highestSeverityHasNotChanged() { | ||||||
|  | 				assertEquals(400, HighlightSeverity.DEFAULT_SEVERITIES.maxOf(HighlightSeverity::myVal)) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user