mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Inlay hints: make it possible to have block and inline element for the same offset for single provider
GitOrigin-RevId: 36a2a318279552bc5bf18449f302347a526a87ec
This commit is contained in:
committed by
intellij-monorepo-bot
parent
efcffb291a
commit
9cdf4b2e36
@@ -6,14 +6,14 @@ import com.intellij.codeInsight.hints.presentation.InlayPresentation
|
||||
interface InlayHintsSink {
|
||||
/**
|
||||
* Adds inline element to underlying editor.
|
||||
* Note, that single provider may add only one presentation to the given offset (no matter inline or block). This requirement may be relaxed in future.
|
||||
* Note, that single provider may add only one presentation to the given offset. This requirement may be relaxed in future.
|
||||
* @see [com.intellij.openapi.editor.InlayModel.addInlineElement]
|
||||
*/
|
||||
fun addInlineElement(offset: Int, relatesToPrecedingText: Boolean, presentation: InlayPresentation)
|
||||
|
||||
/**
|
||||
* Adds block element to underlying editor.
|
||||
* Note, that single provider may add only one presentation to the given offset (no matter inline or block). This requirement may be relaxed in future.
|
||||
* Note, that single provider may add only one presentation to the given offset. This requirement may be relaxed in future.
|
||||
* @see [com.intellij.openapi.editor.InlayModel.addBlockElement]
|
||||
*/
|
||||
fun addBlockElement(offset: Int, relatesToPrecedingText: Boolean, showAbove: Boolean, priority: Int, presentation: InlayPresentation)
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.intellij.codeInsight.hints
|
||||
import com.intellij.codeInsight.hints.presentation.InlayPresentation
|
||||
import com.intellij.codeInsight.hints.presentation.PresentationListener
|
||||
import com.intellij.codeInsight.hints.presentation.PresentationRenderer
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorCustomElementRenderer
|
||||
import com.intellij.openapi.editor.Inlay
|
||||
@@ -32,8 +33,10 @@ private class BlockElement(
|
||||
presentation: InlayPresentation
|
||||
) : InlayHint(offset, presentation)
|
||||
|
||||
private class HintsAtOffset(var inlineElement: InlineElement?, var blockElement: BlockElement?)
|
||||
|
||||
class InlayHintsSinkImpl<T>(val key: SettingsKey<T>) : InlayHintsSink {
|
||||
private val hints = TIntObjectHashMap<InlayHint>()
|
||||
private val hints = TIntObjectHashMap<HintsAtOffset>()
|
||||
|
||||
override fun addInlineElement(offset: Int, relatesToPrecedingText: Boolean, presentation: InlayPresentation) {
|
||||
addHint(InlineElement(offset, relatesToPrecedingText, presentation))
|
||||
@@ -48,10 +51,37 @@ class InlayHintsSinkImpl<T>(val key: SettingsKey<T>) : InlayHintsSink {
|
||||
}
|
||||
|
||||
private fun addHint(hint: InlayHint) {
|
||||
hints.put(hint.offset, hint)
|
||||
var hintsAtOffset = hints[hint.offset]
|
||||
if (hintsAtOffset == null) {
|
||||
hintsAtOffset = HintsAtOffset(null, null)
|
||||
hints.put(hint.offset, hintsAtOffset)
|
||||
}
|
||||
when (hint) {
|
||||
is InlineElement -> {
|
||||
if (hintsAtOffset.inlineElement == null) {
|
||||
hintsAtOffset.inlineElement = hint
|
||||
} else {
|
||||
logAtTheSameOffset(hint)
|
||||
}
|
||||
}
|
||||
is BlockElement -> {
|
||||
if (hintsAtOffset.blockElement == null) {
|
||||
hintsAtOffset.blockElement = hint
|
||||
} else {
|
||||
logAtTheSameOffset(hint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun logAtTheSameOffset(hint: InlayHint) {
|
||||
LOG.warn("Hint added to the same offset: ${hint.offset} ${hint.presentation}")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method called every time, when it is required to update hints even for disabled providers.
|
||||
*/
|
||||
fun applyToEditor(editor: Editor,
|
||||
existingHorizontalInlays: List<Inlay<EditorCustomElementRenderer>>,
|
||||
existingVerticalInlays: List<Inlay<EditorCustomElementRenderer>>,
|
||||
@@ -66,25 +96,35 @@ class InlayHintsSinkImpl<T>(val key: SettingsKey<T>) : InlayHintsSink {
|
||||
}
|
||||
|
||||
private fun createNewHints(inlayModel: InlayModel) {
|
||||
hints.forEachEntry { offset, hint ->
|
||||
val presentation = hint.presentation
|
||||
val presentationRenderer = PresentationRenderer(presentation)
|
||||
val inlay = when (hint) {
|
||||
is InlineElement -> inlayModel.addInlineElement(offset, hint.relatesToPrecedingText, presentationRenderer)
|
||||
is BlockElement -> inlayModel.addBlockElement(
|
||||
offset,
|
||||
hint.relatesToPrecedingText,
|
||||
hint.showAbove,
|
||||
hint.priority,
|
||||
presentationRenderer
|
||||
)
|
||||
} ?: return@forEachEntry true
|
||||
inlay.putUserData(INLAY_KEY, key)
|
||||
presentation.addListener(InlayListener(inlay))
|
||||
hints.forEachEntry { offset, hints ->
|
||||
hints.inlineElement?.let {
|
||||
createNewHint(inlayModel, it, offset)
|
||||
}
|
||||
hints.blockElement?.let {
|
||||
createNewHint(inlayModel, it, offset)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNewHint(inlayModel: InlayModel, hint: InlayHint, offset: Int) : Inlay<PresentationRenderer>? {
|
||||
val presentation = hint.presentation
|
||||
val presentationRenderer = PresentationRenderer(presentation)
|
||||
val inlay = when (hint) {
|
||||
is InlineElement -> inlayModel.addInlineElement(offset, hint.relatesToPrecedingText, presentationRenderer)
|
||||
is BlockElement -> inlayModel.addBlockElement(
|
||||
offset,
|
||||
hint.relatesToPrecedingText,
|
||||
hint.showAbove,
|
||||
hint.priority,
|
||||
presentationRenderer
|
||||
)
|
||||
} ?: return null
|
||||
inlay.putUserData(INLAY_KEY, key)
|
||||
presentation.addListener(InlayListener(inlay))
|
||||
return inlay
|
||||
}
|
||||
|
||||
class InlayListener(private val inlay: Inlay<PresentationRenderer>) : PresentationListener {
|
||||
// TODO be more accurate during invalidation (requires changes in Inlay)
|
||||
override fun contentChanged(area: Rectangle) = inlay.repaint()
|
||||
@@ -106,13 +146,18 @@ class InlayHintsSinkImpl<T>(val key: SettingsKey<T>) : InlayHintsSink {
|
||||
val inlayKey = inlay.getUserData(INLAY_KEY) as SettingsKey<*>?
|
||||
if (inlayKey != key) continue
|
||||
val offset = inlay.offset
|
||||
val newHint = hints[offset]
|
||||
if (newHint != null && newHint is InlineElement != isInline) continue
|
||||
if (newHint == null || !isEnabled) {
|
||||
val hint = when (val hintsAtOffset = hints[offset]) {
|
||||
null -> null
|
||||
else -> when {
|
||||
isInline -> hintsAtOffset.inlineElement
|
||||
else -> hintsAtOffset.blockElement
|
||||
}
|
||||
}
|
||||
if (hint == null || !isEnabled) {
|
||||
Disposer.dispose(inlay)
|
||||
}
|
||||
else {
|
||||
val newPresentation = newHint.presentation
|
||||
val newPresentation = hint.presentation
|
||||
val renderer = inlay.renderer as PresentationRenderer
|
||||
val previousPresentation = renderer.presentation
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -129,5 +174,7 @@ class InlayHintsSinkImpl<T>(val key: SettingsKey<T>) : InlayHintsSink {
|
||||
companion object {
|
||||
private val INLAY_KEY: Key<Any?> = Key.create("INLAY_KEY")
|
||||
private const val BulkChangeThreshold = 1000
|
||||
|
||||
@JvmField val LOG = logger<InlayHintsSinkImpl<*>>()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInsight.hints
|
||||
|
||||
import com.intellij.codeInsight.hints.presentation.SpacePresentation
|
||||
import com.intellij.openapi.editor.EditorCustomElementRenderer
|
||||
import com.intellij.openapi.editor.Inlay
|
||||
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
|
||||
|
||||
class InlayPassTest : LightPlatformCodeInsightFixtureTestCase() {
|
||||
private val noSettings = SettingsKey<NoSettings>("no")
|
||||
|
||||
fun testBlockAndInlineElementMayBeAtSameOffset() {
|
||||
myFixture.configureByText("file.java", "class A{ }")
|
||||
val sink = InlayHintsSinkImpl(noSettings)
|
||||
sink.addBlockElement(5, true, true, 0, SpacePresentation(0, 0))
|
||||
sink.addInlineElement(5, true, SpacePresentation(10, 10))
|
||||
val editor = myFixture.editor
|
||||
sink.applyToEditor(editor, emptyList(), emptyList(), true)
|
||||
val file = myFixture.file
|
||||
assertEquals(1, inlineElements.size)
|
||||
assertEquals(1, blockElements.size)
|
||||
assertEquals(5, inlineElements.first().offset)
|
||||
assertEquals(5, blockElements.first().offset)
|
||||
}
|
||||
|
||||
fun testTurnedOffHintsDisappear() {
|
||||
myFixture.configureByText("file.java", "class A{ }")
|
||||
val sink = InlayHintsSinkImpl(noSettings)
|
||||
sink.addBlockElement(5, true, true, 0, SpacePresentation(0, 0))
|
||||
sink.addInlineElement(5, true, SpacePresentation(10, 10))
|
||||
val editor = myFixture.editor
|
||||
sink.applyToEditor(editor, inlineElements, blockElements, true)
|
||||
sink.applyToEditor(editor, inlineElements, blockElements, false)
|
||||
assertEquals(0, inlineElements.size)
|
||||
assertEquals(0, blockElements.size)
|
||||
}
|
||||
|
||||
private val blockElements: MutableList<Inlay<EditorCustomElementRenderer>>
|
||||
get() = myFixture.editor.inlayModel.getBlockElementsInRange(0, myFixture.file.textRange.endOffset)
|
||||
|
||||
private val inlineElements: MutableList<Inlay<EditorCustomElementRenderer>>
|
||||
get() = myFixture.editor.inlayModel.getInlineElementsInRange(0, myFixture.file.textRange.endOffset)
|
||||
}
|
||||
Reference in New Issue
Block a user