mirror of
https://github.com/chylex/IntelliJ-Inspection-Lens.git
synced 2024-11-25 16:42:54 +01:00
Compare commits
No commits in common. "2eb185aa9dc25d7878ea762f34046cb0703dd9e5" and "77a5119c8d54e641be9ea4bcdb12df546eacc5b8" have entirely different histories.
2eb185aa9d
...
77a5119c8d
@ -4,7 +4,7 @@ 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
|
||||||
import com.intellij.openapi.editor.InlayProperties
|
import com.intellij.openapi.editor.InlayProperties
|
||||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
import com.intellij.openapi.editor.ex.RangeHighlighterEx
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,32 +18,28 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
|
|||||||
return editor.getUserData(KEY) ?: EditorInlayLensManager(editor).also { editor.putUserData(KEY, it) }
|
return editor.getUserData(KEY) ?: EditorInlayLensManager(editor).also { editor.putUserData(KEY, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(editor: Editor) {
|
private fun updateRenderer(renderer: LensRenderer, info: HighlightInfo) {
|
||||||
val manager = editor.getUserData(KEY)
|
renderer.text = info.description.takeIf(String::isNotBlank)?.let(::addMissingPeriod) ?: " "
|
||||||
if (manager != null) {
|
renderer.severity = LensSeverity.from(info.severity)
|
||||||
manager.hideAll()
|
}
|
||||||
editor.putUserData(KEY, null)
|
|
||||||
|
private fun addMissingPeriod(text: String): String {
|
||||||
|
return if (text.endsWith('.')) text else "$text."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getInlayHintOffset(info: HighlightInfo): Int {
|
private val inlays = mutableMapOf<RangeHighlighterEx, Inlay<LensRenderer>>()
|
||||||
// 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(highlighter: RangeHighlighterEx, info: HighlightInfo) {
|
||||||
|
|
||||||
fun show(highlighter: RangeHighlighter, info: HighlightInfo) {
|
|
||||||
val currentInlay = inlays[highlighter]
|
val currentInlay = inlays[highlighter]
|
||||||
if (currentInlay != null && currentInlay.isValid) {
|
if (currentInlay != null && currentInlay.isValid) {
|
||||||
currentInlay.renderer.setPropertiesFrom(info)
|
updateRenderer(currentInlay.renderer, info)
|
||||||
currentInlay.update()
|
currentInlay.update()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val offset = getInlayHintOffset(info)
|
val offset = info.actualEndOffset - 1
|
||||||
val renderer = LensRenderer(info)
|
val renderer = LensRenderer().also { updateRenderer(it, info) }
|
||||||
val properties = InlayProperties().relatesToPrecedingText(true).disableSoftWrapping(true).priority(-offset)
|
val properties = InlayProperties().relatesToPrecedingText(true).priority(-offset)
|
||||||
|
|
||||||
editor.inlayModel.addAfterLineEndElement(offset, properties, renderer)?.let {
|
editor.inlayModel.addAfterLineEndElement(offset, properties, renderer)?.let {
|
||||||
inlays[highlighter] = it
|
inlays[highlighter] = it
|
||||||
@ -51,12 +47,7 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hide(highlighter: RangeHighlighter) {
|
fun hide(highlighter: RangeHighlighterEx) {
|
||||||
inlays.remove(highlighter)?.dispose()
|
inlays.remove(highlighter)?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hideAll() {
|
|
||||||
inlays.values.forEach(Inlay<*>::dispose)
|
|
||||||
inlays.clear()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
package com.chylex.intellij.inspectionlens
|
|
||||||
|
|
||||||
import com.intellij.openapi.Disposable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets automatically disposed when the plugin is unloaded.
|
|
||||||
*/
|
|
||||||
class InspectionLensPluginDisposableService : Disposable {
|
|
||||||
override fun dispose() {}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package com.chylex.intellij.inspectionlens
|
|
||||||
|
|
||||||
import com.intellij.ide.plugins.DynamicPluginListener
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* On load, it installs the [LensMarkupModelListener] to all open editors.
|
|
||||||
* On unload, it removes all lenses from all open editors.
|
|
||||||
*/
|
|
||||||
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) {
|
|
||||||
if (pluginDescriptor.pluginId.idString == PLUGIN_ID) {
|
|
||||||
ProjectManager.getInstanceIfCreated()?.forEachEditor(LensMarkupModelListener.Companion::install)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun beforePluginUnload(pluginDescriptor: IdeaPluginDescriptor, isUpdate: Boolean) {
|
|
||||||
if (pluginDescriptor.pluginId.idString == PLUGIN_ID) {
|
|
||||||
ProjectManager.getInstanceIfCreated()?.forEachEditor {
|
|
||||||
EditorInlayLensManager.remove(it.editor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,7 @@
|
|||||||
package com.chylex.intellij.inspectionlens
|
package com.chylex.intellij.inspectionlens
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.ex.MarkupModelEx
|
||||||
|
import com.intellij.openapi.editor.impl.DocumentMarkupModel
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
||||||
import com.intellij.openapi.fileEditor.TextEditor
|
import com.intellij.openapi.fileEditor.TextEditor
|
||||||
@ -7,14 +9,18 @@ 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.
|
* Listens for newly opened editors, and attaches a [LensMarkupModelListener] to their document model.
|
||||||
*/
|
*/
|
||||||
class LensFileEditorListener : FileEditorManagerListener {
|
class LensFileEditorListener : FileEditorManagerListener {
|
||||||
override fun fileOpenedSync(source: FileEditorManager, file: VirtualFile, editorsWithProviders: MutableList<FileEditorWithProvider>) {
|
override fun fileOpenedSync(source: FileEditorManager, file: VirtualFile, editorsWithProviders: MutableList<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)
|
val editor = fileEditor.editor
|
||||||
|
val markupModel = DocumentMarkupModel.forDocument(editor.document, editor.project, false)
|
||||||
|
if (markupModel is MarkupModelEx) {
|
||||||
|
markupModel.addMarkupModelListener(fileEditor, LensMarkupModelListener(editor))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,16 @@
|
|||||||
package com.chylex.intellij.inspectionlens
|
package com.chylex.intellij.inspectionlens
|
||||||
|
|
||||||
import com.chylex.intellij.inspectionlens.util.MultiParentDisposable
|
|
||||||
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
|
||||||
import com.intellij.lang.annotation.HighlightSeverity
|
import com.intellij.lang.annotation.HighlightSeverity
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
|
||||||
import com.intellij.openapi.editor.Editor
|
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.ex.RangeHighlighterEx
|
||||||
import com.intellij.openapi.editor.impl.DocumentMarkupModel
|
|
||||||
import com.intellij.openapi.editor.impl.event.MarkupModelListener
|
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].
|
* Listens for inspection highlights and reports them to [EditorInlayLensManager].
|
||||||
*/
|
*/
|
||||||
class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelListener {
|
class LensMarkupModelListener(editor: Editor) : MarkupModelListener {
|
||||||
private val lens = EditorInlayLensManager.getOrCreate(editor)
|
private val lens = EditorInlayLensManager.getOrCreate(editor)
|
||||||
|
|
||||||
override fun afterAdded(highlighter: RangeHighlighterEx) {
|
override fun afterAdded(highlighter: RangeHighlighterEx) {
|
||||||
@ -31,7 +25,7 @@ class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelL
|
|||||||
lens.hide(highlighter)
|
lens.hide(highlighter)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showIfValid(highlighter: RangeHighlighter) {
|
private fun showIfValid(highlighter: RangeHighlighterEx) {
|
||||||
if (!highlighter.isValid) {
|
if (!highlighter.isValid) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -53,35 +47,9 @@ class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showIfNonNullDescription(highlighter: RangeHighlighter, info: HighlightInfo) {
|
private fun showIfNonNullDescription(highlighter: RangeHighlighterEx, info: HighlightInfo) {
|
||||||
if (info.description != null) {
|
if (info.description != null) {
|
||||||
lens.show(highlighter, info)
|
lens.show(highlighter, info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
|
|
||||||
for (highlighter in markupModel.allHighlighters) {
|
|
||||||
listener.showIfValid(highlighter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.chylex.intellij.inspectionlens
|
package com.chylex.intellij.inspectionlens
|
||||||
|
|
||||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
|
||||||
import com.intellij.codeInsight.daemon.impl.HintRenderer
|
import com.intellij.codeInsight.daemon.impl.HintRenderer
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.Inlay
|
import com.intellij.openapi.editor.Inlay
|
||||||
@ -12,44 +11,23 @@ import java.awt.Rectangle
|
|||||||
/**
|
/**
|
||||||
* Renders the text of an inspection lens.
|
* Renders the text of an inspection lens.
|
||||||
*/
|
*/
|
||||||
class LensRenderer(info: HighlightInfo) : HintRenderer(null) {
|
class LensRenderer : HintRenderer(null) {
|
||||||
private lateinit var severity: LensSeverity
|
private companion object {
|
||||||
|
private val ATTRIBUTES = TextAttributes(null, null, null, null, Font.ITALIC)
|
||||||
init {
|
|
||||||
setPropertiesFrom(info)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPropertiesFrom(info: HighlightInfo) {
|
var severity = LensSeverity.OTHER
|
||||||
text = getValidDescriptionText(info.description)
|
|
||||||
severity = LensSeverity.from(info.severity)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun paint(inlay: Inlay<*>, g: Graphics, r: Rectangle, textAttributes: TextAttributes) {
|
override fun paint(inlay: Inlay<*>, g: Graphics, r: Rectangle, textAttributes: TextAttributes) {
|
||||||
fixBaselineForTextRendering(r)
|
r.y += 1
|
||||||
super.paint(inlay, g, r, textAttributes)
|
super.paint(inlay, g, r, textAttributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTextAttributes(editor: Editor): TextAttributes {
|
override fun getTextAttributes(editor: Editor): TextAttributes {
|
||||||
return ATTRIBUTES_SINGLETON.also { it.foregroundColor = severity.getColor(editor) }
|
return ATTRIBUTES.also { it.foregroundColor = severity.getColor(editor) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun useEditorFont(): Boolean {
|
override fun useEditorFont(): Boolean {
|
||||||
return true
|
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,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) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,14 +11,6 @@
|
|||||||
|
|
||||||
<depends>com.intellij.modules.platform</depends>
|
<depends>com.intellij.modules.platform</depends>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
|
||||||
<applicationService serviceImplementation="com.chylex.intellij.inspectionlens.InspectionLensPluginDisposableService" />
|
|
||||||
</extensions>
|
|
||||||
|
|
||||||
<applicationListeners>
|
|
||||||
<listener class="com.chylex.intellij.inspectionlens.InspectionLensPluginListener" topic="com.intellij.ide.plugins.DynamicPluginListener" />
|
|
||||||
</applicationListeners>
|
|
||||||
|
|
||||||
<projectListeners>
|
<projectListeners>
|
||||||
<listener class="com.chylex.intellij.inspectionlens.LensFileEditorListener" topic="com.intellij.openapi.fileEditor.FileEditorManagerListener" />
|
<listener class="com.chylex.intellij.inspectionlens.LensFileEditorListener" topic="com.intellij.openapi.fileEditor.FileEditorManagerListener" />
|
||||||
</projectListeners>
|
</projectListeners>
|
||||||
|
Loading…
Reference in New Issue
Block a user