1
0
mirror of https://github.com/chylex/IntelliJ-Inspection-Lens.git synced 2024-11-25 16:42:54 +01:00

Compare commits

...

2 Commits

4 changed files with 101 additions and 2 deletions

View File

@ -14,6 +14,10 @@ repositories {
mavenCentral() mavenCentral()
} }
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.9.0")
}
intellij { intellij {
version.set("2022.2") version.set("2022.2")
updateSinceUntilBuild.set(false) updateSinceUntilBuild.set(false)
@ -27,6 +31,10 @@ tasks.buildSearchableOptions {
enabled = false enabled = false
} }
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> { tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "11" kotlinOptions.jvmTarget = "11"
kotlinOptions.freeCompilerArgs = listOf( kotlinOptions.freeCompilerArgs = listOf(

View File

@ -1,5 +1,6 @@
package com.chylex.intellij.inspectionlens package com.chylex.intellij.inspectionlens
import com.chylex.intellij.inspectionlens.EditorInlayLensManager.Companion.MAXIMUM_SEVERITY
import com.intellij.codeInsight.daemon.impl.HighlightInfo import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.Inlay import com.intellij.openapi.editor.Inlay
@ -14,6 +15,13 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
companion object { companion object {
private val KEY = Key<EditorInlayLensManager>(EditorInlayLensManager::class.java.name) private val KEY = Key<EditorInlayLensManager>(EditorInlayLensManager::class.java.name)
/**
* Highest allowed severity for the purposes of sorting multiple highlights at the same offset.
* A [MAXIMUM_SEVERITY] of 500 allows for 8 589 933 positions in the document before sorting breaks down.
* 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
fun getOrCreate(editor: Editor): EditorInlayLensManager { fun getOrCreate(editor: Editor): EditorInlayLensManager {
return editor.getUserData(KEY) ?: EditorInlayLensManager(editor).also { editor.putUserData(KEY, it) } return editor.getUserData(KEY) ?: EditorInlayLensManager(editor).also { editor.putUserData(KEY, it) }
} }
@ -30,6 +38,19 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
// Ensures a highlight at the end of a line does not overflow to the next line. // Ensures a highlight at the end of a line does not overflow to the next line.
return info.actualEndOffset - 1 return info.actualEndOffset - 1
} }
internal fun getInlayHintPriority(offset: Int, severity: Int): Int {
// Sorts highlights first by offset in the document, then by severity.
val positionBucket = offset.coerceAtLeast(0) * MAXIMUM_SEVERITY.toLong()
val positionFactor = (Int.MAX_VALUE - MAXIMUM_SEVERITY - positionBucket).coerceAtLeast(Int.MIN_VALUE + 1L).toInt()
val severityFactor = severity.coerceIn(0, MAXIMUM_SEVERITY)
// The result is between (Int.MIN_VALUE + 1)..Int.MAX_VALUE, allowing for negation without overflow.
return positionFactor + severityFactor
}
private fun getInlayHintPriority(info: HighlightInfo): Int {
return getInlayHintPriority(info.actualEndOffset, info.severity.myVal)
}
} }
private val inlays = mutableMapOf<RangeHighlighter, Inlay<LensRenderer>>() private val inlays = mutableMapOf<RangeHighlighter, Inlay<LensRenderer>>()
@ -43,8 +64,9 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
} }
else { else {
val offset = getInlayHintOffset(info) val offset = getInlayHintOffset(info)
val priority = getInlayHintPriority(info)
val renderer = LensRenderer(info) val renderer = LensRenderer(info)
val properties = InlayProperties().relatesToPrecedingText(true).disableSoftWrapping(true).priority(-offset) val properties = InlayProperties().relatesToPrecedingText(true).disableSoftWrapping(true).priority(priority)
editor.inlayModel.addAfterLineEndElement(offset, properties, renderer)?.let { editor.inlayModel.addAfterLineEndElement(offset, properties, renderer)?.let {
inlays[highlighter] = it inlays[highlighter] = it

View File

@ -42,13 +42,23 @@ class LensRenderer(info: HighlightInfo) : HintRenderer(null) {
private val ATTRIBUTES_SINGLETON = TextAttributes(null, null, null, null, Font.ITALIC) private val ATTRIBUTES_SINGLETON = TextAttributes(null, null, null, null, Font.ITALIC)
private fun getValidDescriptionText(text: String?): String { private fun getValidDescriptionText(text: String?): String {
return if (text.isNullOrBlank()) " " else addMissingPeriod(StringUtil.unescapeXmlEntities(StringUtil.stripHtml(text, " "))) return if (text.isNullOrBlank()) " " else addMissingPeriod(convertHtmlToText(text))
}
private fun convertHtmlToText(potentialHtml: String): String {
return potentialHtml
.ifContains('<') { StringUtil.stripHtml(it, " ") }
.ifContains('&', StringUtil::unescapeXmlEntities)
} }
private fun addMissingPeriod(text: String): String { private fun addMissingPeriod(text: String): String {
return if (text.endsWith('.')) text else "$text." return if (text.endsWith('.')) text else "$text."
} }
private inline fun String.ifContains(charToTest: Char, action: (String) -> String): String {
return if (this.contains(charToTest)) action(this) else this
}
private fun fixBaselineForTextRendering(rect: Rectangle) { private fun fixBaselineForTextRendering(rect: Rectangle) {
rect.y += 1 rect.y += 1
} }

View File

@ -0,0 +1,59 @@
package com.chylex.intellij.inspectionlens
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 EditorInlayLensManagerTest {
@Nested
inner class Priority {
@ParameterizedTest(name = "offsetAndSeverity = {0}")
@ValueSource(ints = [0, -1, Int.MIN_VALUE])
fun minimumOffset(offsetAndSeverity: Int) {
assertEquals(Int.MAX_VALUE, EditorInlayLensManager.getInlayHintPriority(offsetAndSeverity, Int.MAX_VALUE))
}
@ParameterizedTest(name = "offset = {0}")
@ValueSource(ints = [8_589_934, Int.MAX_VALUE])
fun maximumOffset(offset: Int) {
assertEquals(Int.MIN_VALUE + 1, EditorInlayLensManager.getInlayHintPriority(offset, Int.MIN_VALUE))
}
@ParameterizedTest(name = "severity = {0}")
@ValueSource(ints = [0, 1, 250, 499, 500])
fun firstPriorityBucket(severity: Int) {
assertEquals(Int.MAX_VALUE - 500 + severity, EditorInlayLensManager.getInlayHintPriority(0, severity))
}
@ParameterizedTest(name = "severity = {0}")
@ValueSource(ints = [0, 1, 250, 499, 500])
fun secondPriorityBucket(severity: Int) {
assertEquals(Int.MAX_VALUE - 1000 + severity, EditorInlayLensManager.getInlayHintPriority(1, severity))
}
@ParameterizedTest(name = "severity = {0}")
@ValueSource(ints = [0, 1, 250, 499, 500])
fun penultimatePriorityBucket(severity: Int) {
assertEquals(Int.MIN_VALUE + 295 + severity, EditorInlayLensManager.getInlayHintPriority(8_589_933, severity))
}
/**
* If any of these change, re-evaluate [EditorInlayLensManager.MAXIMUM_SEVERITY] and the priority calculations.
*/
@Nested
inner class IdeaAssumptions {
@Test
fun smallestSeverityHasNotChanged() {
assertEquals(10, HighlightSeverity.DEFAULT_SEVERITIES.minOf(HighlightSeverity::myVal))
}
@Test
fun highestSeverityHasNotChanged() {
assertEquals(400, HighlightSeverity.DEFAULT_SEVERITIES.maxOf(HighlightSeverity::myVal))
}
}
}
}