mirror of
https://github.com/chylex/IntelliJ-Inspection-Lens.git
synced 2025-05-10 08:34:05 +02:00
Make inspection lenses clickable and show popup with intentions
This commit is contained in:
parent
3bf7abab49
commit
66abf6b349
src/main/kotlin/com/chylex/intellij/inspectionlens/editor
@ -32,7 +32,9 @@ internal value class EditorLensInlay(private val inlay: Inlay<LensRenderer>) {
|
||||
val renderer = LensRenderer(info)
|
||||
val properties = InlayProperties().relatesToPrecedingText(true).disableSoftWrapping(true).priority(priority)
|
||||
|
||||
return editor.inlayModel.addAfterLineEndElement(offset, properties, renderer)?.let(::EditorLensInlay)
|
||||
return editor.inlayModel.addAfterLineEndElement(offset, properties, renderer)
|
||||
?.also(renderer::setInlay)
|
||||
?.let(::EditorLensInlay)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,67 @@
|
||||
package com.chylex.intellij.inspectionlens.editor
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo.IntentionActionDescriptor
|
||||
import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass.IntentionsInfo
|
||||
import com.intellij.codeInsight.intention.impl.CachedIntentions
|
||||
import com.intellij.codeInsight.intention.impl.IntentionHintComponent
|
||||
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
|
||||
import com.intellij.lang.annotation.HighlightSeverity
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.ReadAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.Inlay
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.findPsiFile
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.ui.awt.RelativePoint
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import java.lang.invoke.MethodHandles
|
||||
|
||||
internal object IntentionPopup {
|
||||
fun show(info: HighlightInfo, inlay: Inlay<*>, point: RelativePoint) {
|
||||
val editor = inlay.editor
|
||||
val project = editor.project ?: return
|
||||
val psiFile = editor.virtualFile.findPsiFile(project) ?: return
|
||||
|
||||
ReadAction
|
||||
.nonBlocking<IntentionsInfo> { collectIntentions(editor, project, psiFile, info, inlay.offset) }
|
||||
.finishOnUiThread(ModalityState.current()) { showPopup(project, psiFile, editor, it, point) }
|
||||
.submit(AppExecutorUtil.getAppExecutorService())
|
||||
}
|
||||
|
||||
private fun collectIntentions(editor: Editor, project: Project, psiFile: PsiFile, info: HighlightInfo, offset: Int): IntentionsInfo {
|
||||
val intentions = mutableListOf<IntentionActionDescriptor>()
|
||||
|
||||
info.findRegisteredQuickFix { descriptor, _ ->
|
||||
if (DumbService.getInstance(project).isUsableInCurrentContext(descriptor.action) && ShowIntentionActionsHandler.availableFor(psiFile, editor, offset, descriptor.action)) {
|
||||
intentions.add(descriptor)
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
return IntentionsInfo().also {
|
||||
it.offset = offset
|
||||
|
||||
if (info.severity === HighlightSeverity.ERROR) {
|
||||
it.errorFixesToShow.addAll(intentions)
|
||||
}
|
||||
else {
|
||||
it.inspectionFixesToShow.addAll(intentions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val showPopupMethod by lazy {
|
||||
IntentionHintComponent::class.java.getDeclaredMethod("showPopup", RelativePoint::class.java)
|
||||
.also { it.isAccessible = true }
|
||||
.let(MethodHandles.lookup()::unreflect)
|
||||
}
|
||||
|
||||
private fun showPopup(project: Project, psiFile: PsiFile, editor: Editor, intentions: IntentionsInfo, point: RelativePoint) {
|
||||
val cachedIntentions = CachedIntentions.create(project, psiFile, editor, intentions)
|
||||
val component = IntentionHintComponent.showIntentionHint(project, psiFile, editor, false, cachedIntentions)
|
||||
showPopupMethod.invoke(component, point)
|
||||
}
|
||||
}
|
@ -1,25 +1,46 @@
|
||||
package com.chylex.intellij.inspectionlens.editor
|
||||
|
||||
import com.intellij.codeInsight.codeVision.ui.popup.layouter.bottom
|
||||
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.colors.EditorFontType
|
||||
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.awt.RelativePoint
|
||||
import com.intellij.ui.paint.EffectPainter
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import java.awt.Cursor
|
||||
import java.awt.Graphics
|
||||
import java.awt.Graphics2D
|
||||
import java.awt.Point
|
||||
import java.awt.Rectangle
|
||||
import java.awt.event.MouseEvent
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
/**
|
||||
* Renders the text of an inspection lens.
|
||||
*/
|
||||
class LensRenderer(info: HighlightInfo) : HintRenderer(null) {
|
||||
class LensRenderer(private var info: HighlightInfo) : HintRenderer(null), InputHandler {
|
||||
private lateinit var severity: LensSeverity
|
||||
private lateinit var inlay: Inlay<*>
|
||||
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
|
||||
|
||||
text = getValidDescriptionText(info.description)
|
||||
severity = LensSeverity.from(info.severity)
|
||||
}
|
||||
@ -27,6 +48,14 @@ class LensRenderer(info: HighlightInfo) : HintRenderer(null) {
|
||||
override fun paint(inlay: Inlay<*>, g: Graphics, r: Rectangle, textAttributes: TextAttributes) {
|
||||
fixBaselineForTextRendering(r)
|
||||
super.paint(inlay, g, r, textAttributes)
|
||||
|
||||
if (hovered) {
|
||||
val editor = inlay.editor as EditorImpl
|
||||
val font = editor.colorsScheme.getFont(EditorFontType.PLAIN)
|
||||
|
||||
g.color = severity.textAttributes.foregroundColor
|
||||
EffectPainter.LINE_UNDERSCORE.paint(g as Graphics2D, r.x + TEXT_PADDING_SIDE, r.y + editor.ascent + 1, r.width - UNDERLINE_SHRINKAGE, editor.descent, font)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTextAttributes(editor: Editor): TextAttributes {
|
||||
@ -37,7 +66,47 @@ class LensRenderer(info: HighlightInfo) : HintRenderer(null) {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun mouseMoved(event: MouseEvent, translated: Point) {
|
||||
setHovered(true)
|
||||
}
|
||||
|
||||
override fun mouseExited() {
|
||||
setHovered(false)
|
||||
}
|
||||
|
||||
private fun setHovered(hovered: Boolean) {
|
||||
if (this.hovered == hovered) {
|
||||
return
|
||||
}
|
||||
|
||||
this.hovered = hovered
|
||||
inlay.repaint()
|
||||
|
||||
val cursor = Cursor.getPredefinedCursor(if (hovered) Cursor.HAND_CURSOR else Cursor.DEFAULT_CURSOR)
|
||||
val contentComponent = inlay.editor.contentComponent
|
||||
if (contentComponent.cursor != cursor) {
|
||||
UIUtil.setCursor(contentComponent, cursor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mousePressed(event: MouseEvent, translated: Point) {
|
||||
if (!SwingUtilities.isLeftMouseButton(event)) {
|
||||
return
|
||||
}
|
||||
|
||||
val bounds = inlay.bounds ?: return
|
||||
|
||||
event.consume()
|
||||
IntentionPopup.show(info, inlay, RelativePoint(event.component, Point(bounds.x + TEXT_PADDING_SIDE + 1, bounds.bottom + 3)))
|
||||
}
|
||||
|
||||
private companion object {
|
||||
/**
|
||||
* [HintRenderer.paintHint] renders padding around text, but not around effects.
|
||||
*/
|
||||
private const val TEXT_PADDING_SIDE = 8
|
||||
private const val UNDERLINE_SHRINKAGE = (TEXT_PADDING_SIDE * 2) + 1
|
||||
|
||||
private fun getValidDescriptionText(text: String?): String {
|
||||
return if (text.isNullOrBlank()) " " else addMissingPeriod(unescapeHtmlEntities(text))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user