From 06b0f857b59ec28ffa8d489ad61c9eab670b8c81 Mon Sep 17 00:00:00 2001 From: Vojtech Balik Date: Wed, 12 Feb 2025 23:49:57 +0100 Subject: [PATCH] [declarative-inlays] IJPL-164727 make InlayDataExternalizer abstract and repurpose it for rem-dev GitOrigin-RevId: 07e417ea3f59ca965ac3679892fd91c568703794 --- .../daemon/impl/DeclarativeHintsZombie.kt | 77 +++++++++++++-- .../hints/declarative/impl/InlayData.kt | 96 +++++++++++-------- .../impl/PresentationTreeExternalizer.kt | 51 +++------- .../impl/RdInlayDataExternalizer.kt | 70 ++++++++++++++ 4 files changed, 212 insertions(+), 82 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/RdInlayDataExternalizer.kt diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DeclarativeHintsZombie.kt b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DeclarativeHintsZombie.kt index 75b8091dd96c..336b78674dad 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DeclarativeHintsZombie.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DeclarativeHintsZombie.kt @@ -1,25 +1,90 @@ // 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.daemon.impl -import com.intellij.codeInsight.hints.declarative.impl.InlayData -import com.intellij.codeInsight.hints.declarative.impl.InlayDataExternalizer +import com.intellij.codeInsight.hints.declarative.* +import com.intellij.codeInsight.hints.declarative.impl.* +import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.impl.zombie.LimbedNecromancy import com.intellij.openapi.editor.impl.zombie.LimbedZombie -import java.io.* +import com.intellij.openapi.util.registry.Registry +import com.intellij.psi.PsiFile +import com.intellij.util.io.DataInputOutputUtil.readINT +import com.intellij.util.io.DataInputOutputUtil.writeINT +import com.intellij.util.io.IOUtil.readUTF +import com.intellij.util.io.IOUtil.writeUTF +import java.io.DataInput +import java.io.DataOutput internal class DeclarativeHintsZombie(limbs: List) : LimbedZombie(limbs) -internal object DeclarativeHintsNecromancy : LimbedNecromancy(InlayDataExternalizer.serdeVersion()) { +internal object DeclarativeHintsNecromancy + : LimbedNecromancy(NecromancyInlayDataExternalizer.serdeVersion()) { override fun buryLimb(grave: DataOutput, limb: InlayData) { - InlayDataExternalizer.save(grave, limb) + NecromancyInlayDataExternalizer.save(grave, limb) } override fun exhumeLimb(grave: DataInput): InlayData { - return InlayDataExternalizer.read(grave) + return NecromancyInlayDataExternalizer.read(grave) } override fun formZombie(limbs: List): DeclarativeHintsZombie { return DeclarativeHintsZombie(limbs) } } + +private object NecromancyPresentationTreeExternalizer : PresentationTreeExternalizer() { + private const val SERDE_VERSION = 0 + override fun serdeVersion(): Int = SERDE_VERSION + super.serdeVersion() + + override fun writeInlayActionPayload(output: DataOutput, actionPayload: InlayActionPayload) { + when(actionPayload) { + is StringInlayActionPayload -> { + writeINT(output, 0) + writeUTF(output, actionPayload.text) + } + is PsiPointerInlayActionPayload -> { + writeINT(output, 1) + } + is SymbolPointerInlayActionPayload -> { + writeINT(output, 2) + } + } + } + + override fun readInlayActionPayload(input: DataInput): InlayActionPayload { + val type = readINT(input) + return when (type) { + 0 -> StringInlayActionPayload(readUTF(input)) + 1 -> PsiPointerInlayActionPayload(ZombieSmartPointer()) + 2 -> SymbolPointerInlayActionPayload(ZombieSymbolPointer()) + else -> throw IllegalStateException("unknown inlay action payload type: $type") + } + } + + override fun decorateContent(content: String): String = decorateIfDebug(content) + + private fun decorateIfDebug(content: String): String { + return if (Registry.`is`("cache.markup.debug")) { + "$content?" + } else { + content + } + } +} + +private object NecromancyInlayDataExternalizer : InlayDataExternalizer(NecromancyPresentationTreeExternalizer) { + private const val SERDE_VERSION = 0 + override fun serdeVersion(): Int = SERDE_VERSION + super.serdeVersion() + + override fun writeProviderClass(output: DataOutput, providerClass: Class<*>) {} + override fun readProviderClass(input: DataInput): Class<*> = ZombieInlayHintsProvider::class.java +} + + +private class ZombieInlayHintsProvider : InlayHintsProvider { + override fun createCollector(file: PsiFile, editor: Editor): InlayHintsCollector? { + throw UnsupportedOperationException("Zombie provider does not support inlay collecting") + } +} + diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayData.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayData.kt index 8b67b9c354d2..3e5e5afe4b56 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayData.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayData.kt @@ -82,13 +82,16 @@ data class InlayData( } } -internal object InlayDataExternalizer : DataExternalizer { - // increment on format changed - private const val SERDE_VERSION = 8 +@ApiStatus.Internal +abstract class InlayDataExternalizer( + private val treeExternalizer: PresentationTreeExternalizer +) : DataExternalizer { + companion object { + // increment on format changed + private const val SERDE_VERSION = 9 + } - private val treeExternalizer: PresentationTreeExternalizer = PresentationTreeExternalizer - - fun serdeVersion(): Int = SERDE_VERSION + treeExternalizer.serdeVersion() + open fun serdeVersion(): Int = SERDE_VERSION + treeExternalizer.serdeVersion() override fun save(output: DataOutput, inlayData: InlayData) { writePosition(output, inlayData.position) @@ -96,8 +99,9 @@ internal object InlayDataExternalizer : DataExternalizer { writeHintFormat(output, inlayData.hintFormat) treeExternalizer.save(output, inlayData.tree) writeUTF(output, inlayData.providerId) - output.writeBoolean(inlayData.disabled) + writeDisabled(output, inlayData.disabled) writePayloads(output, inlayData.payloads) + writeProviderClass(output, inlayData.providerClass) writeSourceId(output, inlayData.sourceId) } @@ -107,9 +111,9 @@ internal object InlayDataExternalizer : DataExternalizer { val hintFormat: HintFormat = readHintFormat(input) val tree: TinyTree = treeExternalizer.read(input) val providerId: String = readUTF(input) - val disabled: Boolean = input.readBoolean() + val disabled: Boolean = readDisabled(input) val payloads: List? = readPayloads(input) - val providerClass: Class<*> = ZombieInlayHintsProvider::class.java + val providerClass: Class<*> = readProviderClass(input) val sourceId: String = readSourceId(input) return InlayData(position, tooltip, hintFormat, tree, providerId, disabled, payloads, providerClass, sourceId) } @@ -138,28 +142,33 @@ internal object InlayDataExternalizer : DataExternalizer { private fun readPosition(input: DataInput): InlayPosition { val type = readINT(input) - if (type == 0) { - val offset = readINT(input) - val related = input.readBoolean() - val priority = readINT(input) - return InlineInlayPosition(offset, related, priority) - } else if (type == 1) { - val line = readINT(input) - val priority = readINT(input) - return EndOfLinePosition(line, priority) - } else if (type == 2) { - val offset = readINT(input) - val verticalPriority = readINT(input) - val priority = readINT(input) - return AboveLineIndentedPosition(offset, verticalPriority, priority) + when (type) { + 0 -> { + val offset = readINT(input) + val related = input.readBoolean() + val priority = readINT(input) + return InlineInlayPosition(offset, related, priority) + } + 1 -> { + val line = readINT(input) + val priority = readINT(input) + return EndOfLinePosition(line, priority) + } + 2 -> { + val offset = readINT(input) + val verticalPriority = readINT(input) + val priority = readINT(input) + return AboveLineIndentedPosition(offset, verticalPriority, priority) + } + else -> throw IllegalStateException("unknown inlay position type: $type") } - throw IllegalStateException("unknown inlay position type: $type") } private fun writeTooltip(output: DataOutput, tooltip: String?) { if (tooltip == null) { output.writeBoolean(false) - } else { + } + else { output.writeBoolean(true) writeUTF(output, tooltip) } @@ -168,7 +177,8 @@ internal object InlayDataExternalizer : DataExternalizer { private fun readTooltip(input: DataInput): String? { return if (input.readBoolean()) { readUTF(input) - } else { + } + else { null } } @@ -180,16 +190,17 @@ internal object InlayDataExternalizer : DataExternalizer { } private fun readHintFormat(input: DataInput): HintFormat { - val hintColorKind= HintColorKind.valueOf(readUTF(input)) - val hintFontSize= HintFontSize.valueOf(readUTF(input)) - val padding= HintMarginPadding.valueOf(readUTF(input)) + val hintColorKind = HintColorKind.valueOf(readUTF(input)) + val hintFontSize = HintFontSize.valueOf(readUTF(input)) + val padding = HintMarginPadding.valueOf(readUTF(input)) return HintFormat(hintColorKind, hintFontSize, padding) } - private fun writePayloads(output: DataOutput, payloads: List?) { + open fun writePayloads(output: DataOutput, payloads: List?) { if (payloads == null) { output.writeBoolean(false) - } else { + } + else { output.writeBoolean(true) writeINT(output, payloads.size) for (p in payloads) { @@ -198,7 +209,7 @@ internal object InlayDataExternalizer : DataExternalizer { } } - private fun readPayloads(input: DataInput): List? { + open fun readPayloads(input: DataInput): List? { if (input.readBoolean()) { val payloadCount = readINT(input) val payloads = ArrayList(payloadCount) @@ -207,7 +218,8 @@ internal object InlayDataExternalizer : DataExternalizer { payloads.add(inlayPayload) } return payloads - } else { + } + else { return null } } @@ -223,17 +235,23 @@ internal object InlayDataExternalizer : DataExternalizer { return InlayPayload(payloadName, inlayActionPayload) } - private fun writeSourceId(output: DataOutput, sourceId: String) { + open fun writeSourceId(output: DataOutput, sourceId: String) { writeUTF(output, sourceId) } - private fun readSourceId(input: DataInput): String { + open fun readSourceId(input: DataInput): String { return readUTF(input) } -} -private class ZombieInlayHintsProvider : InlayHintsProvider { - override fun createCollector(file: PsiFile, editor: Editor): InlayHintsCollector? { - throw UnsupportedOperationException("Zombie provider does not support inlay collecting") + abstract fun writeProviderClass(output: DataOutput, providerClass: Class<*>) + + abstract fun readProviderClass(input: DataInput): Class<*> + + fun writeDisabled(output: DataOutput, disabled: Boolean) { + output.writeBoolean(disabled) + } + + fun readDisabled(input: DataInput): Boolean { + return input.readBoolean() } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/PresentationTreeExternalizer.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/PresentationTreeExternalizer.kt index a4f3aa4020b5..7a67b59aab56 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/PresentationTreeExternalizer.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/PresentationTreeExternalizer.kt @@ -8,13 +8,17 @@ import com.intellij.util.io.DataInputOutputUtil.readINT import com.intellij.util.io.DataInputOutputUtil.writeINT import com.intellij.util.io.IOUtil.readUTF import com.intellij.util.io.IOUtil.writeUTF +import org.jetbrains.annotations.ApiStatus import java.io.DataInput import java.io.DataOutput -internal object PresentationTreeExternalizer : TinyTree.Externalizer() { +@ApiStatus.Internal +abstract class PresentationTreeExternalizer : TinyTree.Externalizer() { - // increment on format changed - private const val SERDE_VERSION = 1 + companion object { + // increment on format changed + private const val SERDE_VERSION = 1 + } override fun serdeVersion(): Int = SERDE_VERSION + super.serdeVersion() @@ -42,10 +46,10 @@ internal object PresentationTreeExternalizer : TinyTree.Externalizer() { when (type) { 0 -> { val string = readNullableString(input) ?: return null - return decorateIfDebug(string) + return decorateContent(string) } 1 -> { - val content = decorateIfDebug(readUTF(input)) + val content = decorateContent(readUTF(input)) return ActionWithContent(readInlayActionData(input), content) } 2 -> { @@ -72,47 +76,20 @@ internal object PresentationTreeExternalizer : TinyTree.Externalizer() { } } - private fun writeInlayActionData(output: DataOutput, inlayActionData: InlayActionData) { + open fun writeInlayActionData(output: DataOutput, inlayActionData: InlayActionData) { writeUTF(output, inlayActionData.handlerId) writeInlayActionPayload(output, inlayActionData.payload) } - private fun readInlayActionData(input: DataInput): InlayActionData { + open fun readInlayActionData(input: DataInput): InlayActionData { val handlerId = readUTF(input) val payload = readInlayActionPayload(input) return InlayActionData(payload, handlerId) } - fun writeInlayActionPayload(output: DataOutput, actionPayload: InlayActionPayload) { - when(actionPayload) { - is StringInlayActionPayload -> { - writeINT(output, 0) - writeUTF(output, actionPayload.text) - } - is PsiPointerInlayActionPayload -> { - writeINT(output, 1) - } - is SymbolPointerInlayActionPayload -> { - writeINT(output, 2) - } - } - } + abstract fun writeInlayActionPayload(output: DataOutput, actionPayload: InlayActionPayload) - fun readInlayActionPayload(input: DataInput): InlayActionPayload { - val type = readINT(input) - return when (type) { - 0 -> StringInlayActionPayload(readUTF(input)) - 1 -> PsiPointerInlayActionPayload(ZombieSmartPointer()) - 2 -> SymbolPointerInlayActionPayload(ZombieSymbolPointer()) - else -> throw IllegalStateException("unknown inlay action payload type: $type") - } - } + abstract fun readInlayActionPayload(input: DataInput): InlayActionPayload - private fun decorateIfDebug(content: String): String { - return if (Registry.`is`("cache.markup.debug")) { - "$content?" - } else { - content - } - } + protected open fun decorateContent(content: String): String = content } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/RdInlayDataExternalizer.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/RdInlayDataExternalizer.kt new file mode 100644 index 000000000000..b7526e1e1cca --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/RdInlayDataExternalizer.kt @@ -0,0 +1,70 @@ +// Copyright 2000-2025 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.InlayActionData +import com.intellij.codeInsight.hints.declarative.InlayActionPayload +import com.intellij.codeInsight.hints.declarative.InlayHintsCollector +import com.intellij.codeInsight.hints.declarative.InlayHintsProvider +import com.intellij.codeInsight.hints.declarative.InlayPayload +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiFile +import org.jetbrains.annotations.ApiStatus +import java.io.DataInput +import java.io.DataOutput + +@ApiStatus.Internal +object RdInlayDataExternalizer : InlayDataExternalizer(RdPresentationTreeExternalizer) { + override fun writeProviderClass(output: DataOutput, providerClass: Class<*>) { + // do nothing + } + + override fun readProviderClass(input: DataInput): Class<*> { + return RdDummyInlayProvider::class.java + } + + override fun writeSourceId(output: DataOutput, sourceId: String) { + // do nothing + } + + override fun readSourceId(input: DataInput): String { + return RD_INLAY_DATA_SOURCE_ID + } + + override fun writePayloads(output: DataOutput, payloads: List?) { + // do nothing + } + + override fun readPayloads(input: DataInput): List? { + return null + } +} + +private object RdPresentationTreeExternalizer : PresentationTreeExternalizer() { + override fun writeInlayActionPayload(output: DataOutput, actionPayload: InlayActionPayload) { + error("InlayActionPayload should not be sent to frontend") + } + + override fun readInlayActionPayload(input: DataInput): InlayActionPayload { + error("InlayActionPayload should not be read on frontend") + } + + override fun readInlayActionData(input: DataInput): InlayActionData { + return INLAY_ACTION_DATA_PLACEHOLDER + } + + override fun writeInlayActionData(output: DataOutput, inlayActionData: InlayActionData) { + // do nothing + } +} + +private object RdInlayActionPayloadPlaceholder : InlayActionPayload + +private val INLAY_ACTION_DATA_PLACEHOLDER = InlayActionData(RdInlayActionPayloadPlaceholder, "rd.placeholder.handler.id") + +private object RdDummyInlayProvider : InlayHintsProvider { + override fun createCollector(file: PsiFile, editor: Editor): InlayHintsCollector? { + error("Dummy provider must not be called") + } +} + +const val RD_INLAY_DATA_SOURCE_ID: String = "rd.deserialized.inlay.data" \ No newline at end of file