From 94602bd8f9281bf768ef8cbbbdbb8eb2877ff233 Mon Sep 17 00:00:00 2001
From: synopss <synopss@outlook.com>
Date: Fri, 12 May 2023 18:06:12 +0200
Subject: [PATCH] Add line background colors

Closes #14
---
 .../inspectionlens/editor/EditorLens.kt       | 14 ++++-
 .../editor/EditorLensLineBackground.kt        | 53 +++++++++++++++++++
 .../editor/EditorLensManager.kt               |  3 ++
 .../inspectionlens/editor/LensRenderer.kt     |  2 +-
 .../inspectionlens/editor/LensSeverity.kt     | 23 ++++----
 .../editor/LensSeverityTextAttributes.kt      | 12 ++++-
 6 files changed, 93 insertions(+), 14 deletions(-)
 create mode 100644 src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensLineBackground.kt

diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLens.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLens.kt
index 433dd35..485791c 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLens.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLens.kt
@@ -3,7 +3,7 @@ package com.chylex.intellij.inspectionlens.editor
 import com.intellij.codeInsight.daemon.impl.HighlightInfo
 import com.intellij.openapi.editor.Editor
 
-internal class EditorLens private constructor(private var inlay: EditorLensInlay) {
+internal class EditorLens private constructor(private var inlay: EditorLensInlay, private var lineBackground: EditorLensLineBackground) {
 	fun update(info: HighlightInfo): Boolean {
 		val editor = inlay.editor
 		
@@ -11,16 +11,26 @@ internal class EditorLens private constructor(private var inlay: EditorLensInlay
 			inlay = EditorLensInlay.show(editor, info) ?: return false
 		}
 		
+		if (lineBackground.shouldRecreate(info)) {
+			lineBackground.hide(editor)
+			lineBackground = EditorLensLineBackground.show(editor, info)
+		}
+		
 		return true
 	}
 	
 	fun hide() {
+		val editor = inlay.editor
+		
 		inlay.hide()
+		lineBackground.hide(editor)
 	}
 	
 	companion object {
 		fun show(editor: Editor, info: HighlightInfo): EditorLens? {
-			return EditorLensInlay.show(editor, info)?.let(::EditorLens)
+			val inlay = EditorLensInlay.show(editor, info) ?: return null
+			val lineBackground = EditorLensLineBackground.show(editor, info)
+			return EditorLens(inlay, lineBackground)
 		}
 	}
 }
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensLineBackground.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensLineBackground.kt
new file mode 100644
index 0000000..7bb1857
--- /dev/null
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensLineBackground.kt
@@ -0,0 +1,53 @@
+package com.chylex.intellij.inspectionlens.editor
+
+import com.intellij.codeInsight.daemon.impl.HighlightInfo
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.editor.markup.HighlighterLayer
+import com.intellij.openapi.editor.markup.HighlighterTargetArea.LINES_IN_RANGE
+import com.intellij.openapi.editor.markup.RangeHighlighter
+
+@JvmInline
+internal value class EditorLensLineBackground(private val highlighter: RangeHighlighter) {
+	@Suppress("RedundantIf")
+	fun shouldRecreate(info: HighlightInfo): Boolean {
+		if (!highlighter.isValid) {
+			return true
+		}
+		
+		val severity = LensSeverity.from(info.severity)
+		
+		val currentTextAttributes = highlighter.getTextAttributes(null)
+		val newTextAttributes = severity.lineAttributes
+		if (currentTextAttributes !== newTextAttributes) {
+			return true
+		}
+		
+		val currentLayer = highlighter.layer
+		val newLayer = getHighlightLayer(severity)
+		if (currentLayer != newLayer) {
+			return true
+		}
+		
+		return false
+	}
+	
+	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
+			
+			val severity = LensSeverity.from(info.severity)
+			val layer = getHighlightLayer(severity)
+			
+			return EditorLensLineBackground(editor.markupModel.addRangeHighlighter(startOffset, endOffset, layer, severity.lineAttributes, LINES_IN_RANGE))
+		}
+		
+		private fun getHighlightLayer(severity: LensSeverity): Int {
+			return HighlighterLayer.CARET_ROW - 100 - severity.ordinal
+		}
+	}
+}
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt
index 99f48ef..1343fd2 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt
@@ -71,6 +71,9 @@ class EditorLensManager private constructor(private val editor: Editor) {
 		}
 	}
 	
+	/**
+	 * 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) {
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensRenderer.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensRenderer.kt
index 2ae39a0..16d1630 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensRenderer.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensRenderer.kt
@@ -30,7 +30,7 @@ class LensRenderer(info: HighlightInfo) : HintRenderer(null) {
 	}
 	
 	override fun getTextAttributes(editor: Editor): TextAttributes {
-		return severity.colorAttributes
+		return severity.textAttributes
 	}
 	
 	override fun useEditorFont(): Boolean {
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt
index f144957..412c7c0 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt
@@ -5,6 +5,7 @@ import com.chylex.intellij.inspectionlens.utils.DebouncingInvokeOnDispatchThread
 import com.intellij.lang.annotation.HighlightSeverity
 import com.intellij.spellchecker.SpellCheckerSeveritiesProvider
 import com.intellij.ui.ColorUtil
+import com.intellij.ui.ColorUtil.toAlpha
 import com.intellij.ui.JBColor
 import java.awt.Color
 import java.awt.Font
@@ -15,22 +16,26 @@ import java.util.Collections
  */
 @Suppress("UseJBColor", "InspectionUsingGrayColors")
 enum class LensSeverity(baseColor: Color, lightThemeDarkening: Int, darkThemeBrightening: Int) {
-	ERROR          (Color(158,  41,  39), lightThemeDarkening = 1, darkThemeBrightening = 4),
-	WARNING        (Color(190, 145,  23), lightThemeDarkening = 4, darkThemeBrightening = 1),
-	WEAK_WARNING   (Color(117, 109,  86), lightThemeDarkening = 3, darkThemeBrightening = 3),
-	SERVER_PROBLEM (Color(176,  97,   0), lightThemeDarkening = 4, darkThemeBrightening = 2),
-	GRAZIE         (Color( 53, 146, 196), lightThemeDarkening = 2, darkThemeBrightening = 1),
-	TYPO           (Color( 73, 156,  84), lightThemeDarkening = 3, darkThemeBrightening = 1),
-	OTHER          (Color(128, 128, 128), lightThemeDarkening = 1, darkThemeBrightening = 2);
+	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 colorAttributes: LensSeverityTextAttributes
+	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)
-		colorAttributes = LensSeverityTextAttributes(foregroundColor = textColor, fontStyle = Font.ITALIC)
+		val lineColor = JBColor(toAlpha(lightThemeColor, 10), toAlpha(darkThemeColor, 13))
+		
+		textAttributes = LensSeverityTextAttributes(foregroundColor = textColor, fontStyle = Font.ITALIC)
+		lineAttributes = LensSeverityTextAttributes(backgroundColor = lineColor)
 	}
 	
 	companion object {
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverityTextAttributes.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverityTextAttributes.kt
index c74d984..7eaf9c8 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverityTextAttributes.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverityTextAttributes.kt
@@ -4,11 +4,19 @@ import com.intellij.openapi.editor.markup.UnmodifiableTextAttributes
 import com.intellij.ui.JBColor
 import java.awt.Font
 
-class LensSeverityTextAttributes(private val foregroundColor: JBColor, private val fontStyle: Int = Font.PLAIN) : UnmodifiableTextAttributes() {
-	override fun getForegroundColor(): JBColor {
+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
 	}