From 6c70af79a4b24b1f7d5ba8953a4e497854d82a23 Mon Sep 17 00:00:00 2001 From: Vojtech Balik Date: Wed, 13 Nov 2024 11:46:17 +0100 Subject: [PATCH] [declarative-inlays] IJPL-164727 support for remote clients GitOrigin-RevId: d284550a00efea6b0f1e26bb1e27328499e115b3 --- .../declarative/PresentationTreeBuilder.kt | 4 +- platform/lang-impl/api-dump-unreviewed.txt | 9 +-- .../daemon/impl/DeclarativeHintsZombie.kt | 1 + .../codeInsight/hints/InlayHintsUtils.kt | 3 +- .../impl/DeclarativeInlayActionService.kt | 6 -- .../impl/DeclarativeInlayHintsPass.kt | 31 +++++++--- .../impl/DeclarativeInlayUpdateListener.kt | 26 ++++++++ .../declarative/impl/InlayPresentationList.kt | 3 +- .../declarative/impl/InlayTreeSinkImpl.kt | 2 + .../impl/PresentationTreeBuilderImpl.kt | 2 +- .../impl/RdInlayDataExternalizer.kt | 4 +- .../DeclarativeInlayRendererBase.kt | 6 +- .../impl/util/DeclarativeInlayHintsUtil.kt | 60 +++++++++++++++++++ .../hints/declarative/impl/util/TinyTree.kt | 17 ++++++ 14 files changed, 145 insertions(+), 29 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayUpdateListener.kt create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/util/DeclarativeInlayHintsUtil.kt diff --git a/platform/lang-api/src/com/intellij/codeInsight/hints/declarative/PresentationTreeBuilder.kt b/platform/lang-api/src/com/intellij/codeInsight/hints/declarative/PresentationTreeBuilder.kt index 1895e527eb3d..37dd2f40490e 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/hints/declarative/PresentationTreeBuilder.kt +++ b/platform/lang-api/src/com/intellij/codeInsight/hints/declarative/PresentationTreeBuilder.kt @@ -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 { diff --git a/platform/lang-impl/api-dump-unreviewed.txt b/platform/lang-impl/api-dump-unreviewed.txt index 47c079ab5e9f..655243278b6e 100644 --- a/platform/lang-impl/api-dump-unreviewed.txt +++ b/platform/lang-impl/api-dump-unreviewed.txt @@ -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 -- (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 - (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 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 336b78674dad..5f7f5bcf0536 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 @@ -49,6 +49,7 @@ private object NecromancyPresentationTreeExternalizer : PresentationTreeExternal is SymbolPointerInlayActionPayload -> { writeINT(output, 2) } + else -> { /* do nothing */ } } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/InlayHintsUtils.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/InlayHintsUtils.kt index 6b96be6af556..6131bd61bfc8 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/InlayHintsUtils.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/InlayHintsUtils.kt @@ -278,7 +278,8 @@ object InlayHintsUtils { private val TEXT_METRICS_STORAGE = Key.create("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) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayActionService.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayActionService.kt index 79d09c11ec16..5468235d5e92 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayActionService.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayActionService.kt @@ -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) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayHintsPass.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayHintsPass.kt index 08b6315b65fd..5cd18fbba7a2 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayHintsPass.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayHintsPass.kt @@ -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 tryUpdateInlayAndRemoveFromDeleteList( + /** @return `true` if a suitable inlay was found and updated; `false` otherwise. */ + private inline fun tryUpdateInlayAndRemoveFromDeleteList( offsetToExistingInlays: Int2ObjectOpenHashMap>>>, inlayData: M, groupKey: Int, - require: (Inlay>) -> Boolean + require: (Inlay>) -> Boolean, + afterModelUpdate: (existingInlay: Inlay>, oldModel: List, 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.forEachRun(action: (Int, String, Int, List) -> Unit) { + private inline fun List.forEachRun( + action: (line: Int, + providerId: String, + verticalPriority: Int, + inlayData: List) -> Unit + ) { if (isEmpty()) return // run ⇔ group of inlays with the same line, provider and vertical priority // preprocessedInlayData.aboveLine are sorted by these properties diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayUpdateListener.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayUpdateListener.kt new file mode 100644 index 000000000000..0b836a08307d --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/DeclarativeInlayUpdateListener.kt @@ -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 = 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>, oldModel: List, newModel: List) +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayPresentationList.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayPresentationList.kt index 0a0c528fc30a..a473f5d699bb 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayPresentationList.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayPresentationList.kt @@ -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().invokeInlayMenu(model, e) + service().invokeInlayMenu(model, e, RelativePoint(e.mouseEvent.locationOnScreen)) } private val marginAndPadding: Pair get() = MARGIN_PADDING_BY_FORMAT[model.hintFormat.horizontalMarginPadding]!! diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayTreeSinkImpl.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayTreeSinkImpl.kt index f8ab5572b753..b348ba4ba09f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayTreeSinkImpl.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/InlayTreeSinkImpl.kt @@ -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, diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/PresentationTreeBuilderImpl.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/PresentationTreeBuilderImpl.kt index 1701414895ec..dac7354d2af7 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/PresentationTreeBuilderImpl.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/PresentationTreeBuilderImpl.kt @@ -151,4 +151,4 @@ private class InlayTreeBuildingContext(private val position: InlayPosition) { } fun isTruncateTextNodes() = position is InlineInlayPosition -} \ No newline at end of file +} 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 index b7526e1e1cca..3b5715569baf 100644 --- 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 @@ -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" \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/inlayRenderer/DeclarativeInlayRendererBase.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/inlayRenderer/DeclarativeInlayRendererBase.kt index e061be6eacaf..c96ed6643143 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/inlayRenderer/DeclarativeInlayRendererBase.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/inlayRenderer/DeclarativeInlayRendererBase.kt @@ -38,7 +38,8 @@ abstract class DeclarativeInlayRendererBase( abstract val presentationLists: List @RequiresEdt - internal fun updateModel(newModel: Model) { + @ApiStatus.Internal + fun updateModel(newModel: Model) { view.updateModel(newModel) } @@ -72,7 +73,8 @@ abstract class DeclarativeInlayRendererBase( return "DummyActionGroup" } - internal fun toInlayData(needUpToDateOffsets: Boolean = true): List { + @ApiStatus.Internal + fun toInlayData(needUpToDateOffsets: Boolean = true): List { // 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; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/util/DeclarativeInlayHintsUtil.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/util/DeclarativeInlayHintsUtil.kt new file mode 100644 index 000000000000..55a8a60b5ff8 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/util/DeclarativeInlayHintsUtil.kt @@ -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.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 + } +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/util/TinyTree.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/util/TinyTree.kt index b4b767d82bb7..1e0e1e81baf0 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/util/TinyTree.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/declarative/impl/util/TinyTree.kt @@ -77,6 +77,10 @@ class TinyTree 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 private constructor( } } + fun isSameAs( + other: TinyTree, + 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