5 Commits
12 changed files with 122 additions and 131 deletions
+8 -1
View File
@@ -8,7 +8,14 @@ plugins {
} }
group = "com.chylex.intellij.inspectionlens" group = "com.chylex.intellij.inspectionlens"
version = "1.6.1" version = "1.7.0"
idea {
module {
excludeDirs.add(file(".kotlin"))
excludeDirs.add(file("build"))
}
}
repositories { repositories {
mavenCentral() mavenCentral()
@@ -14,26 +14,26 @@ internal class EditorLensManager(private val editor: Editor) {
private val lenses = IdentityHashMap<RangeHighlighter, EditorLens>() private val lenses = IdentityHashMap<RangeHighlighter, EditorLens>()
private val settings = service<LensSettingsState>() private val settings = service<LensSettingsState>()
private fun show(highlighterWithInfo: HighlighterWithInfo) { private fun show(inspection: Inspection) {
if (editor.isDisposed) { if (editor.isDisposed) {
return return
} }
val (highlighter, info) = highlighterWithInfo val highlighter = inspection.highlighter
if (!highlighter.isValid) { if (!highlighter.isValid) {
return return
} }
val existingLens = lenses[highlighter] val existingLens = lenses[highlighter]
if (existingLens != null) { if (existingLens != null) {
if (existingLens.update(info, settings)) { if (existingLens.update(inspection, settings)) {
return return
} }
existingLens.hide() existingLens.hide()
} }
val newLens = EditorLens.show(editor, info, settings) val newLens = EditorLens.show(editor, inspection, settings)
if (newLens != null) { if (newLens != null) {
lenses[highlighter] = newLens lenses[highlighter] = newLens
} }
@@ -66,9 +66,9 @@ internal class EditorLensManager(private val editor: Editor) {
sealed interface Command { sealed interface Command {
fun apply(lensManager: EditorLensManager) fun apply(lensManager: EditorLensManager)
class Show(private val highlighter: HighlighterWithInfo) : Command { class Show(private val inspection: Inspection) : Command {
override fun apply(lensManager: EditorLensManager) { override fun apply(lensManager: EditorLensManager) {
lensManager.show(highlighter) lensManager.show(inspection)
} }
} }
@@ -7,8 +7,8 @@ internal class EditorLensManagerDispatcher(private val lensManager: EditorLensMa
private var queuedItems = mutableListOf<EditorLensManager.Command>() private var queuedItems = mutableListOf<EditorLensManager.Command>()
private var isEnqueued = false private var isEnqueued = false
fun show(highlighterWithInfo: HighlighterWithInfo) { fun show(inspection: Inspection) {
enqueue(EditorLensManager.Command.Show(highlighterWithInfo)) enqueue(EditorLensManager.Command.Show(inspection))
} }
fun hide(highlighter: RangeHighlighter) { fun hide(highlighter: RangeHighlighter) {
@@ -1,28 +0,0 @@
package com.chylex.intellij.inspectionlens.editor
import com.intellij.codeInsight.daemon.impl.AsyncDescriptionSupplier
import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.openapi.editor.markup.RangeHighlighter
import java.util.function.Consumer
open class HighlighterWithInfo private constructor(val highlighter: RangeHighlighter, private val info: HighlightInfo) {
val hasDescription
get() = info.description != null
operator fun component1() = highlighter
operator fun component2() = info
class Async(highlighter: RangeHighlighter, info: HighlightInfo, private val provider: AsyncDescriptionSupplier) : HighlighterWithInfo(highlighter, info) {
fun requestDescription(callback: Consumer<String?>) {
provider.requestDescription().onSuccess(callback)
}
}
companion object {
fun from(highlighter: RangeHighlighter, info: HighlightInfo) =
if (info is AsyncDescriptionSupplier)
Async(highlighter, info, info)
else
HighlighterWithInfo(highlighter, info)
}
}
@@ -0,0 +1,6 @@
package com.chylex.intellij.inspectionlens.editor
import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.openapi.editor.markup.RangeHighlighter
data class Inspection(val highlighter: RangeHighlighter, val info: HighlightInfo)
@@ -2,6 +2,7 @@ package com.chylex.intellij.inspectionlens.editor
import com.chylex.intellij.inspectionlens.InspectionLens import com.chylex.intellij.inspectionlens.InspectionLens
import com.chylex.intellij.inspectionlens.settings.LensSettingsState import com.chylex.intellij.inspectionlens.settings.LensSettingsState
import com.intellij.codeInsight.daemon.impl.AsyncDescriptionSupplier
import com.intellij.codeInsight.daemon.impl.HighlightInfo import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil.isFileLevelOrGutterAnnotation import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil.isFileLevelOrGutterAnnotation
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
@@ -39,28 +40,25 @@ internal class LensMarkupModelListener(private val lensManagerDispatcher: Editor
return return
} }
val highlighterWithInfo = HighlighterWithInfo.from(highlighter, info) if (info is AsyncDescriptionSupplier) {
processHighlighterWithInfo(highlighterWithInfo, lensManagerDispatcher::show, ::showAsynchronously) info.requestDescription().onSuccess {
if (highlighter.isValid) {
showInspection(highlighter, info)
}
}
}
else {
showInspection(highlighter, info)
}
} }
private fun getFilteredHighlightInfo(highlighter: RangeHighlighter): HighlightInfo? { private fun getFilteredHighlightInfo(highlighter: RangeHighlighter): HighlightInfo? {
return HighlightInfo.fromRangeHighlighter(highlighter)?.takeIf { settings.severityFilter.test(it.severity) } return HighlightInfo.fromRangeHighlighter(highlighter)?.takeIf { settings.severityFilter.test(it.severity) }
} }
private inline fun processHighlighterWithInfo(highlighterWithInfo: HighlighterWithInfo, actionForImmediate: (HighlighterWithInfo) -> Unit, actionForAsync: (HighlighterWithInfo.Async) -> Unit) { private fun showInspection(highlighter: RangeHighlighter, info: HighlightInfo) {
if (highlighterWithInfo is HighlighterWithInfo.Async) { if (info.description != null) {
actionForAsync(highlighterWithInfo) lensManagerDispatcher.show(Inspection(highlighter, info))
}
else if (highlighterWithInfo.hasDescription) {
actionForImmediate(highlighterWithInfo)
}
}
private fun showAsynchronously(highlighterWithInfo: HighlighterWithInfo.Async) {
highlighterWithInfo.requestDescription {
if (highlighterWithInfo.highlighter.isValid && highlighterWithInfo.hasDescription) {
lensManagerDispatcher.show(highlighterWithInfo)
}
} }
} }
@@ -1,23 +1,23 @@
package com.chylex.intellij.inspectionlens.editor.lens package com.chylex.intellij.inspectionlens.editor.lens
import com.chylex.intellij.inspectionlens.editor.Inspection
import com.chylex.intellij.inspectionlens.settings.LensSettingsState 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.Editor
internal class EditorLens private constructor(private var inlay: EditorLensInlay, private var lineBackground: EditorLensLineBackground, private var severity: LensSeverity) { internal class EditorLens private constructor(private var inlay: EditorLensInlay, private var lineBackground: EditorLensLineBackground, private var severity: LensSeverity) {
fun update(info: HighlightInfo, settings: LensSettingsState): Boolean { fun update(inspection: Inspection, settings: LensSettingsState): Boolean {
val editor = inlay.editor val editor = inlay.editor
val oldSeverity = severity val oldSeverity = severity
severity = LensSeverity.from(info.severity) severity = LensSeverity.from(inspection.info.severity)
if (!inlay.tryUpdate(info)) { if (!inlay.tryUpdate(inspection)) {
inlay = EditorLensInlay.show(editor, info, settings) ?: return false inlay = EditorLensInlay.show(editor, inspection, settings) ?: return false
} }
if (lineBackground.isInvalid || oldSeverity != severity) { if (lineBackground.isInvalid || oldSeverity != severity) {
lineBackground.hide(editor) lineBackground.hide(editor)
lineBackground = EditorLensLineBackground.show(editor, info) lineBackground = EditorLensLineBackground.show(editor, inspection.info)
} }
return true return true
@@ -33,8 +33,9 @@ internal class EditorLens private constructor(private var inlay: EditorLensInlay
} }
companion object { companion object {
fun show(editor: Editor, info: HighlightInfo, settings: LensSettingsState): EditorLens? { fun show(editor: Editor, inspection: Inspection, settings: LensSettingsState): EditorLens? {
val inlay = EditorLensInlay.show(editor, info, settings) ?: return null val info = inspection.info
val inlay = EditorLensInlay.show(editor, inspection, settings) ?: return null
val lineBackground = EditorLensLineBackground.show(editor, info) val lineBackground = EditorLensLineBackground.show(editor, info)
val severity = LensSeverity.from(info.severity) val severity = LensSeverity.from(info.severity)
return EditorLens(inlay, lineBackground, severity) return EditorLens(inlay, lineBackground, severity)
@@ -1,5 +1,6 @@
package com.chylex.intellij.inspectionlens.editor.lens package com.chylex.intellij.inspectionlens.editor.lens
import com.chylex.intellij.inspectionlens.editor.Inspection
import com.chylex.intellij.inspectionlens.settings.LensSettingsState import com.chylex.intellij.inspectionlens.settings.LensSettingsState
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
@@ -11,12 +12,12 @@ internal value class EditorLensInlay(private val inlay: Inlay<LensRenderer>) {
val editor val editor
get() = inlay.editor get() = inlay.editor
fun tryUpdate(info: HighlightInfo): Boolean { fun tryUpdate(inspection: Inspection): Boolean {
if (!inlay.isValid) { if (!inlay.isValid) {
return false return false
} }
inlay.renderer.setPropertiesFrom(info) inlay.renderer.setPropertiesFrom(inspection)
inlay.update() inlay.update()
return true return true
} }
@@ -26,10 +27,10 @@ internal value class EditorLensInlay(private val inlay: Inlay<LensRenderer>) {
} }
companion object { companion object {
fun show(editor: Editor, info: HighlightInfo, settings: LensSettingsState): EditorLensInlay? { fun show(editor: Editor, inspection: Inspection, settings: LensSettingsState): EditorLensInlay? {
val offset = getInlayHintOffset(info) val offset = getInlayHintOffset(inspection.info)
val priority = getInlayHintPriority(editor, info) val priority = getInlayHintPriority(editor, inspection.info)
val renderer = LensRenderer(info, settings) val renderer = LensRenderer(inspection, settings)
val properties = InlayProperties() val properties = InlayProperties()
.relatesToPrecedingText(true) .relatesToPrecedingText(true)
@@ -71,6 +72,6 @@ internal value class EditorLensInlay(private val inlay: Inlay<LensRenderer>) {
private fun getLineStartOffset(editor: Editor, offset: Int): Int { private fun getLineStartOffset(editor: Editor, offset: Int): Int {
val position = editor.offsetToLogicalPosition(offset) val position = editor.offsetToLogicalPosition(offset)
return editor.document.getLineStartOffset(position.line) return editor.document.getLineStartOffset(position.line)
} }
} }
} }
@@ -1,33 +1,31 @@
package com.chylex.intellij.inspectionlens.editor.lens package com.chylex.intellij.inspectionlens.editor.lens
import com.chylex.intellij.inspectionlens.InspectionLens import com.chylex.intellij.inspectionlens.InspectionLens
import com.chylex.intellij.inspectionlens.editor.Inspection
import com.intellij.codeInsight.daemon.impl.HighlightInfo import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.codeInsight.daemon.impl.HighlightInfo.IntentionActionDescriptor import com.intellij.codeInsight.daemon.impl.HighlightInfo.IntentionActionDescriptor
import com.intellij.codeInsight.daemon.impl.IntentionsUI import com.intellij.codeInsight.daemon.impl.IntentionsUI
import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass
import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass.IntentionsInfo import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass.IntentionsInfo
import com.intellij.codeInsight.hint.HintManager import com.intellij.codeInsight.hint.HintManager
import com.intellij.codeInsight.intention.impl.CachedIntentions import com.intellij.codeInsight.intention.impl.CachedIntentions
import com.intellij.codeInsight.intention.impl.IntentionHintComponent import com.intellij.codeInsight.intention.impl.IntentionHintComponent
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
import com.intellij.ide.DataManager import com.intellij.ide.DataManager
import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.ActionUiKind import com.intellij.openapi.actionSystem.ActionUiKind
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.Inlay import com.intellij.openapi.editor.Inlay
import com.intellij.openapi.project.DumbService import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiUtilBase import com.intellij.psi.util.PsiUtilBase
import com.intellij.ui.awt.RelativePoint import com.intellij.ui.awt.RelativePoint
import com.intellij.util.concurrency.AppExecutorUtil
import java.lang.reflect.Method import java.lang.reflect.Method
internal object IntentionsPopup { internal object IntentionsPopup {
@@ -38,38 +36,39 @@ internal object IntentionsPopup {
val parameterTypes = method.parameterTypes val parameterTypes = method.parameterTypes
method.name == "showPopup" && method.name == "showPopup" &&
parameterTypes.size in 1..2 && parameterTypes.size == 2 &&
parameterTypes[0] === RelativePoint::class.java && parameterTypes[0] === RelativePoint::class.java &&
parameterTypes.getOrNull(1).let { p -> p == null || p.name == INTENTION_SOURCE_CLASS_NAME } parameterTypes[1].name == INTENTION_SOURCE_CLASS_NAME
} }
method.isAccessible = true method.isAccessible = true
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val args: Array<Any?> = if (method.parameterCount == 1) val intentionSourceEnum = Class.forName(INTENTION_SOURCE_CLASS_NAME) as Class<Enum<*>>
arrayOf(null) val intentionSource = intentionSourceEnum.enumConstants.first { it.name == "OTHER" }
else
arrayOf(null, (Class.forName(INTENTION_SOURCE_CLASS_NAME) as Class<Enum<*>>).enumConstants.first { it.name == "OTHER" })
ShowPopupMethod(method, args) ShowPopupMethod(method, arrayOf(null, intentionSource))
} catch (t: Throwable) { } catch (t: Throwable) {
InspectionLens.LOG.warn("Could not initialize intention popup", t) InspectionLens.LOG.warn("Could not initialize intention popup", t)
null null
} }
val hasShowPopupMethod
get() = showPopupMethod != null
private class ShowPopupMethod(private val method: Method, private val args: Array<Any?>) { private class ShowPopupMethod(private val method: Method, private val args: Array<Any?>) {
operator fun invoke(component: IntentionHintComponent) { operator fun invoke(component: IntentionHintComponent) {
method.invoke(component, *args) method.invoke(component, *args)
} }
} }
fun show(highlightInfo: HighlightInfo, inlay: Inlay<*>) { fun show(inspection: Inspection, inlay: Inlay<*>) {
if (!tryShow(highlightInfo, inlay)) { if (!tryShow(inspection, inlay)) {
showNoActionsAvailable(inlay.editor) HintManager.getInstance().showInformationHint(inlay.editor, "No context actions available at this location")
} }
} }
private fun tryShow(highlightInfo: HighlightInfo, inlay: Inlay<*>): Boolean { private fun tryShow(inspection: Inspection, inlay: Inlay<*>): Boolean {
val editor = inlay.editor val editor = inlay.editor
val project = editor.project ?: return false val project = editor.project ?: return false
val file = PsiUtilBase.getPsiFileInEditor(editor, project) ?: return false val file = PsiUtilBase.getPsiFileInEditor(editor, project) ?: return false
@@ -77,46 +76,42 @@ internal object IntentionsPopup {
PsiDocumentManager.getInstance(project).commitAllDocuments() PsiDocumentManager.getInstance(project).commitAllDocuments()
IntentionsUI.getInstance(project).hide() IntentionsUI.getInstance(project).hide()
ReadAction val intentions = collectIntentions(editor, file, inspection.highlighter, inlay.offset)
.nonBlocking<IntentionsInfo> { collectIntentions(editor, project, file, highlightInfo, inlay.offset) } if (intentions == null) {
.finishOnUiThread(ModalityState.current()) { tryShowPopup(project, file, editor, it) } return false
.submit(AppExecutorUtil.getAppExecutorService()) }
return true try {
showIntentionsPopup(project, file, editor, intentions)
return true
} catch (t: Throwable) {
InspectionLens.LOG.error("Could not show intention popup", t)
return false
}
} }
private fun collectIntentions(editor: Editor, project: Project, file: PsiFile, info: HighlightInfo, offset: Int): IntentionsInfo { private fun collectIntentions(editor: Editor, file: PsiFile, highlighter: RangeHighlighter, offset: Int): IntentionsInfo? {
val intentions = mutableListOf<IntentionActionDescriptor>() val resolvedInfo = HighlightInfo.fromRangeHighlighter(highlighter)
if (resolvedInfo == null) {
return null
}
info.findRegisteredQuickFix { descriptor, _ -> val intentionActions = mutableListOf<IntentionActionDescriptor>()
if (DumbService.getInstance(project).isUsableInCurrentContext(descriptor.action) && ShowIntentionActionsHandler.availableFor(file, editor, offset, descriptor.action)) {
intentions.add(descriptor) resolvedInfo.findRegisteredQuickFix { descriptor, _ ->
if (ShowIntentionActionsHandler.availableFor(file, editor, offset, descriptor.action)) {
intentionActions.add(descriptor)
} }
null null
} }
return IntentionsInfo().also { return IntentionsInfo().also {
it.offset = offset it.offset = offset
ShowIntentionsPass.fillIntentionsInfoForHighlightInfo(resolvedInfo, it, intentionActions)
if (info.severity === HighlightSeverity.ERROR) {
it.errorFixesToShow.addAll(intentions)
}
else {
it.inspectionFixesToShow.addAll(intentions)
}
} }
} }
private fun tryShowPopup(project: Project, file: PsiFile, editor: Editor, intentions: IntentionsInfo) { private fun showIntentionsPopup(project: Project, file: PsiFile, editor: Editor, intentions: IntentionsInfo) {
try {
showPopup(project, file, editor, intentions)
} catch (t: Throwable) {
InspectionLens.LOG.error("Could not show intention popup", t)
showNoActionsAvailable(editor)
}
}
private fun showPopup(project: Project, file: PsiFile, editor: Editor, intentions: IntentionsInfo) {
if (intentions.isEmpty || showPopupMethod == null) { if (intentions.isEmpty || showPopupMethod == null) {
val showIntentionsAction = ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS) val showIntentionsAction = ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS)
val dataContext = DataManager.getInstance().getDataContext(editor.component) val dataContext = DataManager.getInstance().getDataContext(editor.component)
@@ -130,7 +125,4 @@ internal object IntentionsPopup {
} }
} }
private fun showNoActionsAvailable(editor: Editor) {
HintManager.getInstance().showInformationHint(editor, "No context actions available at this location")
}
} }
@@ -1,8 +1,8 @@
package com.chylex.intellij.inspectionlens.editor.lens package com.chylex.intellij.inspectionlens.editor.lens
import com.chylex.intellij.inspectionlens.editor.Inspection
import com.chylex.intellij.inspectionlens.settings.LensHoverMode import com.chylex.intellij.inspectionlens.settings.LensHoverMode
import com.chylex.intellij.inspectionlens.settings.LensSettingsState 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.daemon.impl.HintRenderer
import com.intellij.codeInsight.hints.presentation.InputHandler import com.intellij.codeInsight.hints.presentation.InputHandler
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
@@ -27,14 +27,14 @@ import javax.swing.SwingUtilities
/** /**
* Renders the text of an inspection lens. * Renders the text of an inspection lens.
*/ */
class LensRenderer(private var info: HighlightInfo, private val settings: LensSettingsState) : HintRenderer(null), InputHandler { class LensRenderer(private var inspection: Inspection, private val settings: LensSettingsState) : HintRenderer(null), InputHandler {
private lateinit var inlay: Inlay<*> private lateinit var inlay: Inlay<*>
private lateinit var attributes: LensSeverityTextAttributes private lateinit var attributes: LensSeverityTextAttributes
private var extraRightPadding = 0 private var extraRightPadding = 0
private var hovered = false private var hovered = false
init { init {
setPropertiesFrom(info) setPropertiesFrom(inspection)
} }
fun setInlay(inlay: Inlay<*>) { fun setInlay(inlay: Inlay<*>) {
@@ -42,12 +42,12 @@ class LensRenderer(private var info: HighlightInfo, private val settings: LensSe
this.inlay = inlay this.inlay = inlay
} }
fun setPropertiesFrom(info: HighlightInfo) { fun setPropertiesFrom(inspection: Inspection) {
this.info = info this.inspection = inspection
val description = getValidDescriptionText(info.description, settings.maxDescriptionLength) val description = getValidDescriptionText(inspection.info.description, settings.maxDescriptionLength)
text = description text = description
attributes = LensSeverity.from(info.severity).textAttributes attributes = LensSeverity.from(inspection.info.severity).textAttributes
extraRightPadding = if (description.lastOrNull() == '.') 2 else 0 extraRightPadding = if (description.lastOrNull() == '.') 2 else 0
} }
@@ -55,7 +55,7 @@ class LensRenderer(private var info: HighlightInfo, private val settings: LensSe
fixBaselineForTextRendering(r) fixBaselineForTextRendering(r)
super.paint(inlay, g, r, textAttributes) super.paint(inlay, g, r, textAttributes)
if (hovered && isHoveringText()) { if (hovered) {
paintHoverEffect(inlay, g, r) paintHoverEffect(inlay, g, r)
} }
} }
@@ -85,7 +85,7 @@ class LensRenderer(private var info: HighlightInfo, private val settings: LensSe
} }
override fun mouseMoved(event: MouseEvent, translated: Point) { override fun mouseMoved(event: MouseEvent, translated: Point) {
setHovered(isHoveringText(translated)) setHovered(isHoveringText())
} }
override fun mouseExited() { override fun mouseExited() {
@@ -114,7 +114,7 @@ class LensRenderer(private var info: HighlightInfo, private val settings: LensSe
override fun mousePressed(event: MouseEvent, translated: Point) { override fun mousePressed(event: MouseEvent, translated: Point) {
val hoverMode = settings.lensHoverMode val hoverMode = settings.lensHoverMode
if (hoverMode == LensHoverMode.DISABLED || !isHoveringText(translated)) { if (hoverMode == LensHoverMode.DISABLED || !isHoveringText()) {
return return
} }
@@ -122,25 +122,21 @@ class LensRenderer(private var info: HighlightInfo, private val settings: LensSe
event.consume() event.consume()
val editor = inlay.editor val editor = inlay.editor
moveToOffset(editor, info.actualStartOffset) moveToOffset(editor, inspection.info.actualStartOffset)
if ((event.button == MouseEvent.BUTTON1) xor (hoverMode != LensHoverMode.DEFAULT)) { if ((event.button == MouseEvent.BUTTON1) xor (hoverMode != LensHoverMode.DEFAULT)) {
IntentionsPopup.show(info, inlay) IntentionsPopup.show(inspection, inlay)
} }
} }
} }
private fun isHoveringText(): Boolean { private fun isHoveringText(): Boolean {
val bounds = inlay.bounds ?: return false val bounds = inlay.bounds ?: return false
val translatedPoint = MouseInfo.getPointerInfo().location.apply { val point = MouseInfo.getPointerInfo().location.apply {
SwingUtilities.convertPointFromScreen(this, inlay.editor.contentComponent) SwingUtilities.convertPointFromScreen(this, inlay.editor.contentComponent)
translate(-bounds.x, -bounds.y) translate(-bounds.x, -bounds.y)
} }
return isHoveringText(translatedPoint)
}
private fun isHoveringText(point: Point): Boolean {
return point.x >= HOVER_HORIZONTAL_PADDING return point.x >= HOVER_HORIZONTAL_PADDING
&& point.y >= 4 && point.y >= 4
&& point.x < inlay.widthInPixels - HOVER_HORIZONTAL_PADDING - extraRightPadding && point.x < inlay.widthInPixels - HOVER_HORIZONTAL_PADDING - extraRightPadding
+7
View File
@@ -14,6 +14,12 @@
]]></description> ]]></description>
<change-notes><![CDATA[ <change-notes><![CDATA[
<b>Version 1.7.0</b>
<ul>
<li>Updated minimum version to IntelliJ 2025.3 to address API deprecations.</li>
<li>Improved reliability of only showing relevant quick fixes when clicking an inspection.</li>
<li>Fixed lenses not always rendering an underline when hovered.</li>
</ul>
<b>Version 1.6.1</b> <b>Version 1.6.1</b>
<ul> <ul>
<li>Fixed exception caused by accessing disposed editors.</li> <li>Fixed exception caused by accessing disposed editors.</li>
@@ -122,6 +128,7 @@
instance="com.chylex.intellij.inspectionlens.settings.LensApplicationConfigurable" instance="com.chylex.intellij.inspectionlens.settings.LensApplicationConfigurable"
displayName="Inspection Lens" displayName="Inspection Lens"
parentId="tools" /> parentId="tools" />
<errorHandler implementation="com.intellij.diagnostic.JetBrainsMarketplaceErrorReportSubmitter"/>
</extensions> </extensions>
<actions> <actions>
@@ -0,0 +1,11 @@
package com.chylex.intellij.inspectionlens.editor.lens
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class IntentionsPopupTest {
@Test
fun hasShowPopupMethod() {
assertTrue(IntentionsPopup.hasShowPopupMethod)
}
}