mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[declarative-inlays] IJPL-164727 support for remote clients
GitOrigin-RevId: d284550a00efea6b0f1e26bb1e27328499e115b3
This commit is contained in:
committed by
intellij-monorepo-bot
parent
06b0f857b5
commit
6c70af79a4
+3
-1
@@ -4,6 +4,7 @@ package com.intellij.codeInsight.hints.declarative
|
||||
import com.intellij.model.Pointer
|
||||
import com.intellij.model.Symbol
|
||||
import com.intellij.psi.SmartPsiElementPointer
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* Once the tree building started, it must provide at least one text node. Otherwise, an exception will be thrown.
|
||||
@@ -58,7 +59,8 @@ class InlayActionData(val payload: InlayActionPayload, val handlerId: String) {
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface InlayActionPayload
|
||||
@ApiStatus.NonExtendable
|
||||
interface InlayActionPayload
|
||||
|
||||
class StringInlayActionPayload(val text: String) : InlayActionPayload {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
||||
@@ -3918,12 +3918,6 @@ f:com.intellij.codeInsight.hints.declarative.impl.DeclarativeInlayHintsPassFacto
|
||||
- f:getSuitableToFileProviders(com.intellij.psi.PsiFile):java.util.List
|
||||
- f:scheduleRecompute(com.intellij.openapi.editor.Editor,com.intellij.openapi.project.Project):V
|
||||
- f:updateModificationStamp(com.intellij.openapi.editor.Editor,com.intellij.psi.PsiFile):V
|
||||
f:com.intellij.codeInsight.hints.declarative.impl.InlayTreeSinkImpl
|
||||
- com.intellij.codeInsight.hints.declarative.InlayTreeSink
|
||||
- <init>(java.lang.String,java.util.Map,Z,Z,java.lang.Class,java.lang.String):V
|
||||
- addPresentation(com.intellij.codeInsight.hints.declarative.InlayPosition,java.util.List,java.lang.String,com.intellij.codeInsight.hints.declarative.HintFormat,kotlin.jvm.functions.Function1):V
|
||||
- f:finish():java.util.List
|
||||
- whenOptionEnabled(java.lang.String,kotlin.jvm.functions.Function0):V
|
||||
f:com.intellij.codeInsight.hints.declarative.impl.util.TinyTree
|
||||
- sf:Companion:com.intellij.codeInsight.hints.declarative.impl.util.TinyTree$Companion
|
||||
- <init>(B,java.lang.Object):V
|
||||
@@ -3931,9 +3925,12 @@ f:com.intellij.codeInsight.hints.declarative.impl.util.TinyTree
|
||||
- f:getBytePayload(B):B
|
||||
- f:getDataPayload(B):java.lang.Object
|
||||
- f:getSize():I
|
||||
- f:isSameAs(com.intellij.codeInsight.hints.declarative.impl.util.TinyTree,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function2):Z
|
||||
- bs:isSameAs$default(com.intellij.codeInsight.hints.declarative.impl.util.TinyTree,com.intellij.codeInsight.hints.declarative.impl.util.TinyTree,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function2,I,java.lang.Object):Z
|
||||
- f:processChildren(B,kotlin.jvm.functions.Function1):V
|
||||
- f:reverseChildren():V
|
||||
- f:setBytePayload(B,B):V
|
||||
- f:setDataPayload(java.lang.Object,B):V
|
||||
- f:syncProcessChildren(B,B,com.intellij.codeInsight.hints.declarative.impl.util.TinyTree,kotlin.jvm.functions.Function2):V
|
||||
f:com.intellij.codeInsight.hints.declarative.impl.util.TinyTree$Companion
|
||||
a:com.intellij.codeInsight.hints.declarative.impl.util.TinyTree$Externalizer
|
||||
|
||||
@@ -49,6 +49,7 @@ private object NecromancyPresentationTreeExternalizer : PresentationTreeExternal
|
||||
is SymbolPointerInlayActionPayload -> {
|
||||
writeINT(output, 2)
|
||||
}
|
||||
else -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -278,7 +278,8 @@ object InlayHintsUtils {
|
||||
|
||||
private val TEXT_METRICS_STORAGE = Key.create<InlayTextMetricsStorage>("InlayTextMetricsStorage")
|
||||
|
||||
internal fun getTextMetricStorage(editor: Editor): InlayTextMetricsStorage {
|
||||
@ApiStatus.Internal
|
||||
fun getTextMetricStorage(editor: Editor): InlayTextMetricsStorage {
|
||||
val storage = editor.getUserData(TEXT_METRICS_STORAGE)
|
||||
if (storage == null) {
|
||||
val newStorage = InlayTextMetricsStorage(editor)
|
||||
|
||||
-6
@@ -28,7 +28,6 @@ open class DeclarativeInlayActionService {
|
||||
|
||||
val inlayMenu: AnAction = ActionManager.getInstance().getAction("InlayMenu")
|
||||
val inlayMenuActionGroup = inlayMenu as ActionGroup
|
||||
//val popupMenu = ActionManager.getInstance().createActionPopupMenu("InlayMenuPopup", inlayMenuActionGroup)
|
||||
val dataContext = SimpleDataContext.builder()
|
||||
.add(CommonDataKeys.PROJECT, project)
|
||||
.add(CommonDataKeys.PSI_FILE, psiFile)
|
||||
@@ -37,11 +36,6 @@ open class DeclarativeInlayActionService {
|
||||
.add(InlayHintsProvider.PROVIDER_NAME, providerName)
|
||||
.add(InlayHintsProvider.INLAY_PAYLOADS, hintData.payloads?.associate { it.payloadName to it.payload })
|
||||
.build()
|
||||
//popupMenu.setDataContext {
|
||||
// dataContext
|
||||
//}
|
||||
//
|
||||
//JBPopupMenu.showByEvent(e.mouseEvent, popupMenu.component)
|
||||
|
||||
val popupMenu = JBPopupFactory.getInstance().createActionGroupPopup(null, inlayMenuActionGroup, dataContext, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false)
|
||||
popupMenu.show(relativePoint)
|
||||
|
||||
+22
-9
@@ -45,7 +45,8 @@ class DeclarativeInlayHintsPass(
|
||||
continue
|
||||
}
|
||||
|
||||
val sink = InlayTreeSinkImpl(providerInfo.providerId, providerInfo.optionToEnabled, isPreview, isProviderDisabled, provider.javaClass, passSourceId)
|
||||
val sink = InlayTreeSinkImpl(providerInfo.providerId, providerInfo.optionToEnabled, isPreview, isProviderDisabled,
|
||||
provider.javaClass, passSourceId)
|
||||
sinks.add(sink)
|
||||
when (val collector = createCollector(provider)) {
|
||||
is OwnBypassCollector -> ownCollectors.add(CollectionInfo(sink, collector))
|
||||
@@ -120,6 +121,7 @@ class DeclarativeInlayHintsPass(
|
||||
groupKey = { inlay -> document.getLineNumber(inlay.offset) }
|
||||
)
|
||||
val storage = InlayHintsUtils.getTextMetricStorage(editor)
|
||||
val publisher = project.messageBus.syncPublisher(DeclarativeInlayUpdateListener.TOPIC)
|
||||
|
||||
fun ensureConsistentSourceId(inlayData: InlayData) {
|
||||
if (inlayData.sourceId != sourceId) {
|
||||
@@ -132,7 +134,8 @@ class DeclarativeInlayHintsPass(
|
||||
val lineEndOffset = editor.document.getLineEndOffset(position.line)
|
||||
val updated = tryUpdateInlayAndRemoveFromDeleteList(
|
||||
offsetToExistingEolInlays, inlayData, lineEndOffset,
|
||||
require = { inlay -> inlay.renderer.providerId == inlayData.providerId }
|
||||
require = { inlay -> inlay.renderer.providerId == inlayData.providerId },
|
||||
afterModelUpdate = { inlay, oldModel, newModel -> publisher.afterModelUpdate(inlay, oldModel, listOf(newModel)) }
|
||||
)
|
||||
if (!updated) {
|
||||
val renderer = DeclarativeInlayRenderer(inlayData, storage, inlayData.providerId, sourceId)
|
||||
@@ -151,7 +154,8 @@ class DeclarativeInlayHintsPass(
|
||||
val position = inlayData.position as InlineInlayPosition
|
||||
val updated = tryUpdateInlayAndRemoveFromDeleteList(
|
||||
offsetToExistingInlineInlays, inlayData, position.offset,
|
||||
require = { inlay -> inlay.renderer.providerId == inlayData.providerId }
|
||||
require = { inlay -> inlay.renderer.providerId == inlayData.providerId && inlay.isRelatedToPrecedingText == position.relatedToPrevious },
|
||||
afterModelUpdate = { inlay, oldModel, newModel -> publisher.afterModelUpdate(inlay, oldModel, listOf(newModel)) }
|
||||
)
|
||||
if (!updated) {
|
||||
val renderer = DeclarativeInlayRenderer(inlayData, storage, inlayData.providerId, sourceId)
|
||||
@@ -165,7 +169,8 @@ class DeclarativeInlayHintsPass(
|
||||
inlayData.forEach { ensureConsistentSourceId(it) }
|
||||
val updated = tryUpdateInlayAndRemoveFromDeleteList(
|
||||
offsetToExistingBlockInlays, inlayData, line,
|
||||
require = { inlay -> inlay.renderer.providerId == providerId && inlay.properties.priority == verticalPriority }
|
||||
require = { inlay -> inlay.renderer.providerId == providerId && inlay.properties.priority == verticalPriority },
|
||||
afterModelUpdate = { inlay, oldModel, newModel -> publisher.afterModelUpdate(inlay, oldModel, newModel) }
|
||||
)
|
||||
if (!updated) {
|
||||
val renderer = DeclarativeIndentedBlockInlayRenderer(
|
||||
@@ -195,19 +200,22 @@ class DeclarativeInlayHintsPass(
|
||||
}
|
||||
}
|
||||
|
||||
private fun <M> tryUpdateInlayAndRemoveFromDeleteList(
|
||||
/** @return `true` if a suitable inlay was found and updated; `false` otherwise. */
|
||||
private inline fun <M> tryUpdateInlayAndRemoveFromDeleteList(
|
||||
offsetToExistingInlays: Int2ObjectOpenHashMap<out SmartList<out Inlay<out DeclarativeInlayRendererBase<M>>>>,
|
||||
inlayData: M,
|
||||
groupKey: Int,
|
||||
require: (Inlay<out DeclarativeInlayRendererBase<M>>) -> Boolean
|
||||
require: (Inlay<out DeclarativeInlayRendererBase<M>>) -> Boolean,
|
||||
afterModelUpdate: (existingInlay: Inlay<out DeclarativeInlayRendererBase<M>>, oldModel: List<InlayData>, newModel: M) -> Unit
|
||||
): Boolean {
|
||||
val inlays = offsetToExistingInlays.get(groupKey)
|
||||
if (inlays == null) return false
|
||||
val inlays = offsetToExistingInlays.get(groupKey) ?: return false
|
||||
val iterator = inlays.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val existingInlay = iterator.next()
|
||||
if (require(existingInlay)) {
|
||||
val oldInlayData = existingInlay.renderer.toInlayData(false)
|
||||
existingInlay.renderer.updateModel(inlayData)
|
||||
afterModelUpdate(existingInlay, oldInlayData, inlayData)
|
||||
existingInlay.update()
|
||||
iterator.remove()
|
||||
return true
|
||||
@@ -250,7 +258,12 @@ class DeclarativeInlayHintsPass(
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun List<AboveLineIndentedPositionDetail>.forEachRun(action: (Int, String, Int, List<InlayData>) -> Unit) {
|
||||
private inline fun List<AboveLineIndentedPositionDetail>.forEachRun(
|
||||
action: (line: Int,
|
||||
providerId: String,
|
||||
verticalPriority: Int,
|
||||
inlayData: List<InlayData>) -> Unit
|
||||
) {
|
||||
if (isEmpty()) return
|
||||
// run ⇔ group of inlays with the same line, provider and vertical priority
|
||||
// preprocessedInlayData.aboveLine are sorted by these properties
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.hints.declarative.impl
|
||||
|
||||
import com.intellij.codeInsight.hints.declarative.impl.inlayRenderer.DeclarativeInlayRendererBase
|
||||
import com.intellij.openapi.editor.Inlay
|
||||
import com.intellij.util.messages.Topic
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.util.EventListener
|
||||
|
||||
/**
|
||||
* This listener exists, because [com.intellij.openapi.editor.InlayModel.Listener.onUpdated]
|
||||
* might not be fired for some declarative inlay updates. (For example, it is not fired when only the tooltip changes).
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
interface DeclarativeInlayUpdateListener : EventListener {
|
||||
companion object {
|
||||
@JvmField
|
||||
@Topic.ProjectLevel
|
||||
val TOPIC: Topic<DeclarativeInlayUpdateListener> = Topic(DeclarativeInlayUpdateListener::class.java, Topic.BroadcastDirection.NONE)
|
||||
}
|
||||
|
||||
/** Both [oldModel] and [newModel] are the same as the result
|
||||
* of calling [inlay.renderer.toInlayData(needsUpToDateOffsets = false)][DeclarativeInlayRendererBase.toInlayData]
|
||||
* before and after the model update respectively. */
|
||||
fun afterModelUpdate(inlay: Inlay<out DeclarativeInlayRendererBase<*>>, oldModel: List<InlayData>, newModel: List<InlayData>)
|
||||
}
|
||||
+2
-1
@@ -15,6 +15,7 @@ import com.intellij.openapi.editor.event.EditorMouseEvent
|
||||
import com.intellij.openapi.editor.impl.EditorImpl
|
||||
import com.intellij.openapi.editor.markup.TextAttributes
|
||||
import com.intellij.ui.LightweightHint
|
||||
import com.intellij.ui.awt.RelativePoint
|
||||
import com.intellij.util.SlowOperations
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.intellij.util.containers.enumMapOf
|
||||
@@ -84,7 +85,7 @@ class InlayPresentationList(
|
||||
pointInsideInlay: Point,
|
||||
fontMetricsStorage: InlayTextMetricsStorage,
|
||||
) {
|
||||
service<DeclarativeInlayActionService>().invokeInlayMenu(model, e)
|
||||
service<DeclarativeInlayActionService>().invokeInlayMenu(model, e, RelativePoint(e.mouseEvent.locationOnScreen))
|
||||
}
|
||||
|
||||
private val marginAndPadding: Pair<Int, Int> get() = MARGIN_PADDING_BY_FORMAT[model.hintFormat.horizontalMarginPadding]!!
|
||||
|
||||
+2
@@ -4,6 +4,7 @@ package com.intellij.codeInsight.hints.declarative.impl
|
||||
import com.intellij.codeInsight.hints.declarative.*
|
||||
import com.intellij.diagnostic.PluginException
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
|
||||
/**
|
||||
@@ -13,6 +14,7 @@ import com.intellij.openapi.util.NlsContexts
|
||||
* @param enabledOptions an exhaustive set of options
|
||||
* @param providerClass used for diagnostics only
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
class InlayTreeSinkImpl(
|
||||
private val providerId: String,
|
||||
private val enabledOptions: Map<String, Boolean>,
|
||||
|
||||
+1
-1
@@ -151,4 +151,4 @@ private class InlayTreeBuildingContext(private val position: InlayPosition) {
|
||||
}
|
||||
|
||||
fun isTruncateTextNodes() = position is InlineInlayPosition
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -14,6 +14,8 @@ import java.io.DataOutput
|
||||
|
||||
@ApiStatus.Internal
|
||||
object RdInlayDataExternalizer : InlayDataExternalizer(RdPresentationTreeExternalizer) {
|
||||
const val RD_INLAY_DATA_SOURCE_ID: String = "rd.deserialized.inlay.data"
|
||||
|
||||
override fun writeProviderClass(output: DataOutput, providerClass: Class<*>) {
|
||||
// do nothing
|
||||
}
|
||||
@@ -66,5 +68,3 @@ private object RdDummyInlayProvider : InlayHintsProvider {
|
||||
error("Dummy provider must not be called")
|
||||
}
|
||||
}
|
||||
|
||||
const val RD_INLAY_DATA_SOURCE_ID: String = "rd.deserialized.inlay.data"
|
||||
+4
-2
@@ -38,7 +38,8 @@ abstract class DeclarativeInlayRendererBase<Model>(
|
||||
abstract val presentationLists: List<InlayPresentationList>
|
||||
|
||||
@RequiresEdt
|
||||
internal fun updateModel(newModel: Model) {
|
||||
@ApiStatus.Internal
|
||||
fun updateModel(newModel: Model) {
|
||||
view.updateModel(newModel)
|
||||
}
|
||||
|
||||
@@ -72,7 +73,8 @@ abstract class DeclarativeInlayRendererBase<Model>(
|
||||
return "DummyActionGroup"
|
||||
}
|
||||
|
||||
internal fun toInlayData(needUpToDateOffsets: Boolean = true): List<InlayData> {
|
||||
@ApiStatus.Internal
|
||||
fun toInlayData(needUpToDateOffsets: Boolean = true): List<InlayData> {
|
||||
// this.inlay should always be initialized right after construction.
|
||||
// However, InlayModel.Listener.onAdded will be called before that can happen,
|
||||
// and someone (e.g., rem-dev backend) might want to serialize right away;
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.hints.declarative.impl.util
|
||||
|
||||
import com.intellij.codeInsight.hints.declarative.*
|
||||
import com.intellij.codeInsight.hints.declarative.impl.ActionWithContent
|
||||
import com.intellij.openapi.editor.Inlay
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
object DeclarativeInlayHintsUtil {
|
||||
fun InlayPosition.getPlacement(): Inlay.Placement =
|
||||
when (this) {
|
||||
is AboveLineIndentedPosition -> Inlay.Placement.ABOVE_LINE
|
||||
is EndOfLinePosition -> Inlay.Placement.AFTER_LINE_END
|
||||
is InlineInlayPosition -> Inlay.Placement.INLINE
|
||||
}
|
||||
|
||||
fun InlayPosition.getRelatesToPrecedingText(): Boolean =
|
||||
when (this) {
|
||||
is AboveLineIndentedPosition -> false
|
||||
is EndOfLinePosition -> true
|
||||
is InlineInlayPosition -> relatedToPrevious
|
||||
}
|
||||
|
||||
/** @see com.intellij.openapi.editor.InlayProperties.getPriority */
|
||||
fun InlayPosition.getInlayPriority(): Int =
|
||||
when (this) {
|
||||
is AboveLineIndentedPosition -> verticalPriority
|
||||
is EndOfLinePosition -> priority
|
||||
is InlineInlayPosition -> priority
|
||||
}
|
||||
|
||||
/** Transform [InlayActionData] stored inside a declarative inlay tree node at given index */
|
||||
inline fun TinyTree<Any?>.mapInlayActionData(index: Byte, transform: (InlayActionData) -> InlayActionData) {
|
||||
val dataPayload = getDataPayload(index)
|
||||
if (dataPayload is ActionWithContent) {
|
||||
val oldActionData = dataPayload.actionData
|
||||
val newActionData = transform(oldActionData)
|
||||
if (oldActionData !== newActionData) {
|
||||
setDataPayload(
|
||||
ActionWithContent(
|
||||
newActionData,
|
||||
dataPayload.content),
|
||||
index
|
||||
)
|
||||
}
|
||||
}
|
||||
else if (dataPayload is InlayActionData) {
|
||||
val oldActionData = dataPayload
|
||||
val newActionData = transform(oldActionData)
|
||||
if (oldActionData !== newActionData) {
|
||||
setDataPayload(
|
||||
newActionData,
|
||||
index
|
||||
)
|
||||
}
|
||||
}
|
||||
// else: not a node with InlayActionData, do nothing
|
||||
}
|
||||
}
|
||||
+17
@@ -77,6 +77,10 @@ class TinyTree<T> private constructor(
|
||||
payload.set(index.toInt(), nodePayload)
|
||||
}
|
||||
|
||||
fun setDataPayload(data: T, index: Byte) {
|
||||
this.data[index.toInt()] = data
|
||||
}
|
||||
|
||||
fun processChildren(index: Byte, f: (index: Byte) -> Boolean) {
|
||||
var currentChildIndex = firstChild.getByte(index.toInt())
|
||||
while (currentChildIndex != NO_ELEMENT) {
|
||||
@@ -102,6 +106,19 @@ class TinyTree<T> private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun isSameAs(
|
||||
other: TinyTree<T>,
|
||||
isPayloadSame: (Byte, Byte) -> Boolean = { a, b -> a == b },
|
||||
isDataSame: (T, T) -> Boolean = { a, b -> a == b },
|
||||
): Boolean {
|
||||
if (size != other.size) return false
|
||||
if (firstChild != other.firstChild) return false
|
||||
if (nextChild != other.nextChild) return false
|
||||
if (!payload.indices.all { isPayloadSame(payload.getByte(it), other.payload.getByte(it)) }) return false
|
||||
if (!data.indices.all { isDataSame(data[it], other.data[it]) }) return false
|
||||
return true
|
||||
}
|
||||
|
||||
val size: Int
|
||||
get() = payload.size
|
||||
|
||||
|
||||
Reference in New Issue
Block a user