[editor] IJPL-54 Replace AdDocumentEntity with fleet common DocumentEntity

GitOrigin-RevId: 8dfd8e39a86702c59f9dc514dbbbea2a7729201c
This commit is contained in:
Alexandr Trushev
2025-02-22 11:44:55 +00:00
committed by intellij-monorepo-bot
parent f821e02859
commit 439fbffe94
20 changed files with 90 additions and 1520 deletions
@@ -17,5 +17,6 @@
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.util.coroutines" />
<orderEntry type="module" module-name="intellij.platform.kernel" />
<orderEntry type="module" module-name="intellij.platform.pasta" />
</component>
</module>
@@ -4,14 +4,15 @@ package com.intellij.platform.editor.backend
import com.intellij.codeWithMe.clientId
import com.intellij.openapi.application.isRhizomeAdEnabled
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.impl.ad.AdDocumentEntity
import com.intellij.openapi.editor.impl.editorId
import com.intellij.platform.editor.EditorEntity
import com.intellij.platform.kernel.KernelService
import com.intellij.platform.kernel.util.kernelCoroutineContext
import com.intellij.platform.pasta.common.DocumentEntity
import com.intellij.platform.util.coroutines.childScope
import fleet.kernel.change
import fleet.kernel.shared
import fleet.util.UID
import kotlinx.coroutines.*
internal class BackendEditorState(
@@ -32,7 +33,7 @@ internal class BackendEditorState(
val clientId = requireNotNull(currentCoroutineContext().clientId())
val editorEntity = change {
shared {
val document = AdDocumentEntity.fromString(editor.document.text, editor.document.modificationStamp)
val document = DocumentEntity.fromText(UID.random(), editor.document.text)
EditorEntity.new {
it[EditorEntity.idAttr] = editor.editorId()
it[EditorEntity.clientIdAttr] = clientId
+3 -3
View File
@@ -3,7 +3,7 @@ package com.intellij.platform.editor
import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.editor.impl.EditorId
import com.intellij.openapi.editor.impl.ad.AdDocumentEntity
import com.intellij.platform.pasta.common.DocumentEntity
import com.jetbrains.rhizomedb.EID
import com.jetbrains.rhizomedb.Entity
import com.jetbrains.rhizomedb.Indexing
@@ -16,7 +16,7 @@ import org.jetbrains.annotations.ApiStatus.Internal
@Internal
data class EditorEntity(override val eid: EID) : Entity {
val document: AdDocumentEntity by documentAttr
val document: DocumentEntity by documentAttr
val id: EditorId by idAttr
val clientId: ClientId by clientIdAttr
@@ -27,7 +27,7 @@ data class EditorEntity(override val eid: EID) : Entity {
) {
val idAttr: Required<EditorId> = requiredValue("id", EditorId.serializer(), Indexing.UNIQUE)
val documentAttr: Required<AdDocumentEntity> = requiredRef("document", RefFlags.CASCADE_DELETE_BY)
val documentAttr: Required<DocumentEntity> = requiredRef("document", RefFlags.CASCADE_DELETE_BY)
val clientIdAttr: Required<ClientId> = requiredValue("clientId", ClientIdSerializer)
}
}
@@ -2,10 +2,9 @@
package com.intellij.platform.editor
import com.intellij.openapi.application.isRhizomeAdEnabled
import com.intellij.openapi.editor.impl.ad.AdDocumentEntity
import com.intellij.platform.kernel.EntityTypeProvider
import com.intellij.platform.pasta.common.DocumentEntity
import com.intellij.platform.pasta.common.EditLogEntity
import com.intellij.platform.kernel.EntityTypeProvider
import com.jetbrains.rhizomedb.EntityType
internal class EditorEntityTypeProvider : EntityTypeProvider {
@@ -13,7 +12,6 @@ internal class EditorEntityTypeProvider : EntityTypeProvider {
override fun entityTypes(): List<EntityType<*>> {
if (!isRhizomeAdEnabled) return emptyList()
return listOf(
AdDocumentEntity,
EditorEntity,
DocumentEntity,
EditLogEntity,
@@ -40,6 +40,16 @@ class DocumentEntity(override val eid: EID) : Entity {
val WritableAttr: Required<Boolean> = requiredValue("writable", Boolean.serializer())
val EditLogAttr: Required<EditLogEntity> = requiredRef<EditLogEntity>("editLogEntity", RefFlags.UNIQUE)
val SharedAnchorStorageAttr: Required<AnchorStorage> = requiredValue("sharedAnchorStorage", AnchorStorage.serializer())
fun fromText(uid: UID, text: CharSequence): DocumentEntity = requireChangeScope {
DocumentEntity.new {
it[Durable.Id] = uid
it[TextAttr] = Text.fromString(text.toString())
it[WritableAttr] = true
it[EditLogAttr] = createEmptyEditLog()
it[SharedAnchorStorageAttr] = AnchorStorage.empty()
}
}
}
val timestamp: Long
@@ -1,19 +0,0 @@
// 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.openapi.editor.ex;
import com.intellij.openapi.editor.impl.ad.AdTheManagerImpl;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ApiStatus.Experimental
@ApiStatus.Internal
public interface AdTheManager {
static @NotNull AdTheManager getInstance(@NotNull Project project) {
return project.getService(AdTheManagerImpl.class);
}
@Nullable EditorModel createEditorModel(@NotNull EditorEx editor);
}
@@ -21,7 +21,7 @@ import com.intellij.openapi.editor.event.EditorFactoryListener
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.highlighter.EditorHighlighter
import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory
import com.intellij.openapi.editor.impl.ad.v2.AdTheManagerV2
import com.intellij.openapi.editor.impl.ad.AdTheManager
import com.intellij.openapi.editor.impl.event.EditorEventMulticasterImpl
import com.intellij.openapi.editor.impl.view.EditorPainter
import com.intellij.openapi.extensions.ExtensionPointName
@@ -209,7 +209,7 @@ class EditorFactoryImpl(coroutineScope: CoroutineScope?) : EditorFactory() {
highlighter: EditorHighlighter?,
afterCreation: ((EditorImpl) -> Unit)?,
): EditorImpl {
AdTheManagerV2.getInstance().bindLocalDocEntity(document)
AdTheManager.getInstance().bindLocalDocEntity(document)
val editor = EditorImpl(document, isViewer, project, kind, file, highlighter)
editor.putEditorId()
// must be _before_ event firing
@@ -37,7 +37,7 @@ import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.HighlighterClient;
import com.intellij.openapi.editor.impl.ad.v2.AdTheManagerV2;
import com.intellij.openapi.editor.impl.ad.AdTheManager;
import com.intellij.openapi.editor.impl.event.MarkupModelListener;
import com.intellij.openapi.editor.impl.stickyLines.StickyLinesManager;
import com.intellij.openapi.editor.impl.stickyLines.StickyLinesModel;
@@ -342,7 +342,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
final EditorView myView;
final @Nullable EditorView myAdView;
private static final boolean AD_V2 = Registry.is("ijpl.rhizome.ad.v2.enabled", false);
private boolean myCharKeyPressed;
private boolean myNeedToSelectPreviousChar;
@@ -479,27 +478,22 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
myEditorModel = new EditorModelImpl(this);
if (AD_V2) {
DocumentEx adDocumentV2 = AdTheManagerV2.getInstance().getAdDocument(myDocument);
myAdEditorModel = adDocumentV2 == null ? null : new EditorModel() {
@Override public DocumentEx getDocument() { return adDocumentV2; }
@Override public MarkupModelEx getEditorMarkupModel() { return myMarkupModel; }
@Override public MarkupModelEx getDocumentMarkupModel() { return myEditorFilteringMarkupModel; }
@Override public EditorHighlighter getHighlighter() { return myHighlighter; }
@Override public InlayModelEx getInlayModel() { return myInlayModel; }
@Override public FoldingModelEx getFoldingModel() { return myFoldingModel; }
@Override public SoftWrapModelEx getSoftWrapModel() { return mySoftWrapModel; }
@Override public CaretModel getCaretModel() { return myCaretModel; }
@Override public SelectionModel getSelectionModel() { return mySelectionModel; }
@Override public ScrollingModel getScrollingModel() { return myScrollingModel; }
@Override public FocusModeModel getFocusModel() { return myFocusModeModel; }
@Override public boolean isAd() { return true; }
@Override public void dispose() { AdTheManagerV2.getInstance().releaseDocEntity(myDocument); }
};
}
else {
myAdEditorModel = project == null ? null : AdTheManager.getInstance(project).createEditorModel(this);
}
DocumentEx adDocument = AdTheManager.getInstance().getAdDocument(myDocument);
myAdEditorModel = adDocument == null ? null : new EditorModel() {
@Override public DocumentEx getDocument() { return adDocument; }
@Override public MarkupModelEx getEditorMarkupModel() { return myMarkupModel; }
@Override public MarkupModelEx getDocumentMarkupModel() { return myEditorFilteringMarkupModel; }
@Override public EditorHighlighter getHighlighter() { return myHighlighter; }
@Override public InlayModelEx getInlayModel() { return myInlayModel; }
@Override public FoldingModelEx getFoldingModel() { return myFoldingModel; }
@Override public SoftWrapModelEx getSoftWrapModel() { return mySoftWrapModel; }
@Override public CaretModel getCaretModel() { return myCaretModel; }
@Override public SelectionModel getSelectionModel() { return mySelectionModel; }
@Override public ScrollingModel getScrollingModel() { return myScrollingModel; }
@Override public FocusModeModel getFocusModel() { return myFocusModeModel; }
@Override public boolean isAd() { return true; }
@Override public void dispose() { AdTheManager.getInstance().releaseDocEntity(myDocument); }
};
myView = new EditorView(this, myEditorModel);
myAdView = myAdEditorModel == null ? null : new EditorView(this, myAdEditorModel);
@@ -532,8 +526,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
addListeners();
if (AD_V2 && myAdEditorModel != null) {
AdTheManagerV2.getInstance().bindEditor(this);
if (myAdEditorModel != null) {
AdTheManager.getInstance().bindEditor(this);
}
}
@@ -1188,9 +1182,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
Disposer.dispose(myView);
if (myAdView != null) {
Disposer.dispose(myAdView);
if (AD_V2) {
Disposer.dispose(myAdEditorModel);
}
Disposer.dispose(myAdEditorModel);
}
clearCaretThread();
@@ -1,11 +1,6 @@
// 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.openapi.editor.impl.ad
import andel.editor.RangeMarkerId
import andel.intervals.Interval
import andel.operation.Operation
import andel.operation.captureOperation
import andel.operation.rebase
import andel.text.*
import com.intellij.openapi.Disposable
import com.intellij.openapi.editor.RangeMarker
@@ -14,65 +9,16 @@ import com.intellij.openapi.editor.ex.DocumentEx
import com.intellij.openapi.editor.ex.EditReadOnlyListener
import com.intellij.openapi.editor.ex.LineIterator
import com.intellij.openapi.editor.ex.RangeMarkerEx
import com.intellij.platform.pasta.common.DocumentEntity
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.TextRange
import com.intellij.util.Processor
import com.intellij.util.concurrency.ThreadingAssertions
import com.jetbrains.rhizomedb.ChangeScope
import com.jetbrains.rhizomedb.DbContext
import com.jetbrains.rhizomedb.asOf
import com.jetbrains.rhizomedb.requireChangeScope
import fleet.util.UID
import org.jetbrains.annotations.ApiStatus.Experimental
import java.beans.PropertyChangeListener
import java.util.*
@Experimental
internal class AdDocument(
private val name: String,
private val entity: AdDocumentEntity,
) : DocumentEx {
internal fun replaceString(startOffset: Int, endOffset: Int, chars: CharSequence, modStamp: Long) {
performTextOperation(modStamp) {
textView().replaceOperation(startOffset, endOffset, chars.toString())
}
}
internal fun queryRangeMarkers(startOffset: Long, endOffset: Long): Sequence<Interval<UID, Unit>> {
return documentRead {
entity.anchorStorage.intervals.query(startOffset, endOffset)
}
}
internal fun addRangeMarkers(rangeIds: List<RangeMarkerId>, ranges: List<andel.text.TextRange>) {
documentChange {
entity[AdDocumentEntity.AnchorStorageAttr] = entity.anchorStorage.batchUpdate(
emptyList(),
LongArray(0),
rangeIds,
ranges, // TODO: greedy
)
}
}
internal fun addRangeMarker(rangeId: RangeMarkerId, startOffset: Long, endOffset: Long) {
documentChange {
entity[AdDocumentEntity.AnchorStorageAttr] = entity.anchorStorage.addRangeMarker(
rangeId,
startOffset,
endOffset,
false, // TODO: greedy
false,
)
}
}
internal fun removeRangeMarker(rangeId: RangeMarkerId) {
documentChange {
entity[AdDocumentEntity.AnchorStorageAttr] = entity.anchorStorage.removeRangeMarker(rangeId)
}
}
internal class AdDocument(private val entity: DocumentEntity) : DocumentEx {
// region TEXT
@@ -214,7 +160,7 @@ internal class AdDocument(
override fun getModificationStamp(): Long {
return documentRead {
entity.modStamp
entity.editLog.timestamp
}
}
@@ -361,50 +307,8 @@ internal class AdDocument(
return text().view()
}
private fun textMutableView(): MutableTextView {
return text().mutableView()
}
private fun <T> documentRead(block: () -> T): T {
if (DbContext.threadLocal.get() != null) {
return block()
}
ThreadingAssertions.assertBackgroundThread() // DB always exists w/i EDT
// TODO: lastKnownOrPendingDb
return asOf(ThreadLocalRhizomeDB.lastKnownDb()) {
block()
}
}
private fun <T> documentChange(block: ChangeScope.() -> T): T {
return requireChangeScope {
block()
}
}
private fun performTextOperation(modStamp: Long, operationLambda: ChangeScope.() -> Operation) {
documentChange {
val operationId = UID.random()
val textBefore = entity.text
val doc = entity.asAndelDocument()
val versionedOperation = doc.captureOperation(operationLambda())
val operation = versionedOperation.rebase(doc)
val textAfter = textMutableView().apply { edit(operation) }.text()
val editLogAfter = entity.editLog.append(operationId, operation).trim()
val anchorStorageAfter = entity.anchorStorage.edit(textBefore, textAfter, operation)
entity[AdDocumentEntity.TextAttr] = textAfter
entity[AdDocumentEntity.ModStampAttr] = modStamp
entity[AdDocumentEntity.EditLogAttr] = editLogAfter
entity[AdDocumentEntity.AnchorStorageAttr] = anchorStorageAfter
}
}
override fun toString(): String {
return name
return block()
}
//endregion
@@ -1,58 +0,0 @@
// 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.openapi.editor.impl.ad
import andel.editor.AnchorId
import andel.editor.RangeMarkerId
import andel.intervals.AnchorStorage
import andel.operation.EditLog
import andel.text.Text
import andel.text.TextRange
import com.jetbrains.rhizomedb.Attributes
import com.jetbrains.rhizomedb.EID
import com.jetbrains.rhizomedb.Entity
import com.jetbrains.rhizomedb.requireChangeScope
import fleet.kernel.DurableEntityType
import kotlinx.serialization.builtins.serializer
import org.jetbrains.annotations.ApiStatus.Experimental
import org.jetbrains.annotations.ApiStatus.Internal
private typealias Att<T> = Attributes<AdDocumentEntity>.Required<T>
@Experimental
@Internal
class AdDocumentEntity(override val eid: EID) : Entity {
val text: Text by TextAttr
val modStamp: Long by ModStampAttr
val editLog: EditLog by EditLogAttr
val anchorStorage: AnchorStorage by AnchorStorageAttr
companion object : DurableEntityType<AdDocumentEntity>(AdDocumentEntity::class, ::AdDocumentEntity) {
val TextAttr: Att<Text> = requiredValue("text", Text.serializer())
val ModStampAttr: Att<Long> = requiredValue("modStamp", Long.serializer())
val EditLogAttr: Att<EditLog> = requiredValue("editLog", EditLog.serializer())
val AnchorStorageAttr: Att<AnchorStorage> = requiredValue("anchorStorage", AnchorStorage.serializer())
fun fromString(text: String, modStamp: Long): AdDocumentEntity = requireChangeScope {
AdDocumentEntity.new {
it[TextAttr] = Text.fromString(text)
it[ModStampAttr] = modStamp
it[EditLogAttr] = EditLog.empty()
it[AnchorStorageAttr] = AnchorStorage.empty()
}
}
}
fun asAndelDocument(): andel.editor.Document {
return object : andel.editor.Document {
override val text: Text get() = this@AdDocumentEntity.text
override val timestamp: Long = editLog.timestamp
override val edits: EditLog get() = editLog
override fun resolveAnchor(anchorId: AnchorId): Long? = anchorStorage.resolveAnchor(anchorId)
override fun resolveRangeMarker(markerId: RangeMarkerId): TextRange? = anchorStorage.resolveRangeMarker(markerId)
}
}
override fun toString(): String {
return "AdDocumentEntity(eid=$eid, modStamp=$modStamp, text=$text)"
}
}
@@ -1,62 +1,61 @@
// 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.openapi.editor.impl.ad
import com.intellij.openapi.Disposable
import andel.operation.Operation
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.ex.PrioritizedDocumentListener
import com.intellij.openapi.util.registry.Registry
import com.intellij.platform.pasta.common.DocumentEntity
import fleet.kernel.awaitCommitted
import fleet.kernel.change
import fleet.kernel.shared
import fleet.util.UID
import fleet.util.openmap.OpenMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.jetbrains.annotations.ApiStatus.Experimental
@Experimental
internal class AdDocumentSynchronizer(
private val adDocument: AdDocument,
internal data class AdDocumentSynchronizer(
private val documentId: Any, // RdDocumentId
private val entityId: UID,
private val coroutineScope: CoroutineScope,
private val repaintLambda: suspend () -> Unit,
) : PrioritizedDocumentListener, Disposable.Default {
private val entityDeferred: Deferred<DocumentEntity>,
): PrioritizedDocumentListener {
override fun getPriority(): Int {
return Integer.MIN_VALUE + 1
}
override fun getPriority(): Int = Int.MIN_VALUE + 1
override fun documentChanged(event: DocumentEvent) {
val realChars = event.document.immutableCharSequence
coroutineScope.launch {
if (isDebugMode()) {
delay(500)
}
change {
shared {
adDocument.replaceString(
startOffset = event.offset,
endOffset = event.offset + event.oldLength,
chars = event.newFragment,
modStamp = event.document.modificationStamp,
)
val entityChange = coroutineScope.async {
val operation = operation(event)
val entity = entityDeferred.await()
change { // it is a shared change
entity.mutate(this, OpenMap.empty()) {
edit(operation)
}
}
val adChars = adDocument.immutableCharSequence
if (realChars.hashCode() != adChars.hashCode()) {
assert(realChars.toString() == adChars.toString()) {
"""
AdDocument is out of sync, expected chars:
$realChars
but encountered:
$adChars
""".trimIndent()
}
}
repaintLambda.invoke()
awaitCommitted()
}
// TODO: pumping events in runWithModalProgressBlocking ruins the models
//@Suppress("HardCodedStringLiteral")
//runWithModalProgressBlocking(
// ModalTaskOwner.guess(),
// "Shared document entity synchronization $entityId",
//) { entityChange.await() }
runBlocking { entityChange.await() }
ThreadLocalRhizomeDB.setThreadLocalDb(ThreadLocalRhizomeDB.lastKnownDb())
}
private fun isDebugMode(): Boolean {
return Registry.`is`("ijpl.rhizome.ad.debug.enabled", false)
private fun operation(event: DocumentEvent): Operation {
val oldFragment = event.oldFragment.toString()
val newFragment = event.newFragment.toString()
val lengthBefore = event.document.textLength - newFragment.length + oldFragment.length
return Operation.Companion.replaceAt(
offset = event.offset.toLong(),
oldText = oldFragment,
newText = newFragment,
totalLength = lengthBefore.toLong(),
)
}
}
@@ -1,276 +0,0 @@
// 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.openapi.editor.impl.ad
import andel.editor.RangeMarkerId
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.EDT
import com.intellij.openapi.editor.*
import com.intellij.openapi.editor.ex.InlayModelEx
import com.intellij.openapi.editor.impl.InlayModelImpl
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.openapi.util.Key
import com.jetbrains.rhizomedb.ChangeScope
import fleet.kernel.change
import fleet.kernel.shared
import fleet.util.UID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jetbrains.annotations.ApiStatus.Experimental
import java.awt.Point
import java.awt.Rectangle
import andel.text.TextRange as AndelTextRange
@Experimental
internal class AdInlayModel private constructor(
private val adDocument: AdDocument,
private val mapper: AdRangeMapper<Inlay<*>>,
private val inlayModel: InlayModelImpl,
private val coroutineScope: CoroutineScope,
private val repaintLambda: suspend () -> Unit,
) : InlayModelEx, Disposable {
companion object {
fun fromInlays(
inlayModel: InlayModelImpl,
adDocument: AdDocument,
coroutineScope: CoroutineScope,
repaintLambda: suspend () -> Unit,
): AdInlayModel {
val mapper = AdRangeMapper<Inlay<*>>()
val rangesIds = mutableListOf<RangeMarkerId>()
val ranges = mutableListOf<AndelTextRange>()
for (inlay in inlayModel.getInlineElementsInRange(0, Integer.MAX_VALUE)) {
val rangeId = UID.random()
rangesIds.add(RangeMarkerId(rangeId))
ranges.add(AndelTextRange(inlay.offset, inlay.offset))
mapper.register(rangeId, inlay)
}
adDocument.addRangeMarkers(rangesIds, ranges)
return AdInlayModel(adDocument, mapper, inlayModel, coroutineScope, repaintLambda)
}
}
init {
val listener = object : InlayModel.Listener {
override fun onAdded(inlay: Inlay<*>) {
onInlayUpdate {
// TODO: incorrect offset -> ui offset
val rangeId = UID.random()
adDocument.addRangeMarker(
RangeMarkerId(rangeId),
inlay.offset.toLong(),
inlay.offset.toLong(),
)
mapper.register(rangeId, inlay)
}
}
override fun onRemoved(inlay: Inlay<*>) {
onInlayUpdate {
val rangeId = mapper.unregister(inlay)
if (rangeId != null) {
adDocument.removeRangeMarker(RangeMarkerId(rangeId))
}
}
}
}
coroutineScope.launch(Dispatchers.EDT) {
inlayModel.addListener(listener, this@AdInlayModel)
}
}
private fun onInlayUpdate(body: ChangeScope.() -> Unit) {
coroutineScope.launch {
change {
shared {
body()
}
}
repaintLambda.invoke()
}
}
override fun getBlockElementsForVisualLine(visualLine: Int, above: Boolean): List<Inlay<*>?> {
return emptyList()
}
override fun getAfterLineEndElementsForLogicalLine(logicalLine: Int): List<Inlay<*>?> {
return emptyList()
}
override fun getInlineElementsInRange(startOffset: Int, endOffset: Int): List<Inlay<*>> {
val inlays = mutableListOf<Inlay<*>>()
for (interval in adDocument.queryRangeMarkers(startOffset.toLong(), endOffset.toLong())) {
val inlay = mapper.resolveRange(interval.id)
if (inlay != null) {
val offset = interval.from.toInt()
inlays.add(AdInlay(offset, inlay))
}
}
return inlays
}
override fun getAfterLineEndElementsInRange(startOffset: Int, endOffset: Int): List<Inlay<*>?> {
return emptyList()
}
override fun getBlockElementsInRange(startOffset: Int, endOffset: Int): List<Inlay<*>?> {
return emptyList()
}
/**
* FoldingModel
* CaretModel
* EditorImpl
* SoftWrapModel
* EditorSizeManager
*/
override fun addListener(listener: InlayModel.Listener, disposable: Disposable) {
}
override fun isInBatchMode(): Boolean {
return false
}
override fun getHeightOfBlockElementsBeforeVisualLine(visualLine: Int, startOffset: Int, prevFoldRegionIndex: Int): Int {
return inlayModel.getHeightOfBlockElementsBeforeVisualLine(visualLine, startOffset, prevFoldRegionIndex)
}
override fun dispose() {
}
// region Not yet implemented
override fun <T : EditorCustomElementRenderer?> addInlineElement(offset: Int, relatesToPrecedingText: Boolean, renderer: T & Any): Inlay<T?>? {
TODO("Not yet implemented")
}
override fun <T : EditorCustomElementRenderer?> addInlineElement(offset: Int, relatesToPrecedingText: Boolean, priority: Int, renderer: T & Any): Inlay<T?>? {
TODO("Not yet implemented")
}
override fun <T : EditorCustomElementRenderer?> addInlineElement(offset: Int, properties: InlayProperties, renderer: T & Any): Inlay<T?>? {
TODO("Not yet implemented")
}
override fun <T : EditorCustomElementRenderer?> addBlockElement(offset: Int, relatesToPrecedingText: Boolean, showAbove: Boolean, priority: Int, renderer: T & Any): Inlay<T?>? {
TODO("Not yet implemented")
}
override fun <T : EditorCustomElementRenderer?> addBlockElement(offset: Int, properties: InlayProperties, renderer: T & Any): Inlay<T?>? {
TODO("Not yet implemented")
}
override fun <T : EditorCustomElementRenderer?> addAfterLineEndElement(offset: Int, relatesToPrecedingText: Boolean, renderer: T & Any): Inlay<T?>? {
TODO("Not yet implemented")
}
override fun <T : EditorCustomElementRenderer?> addAfterLineEndElement(offset: Int, properties: InlayProperties, renderer: T & Any): Inlay<T?>? {
TODO("Not yet implemented")
}
override fun hasInlineElementAt(offset: Int): Boolean {
TODO("Not yet implemented")
}
override fun getInlineElementAt(visualPosition: VisualPosition): Inlay<*>? {
TODO("Not yet implemented")
}
override fun getElementAt(point: Point): Inlay<*>? {
TODO("Not yet implemented")
}
override fun setConsiderCaretPositionOnDocumentUpdates(enabled: Boolean) {
TODO("Not yet implemented")
}
override fun execute(batchMode: Boolean, operation: Runnable) {
TODO("Not yet implemented")
}
// endregion
}
private class AdInlay<T : EditorCustomElementRenderer>(
private val offset: Int,
private val origin: Inlay<T>,
) : Inlay<T> {
override fun getOffset(): Int {
return offset
}
override fun getHeightInPixels(): Int {
return origin.getHeightInPixels()
}
override fun getRenderer(): T {
return origin.getRenderer()
}
override fun getWidthInPixels(): Int {
return origin.getWidthInPixels()
}
override fun getPlacement(): Inlay.Placement {
return origin.getPlacement()
}
override fun isRelatedToPrecedingText(): Boolean {
return origin.isRelatedToPrecedingText()
}
override fun isValid(): Boolean {
return origin.isValid
}
override fun <T : Any?> getUserData(key: Key<T?>): T? {
return origin.getUserData(key)
}
override fun getEditor(): Editor {
return origin.editor
}
// region Not yet implemented
override fun getVisualPosition(): VisualPosition {
TODO("Not yet implemented")
}
override fun getBounds(): Rectangle? {
TODO("Not yet implemented")
}
override fun getGutterIconRenderer(): GutterIconRenderer? {
TODO("Not yet implemented")
}
override fun update() {
TODO("Not yet implemented")
}
override fun repaint() {
TODO("Not yet implemented")
}
override fun dispose() {
TODO("Not yet implemented")
}
override fun <T : Any?> putUserDataIfAbsent(key: Key<T?>, value: T & Any): T & Any {
TODO("Not yet implemented")
}
override fun <T : Any?> replace(key: Key<T?>, oldValue: T?, newValue: T?): Boolean {
TODO("Not yet implemented")
}
override fun <T : Any?> putUserData(key: Key<T?>, value: T?) {
TODO("Not yet implemented")
}
// endregion
}
@@ -1,408 +0,0 @@
// 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.openapi.editor.impl.ad
import andel.editor.RangeMarkerId
import com.intellij.openapi.Disposable
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.colors.EditorColorsScheme
import com.intellij.openapi.editor.colors.TextAttributesKey
import com.intellij.openapi.editor.ex.MarkupIterator
import com.intellij.openapi.editor.ex.MarkupModelEx
import com.intellij.openapi.editor.ex.RangeHighlighterEx
import com.intellij.openapi.editor.impl.event.MarkupModelListener
import com.intellij.openapi.editor.markup.*
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.TextRange
import com.intellij.util.Consumer
import com.intellij.util.Processor
import com.jetbrains.rhizomedb.ChangeScope
import fleet.kernel.change
import fleet.kernel.shared
import fleet.util.UID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.jetbrains.annotations.ApiStatus
import java.awt.Color
import andel.text.TextRange as AndelTextRange
@ApiStatus.Experimental
internal class AdMarkupModel private constructor(
private val adDocument: AdDocument,
private val mapper: AdRangeMapper<RangeHighlighterEx>,
markupModel: MarkupModelEx,
private val coroutineScope: CoroutineScope,
private val repaintLambda: suspend () -> Unit,
) : MarkupModelEx, Disposable {
companion object {
fun fromMarkup(
markupModel: MarkupModelEx,
adDocument: AdDocument,
coroutineScope: CoroutineScope,
repaintLambda: suspend () -> Unit,
): AdMarkupModel {
val mapper = addExistingHighlighters(markupModel, adDocument)
val adMarkupModel = AdMarkupModel(adDocument, mapper, markupModel, coroutineScope, repaintLambda)
return adMarkupModel
}
private fun addExistingHighlighters(markupModel: MarkupModelEx, adDocument: AdDocument): AdRangeMapper<RangeHighlighterEx> {
// TODO: there is a gap between allHighlighters and addMarkupModelListener, a highlighter may be missed
val mapper = AdRangeMapper<RangeHighlighterEx>()
val rangesIds = mutableListOf<RangeMarkerId>()
val ranges = mutableListOf<AndelTextRange>()
for (highlighter in markupModel.allHighlighters) {
if (highlighter.isValid) {
val rangeId = UID.random()
rangesIds.add(RangeMarkerId(rangeId))
ranges.add(AndelTextRange(highlighter.startOffset, highlighter.endOffset))
mapper.register(rangeId, highlighter as RangeHighlighterEx)
}
}
adDocument.addRangeMarkers(rangesIds, ranges)
return mapper
}
}
init {
markupModel.addMarkupModelListener(this, object : MarkupModelListener {
override fun afterAdded(highlighter: RangeHighlighterEx) {
onHighlightUpdate {
// TODO: incorrect offset -> ui offset
val rangeId = UID.random()
adDocument.addRangeMarker(
RangeMarkerId(rangeId),
highlighter.startOffset.toLong(),
highlighter.endOffset.toLong(),
)
mapper.register(rangeId, highlighter)
}
}
override fun afterRemoved(highlighter: RangeHighlighterEx) {
onHighlightUpdate {
val rangeId = mapper.unregister(highlighter)
if (rangeId != null) {
adDocument.removeRangeMarker(RangeMarkerId(rangeId))
}
}
}
})
}
override fun processRangeHighlightersOverlappingWith(
start: Int,
end: Int,
processor: Processor<in RangeHighlighterEx>,
): Boolean {
for (interval in adDocument.queryRangeMarkers(start.toLong(), end.toLong())) {
val rh = mapper.resolveRange(interval.id)
if (rh != null && rh.isValid) {
val startOffset = interval.from.toInt()
val endOffset = interval.to.toInt()
val proceed = processor.process(AdRangeHighlighter(
startOffset,
endOffset,
affectedStartOffset(startOffset, rh.targetArea),
affectedEndOffset(endOffset, rh.targetArea),
rh
))
if (!proceed) {
break
}
}
}
return true
}
override fun dispose() {
}
private fun onHighlightUpdate(body: ChangeScope.() -> Unit) {
coroutineScope.launch {
change {
shared {
body()
}
}
repaintLambda.invoke()
}
}
private fun affectedStartOffset(startOffset: Int, area: HighlighterTargetArea): Int {
if (area == HighlighterTargetArea.EXACT_RANGE) {
return startOffset
}
val line = adDocument.getLineNumber(startOffset)
return adDocument.getLineStartOffset(line)
}
private fun affectedEndOffset(endOffset: Int, area: HighlighterTargetArea): Int {
if (area == HighlighterTargetArea.EXACT_RANGE) {
return endOffset
}
val line = adDocument.getLineNumber(endOffset)
return adDocument.getLineEndOffset(line)
}
// region Not yet implemented
override fun addPersistentLineHighlighter(textAttributesKey: TextAttributesKey?, lineNumber: Int, layer: Int): RangeHighlighterEx? {
TODO("Not yet implemented")
}
override fun addPersistentLineHighlighter(lineNumber: Int, layer: Int, textAttributes: TextAttributes?): RangeHighlighterEx? {
TODO("Not yet implemented")
}
override fun containsHighlighter(highlighter: RangeHighlighter): Boolean {
TODO("Not yet implemented")
}
override fun addMarkupModelListener(parentDisposable: Disposable, listener: MarkupModelListener) {
TODO("Not yet implemented")
}
override fun setRangeHighlighterAttributes(highlighter: RangeHighlighter, textAttributes: TextAttributes) {
TODO("Not yet implemented")
}
override fun processRangeHighlightersOutside(start: Int, end: Int, processor: Processor<in RangeHighlighterEx>): Boolean {
TODO("Not yet implemented")
}
override fun overlappingIterator(startOffset: Int, endOffset: Int): MarkupIterator<RangeHighlighterEx?> {
TODO("Not yet implemented")
}
override fun addRangeHighlighterAndChangeAttributes(textAttributesKey: TextAttributesKey?, startOffset: Int, endOffset: Int, layer: Int, targetArea: HighlighterTargetArea, isPersistent: Boolean, changeAttributesAction: Consumer<in RangeHighlighterEx>?): RangeHighlighterEx {
TODO("Not yet implemented")
}
override fun changeAttributesInBatch(highlighter: RangeHighlighterEx, changeAttributesAction: Consumer<in RangeHighlighterEx>) {
TODO("Not yet implemented")
}
override fun getDocument(): Document {
TODO("Not yet implemented")
}
override fun addRangeHighlighter(textAttributesKey: TextAttributesKey?, startOffset: Int, endOffset: Int, layer: Int, targetArea: HighlighterTargetArea): RangeHighlighter {
TODO("Not yet implemented")
}
override fun addRangeHighlighter(startOffset: Int, endOffset: Int, layer: Int, textAttributes: TextAttributes?, targetArea: HighlighterTargetArea): RangeHighlighter {
TODO("Not yet implemented")
}
override fun addLineHighlighter(textAttributesKey: TextAttributesKey?, line: Int, layer: Int): RangeHighlighter {
TODO("Not yet implemented")
}
override fun addLineHighlighter(line: Int, layer: Int, textAttributes: TextAttributes?): RangeHighlighter {
TODO("Not yet implemented")
}
override fun removeHighlighter(rangeHighlighter: RangeHighlighter) {
TODO("Not yet implemented")
}
override fun removeAllHighlighters() {
TODO("Not yet implemented")
}
override fun getAllHighlighters(): Array<out RangeHighlighter> {
TODO("Not yet implemented")
}
override fun <T : Any?> getUserData(key: Key<T?>): T? {
TODO("Not yet implemented")
}
override fun <T : Any?> putUserData(key: Key<T?>, value: T?) {
TODO("Not yet implemented")
}
// endregion
}
private class AdRangeHighlighter(
private val startOffset: Int,
private val endOffset: Int,
private val startAffectedOffset: Int,
private val endAffectedOffset: Int,
private val origin: RangeHighlighterEx,
) : RangeHighlighterEx by origin {
override fun isValid(): Boolean = true
override fun getStartOffset(): Int = startOffset
override fun getEndOffset(): Int = endOffset
override fun getAffectedAreaStartOffset(): Int = startAffectedOffset
override fun getAffectedAreaEndOffset(): Int = endAffectedOffset
override fun getCustomRenderer(): CustomHighlighterRenderer? = origin.customRenderer // TODO
override fun getLineSeparatorPlacement(): SeparatorPlacement? = origin.lineSeparatorPlacement // TODO
override fun getLineSeparatorRenderer(): LineSeparatorRenderer? = origin.lineSeparatorRenderer // TODO
override fun getLineSeparatorColor(): Color? = origin.lineSeparatorColor
override fun isAfterEndOfLine(): Boolean = origin.isAfterEndOfLine
override fun getTextAttributes(scheme: EditorColorsScheme?): TextAttributes? = origin.getTextAttributes(scheme)
override fun isVisibleIfFolded(): Boolean = origin.isVisibleIfFolded
override fun getLayer(): Int = origin.layer
override fun getTargetArea(): HighlighterTargetArea = origin.targetArea
override fun getErrorStripeTooltip(): Any? = origin.errorStripeTooltip
// region Not yet implemented
override fun getId(): Long {
TODO("Not yet implemented")
}
override fun getTextRange(): TextRange {
TODO("Not yet implemented")
}
override fun getForcedTextAttributes(): TextAttributes? {
TODO("Not yet implemented")
}
override fun getForcedErrorStripeMarkColor(): Color? {
TODO("Not yet implemented")
}
override fun setAfterEndOfLine(value: Boolean) {
TODO("Not yet implemented")
}
override fun fireChanged(renderersChanged: Boolean, fontStyleChanged: Boolean, foregroundColorChanged: Boolean) {
TODO("Not yet implemented")
}
override fun setTextAttributes(textAttributes: TextAttributes?) {
TODO("Not yet implemented")
}
override fun setVisibleIfFolded(value: Boolean) {
TODO("Not yet implemented")
}
override fun isPersistent(): Boolean {
TODO("Not yet implemented")
}
override fun isRenderedInGutter(): Boolean {
TODO("Not yet implemented")
}
override fun copyFrom(other: RangeHighlighterEx) {
TODO("Not yet implemented")
}
override fun getTextAttributesKey(): TextAttributesKey? {
TODO("Not yet implemented")
}
override fun setTextAttributesKey(textAttributesKey: TextAttributesKey) {
TODO("Not yet implemented")
}
@Suppress("OVERRIDE_DEPRECATION")
override fun getTextAttributes(): TextAttributes? {
TODO("Not yet implemented")
}
override fun getLineMarkerRenderer(): LineMarkerRenderer? {
TODO("Not yet implemented")
}
override fun setLineMarkerRenderer(renderer: LineMarkerRenderer?) {
TODO("Not yet implemented")
}
override fun setCustomRenderer(renderer: CustomHighlighterRenderer?) {
TODO("Not yet implemented")
}
override fun getGutterIconRenderer(): GutterIconRenderer? {
TODO("Not yet implemented")
}
override fun setGutterIconRenderer(renderer: GutterIconRenderer?) {
TODO("Not yet implemented")
}
override fun getErrorStripeMarkColor(scheme: EditorColorsScheme?): Color? {
TODO("Not yet implemented")
}
override fun setErrorStripeMarkColor(color: Color?) {
TODO("Not yet implemented")
}
override fun setErrorStripeTooltip(tooltipObject: @NlsContexts.Tooltip Any?) {
TODO("Not yet implemented")
}
override fun isThinErrorStripeMark(): Boolean {
TODO("Not yet implemented")
}
override fun setThinErrorStripeMark(value: Boolean) {
TODO("Not yet implemented")
}
override fun setLineSeparatorColor(color: Color?) {
TODO("Not yet implemented")
}
override fun setLineSeparatorRenderer(renderer: LineSeparatorRenderer?) {
TODO("Not yet implemented")
}
override fun setLineSeparatorPlacement(placement: SeparatorPlacement?) {
TODO("Not yet implemented")
}
override fun setEditorFilter(filter: MarkupEditorFilter) {
TODO("Not yet implemented")
}
override fun getEditorFilter(): MarkupEditorFilter {
TODO("Not yet implemented")
}
override fun getDocument(): Document {
TODO("Not yet implemented")
}
override fun setGreedyToLeft(greedy: Boolean) {
TODO("Not yet implemented")
}
override fun setGreedyToRight(greedy: Boolean) {
TODO("Not yet implemented")
}
override fun isGreedyToRight(): Boolean {
TODO("Not yet implemented")
}
override fun isGreedyToLeft(): Boolean {
TODO("Not yet implemented")
}
override fun dispose() {
TODO("Not yet implemented")
}
override fun <T : Any?> getUserData(key: Key<T?>): T? {
TODO("Not yet implemented")
}
override fun <T : Any?> putUserData(key: Key<T?>, value: T?) {
TODO("Not yet implemented")
}
// endregion
}
@@ -1,27 +0,0 @@
// 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.openapi.editor.impl.ad
import fleet.util.UID
import java.util.concurrent.ConcurrentHashMap
internal class AdRangeMapper<T> {
private val idToRange = ConcurrentHashMap<UID, T>()
private val rangeToId = ConcurrentHashMap<T, UID>()
fun resolveRange(id: UID): T? {
return idToRange[id]
}
fun register(id: UID, range: T) {
idToRange.put(id, range)
rangeToId.put(range, id)
}
fun unregister(range: T): UID? {
val id = rangeToId.remove(range)
if (id != null) {
idToRange.remove(id)
}
return id
}
}
@@ -1,8 +1,6 @@
// 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.openapi.editor.impl.ad.v2
package com.intellij.openapi.editor.impl.ad
import andel.intervals.AnchorStorage
import andel.text.Text
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.isRhizomeAdEnabled
@@ -13,24 +11,16 @@ import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.DocumentEx
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.editor.impl.ad.ThreadLocalRhizomeDB
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.platform.pasta.common.DocumentEntity
import com.intellij.platform.pasta.common.DocumentEntity.Companion.EditLogAttr
import com.intellij.platform.pasta.common.DocumentEntity.Companion.SharedAnchorStorageAttr
import com.intellij.platform.pasta.common.DocumentEntity.Companion.TextAttr
import com.intellij.platform.pasta.common.DocumentEntity.Companion.WritableAttr
import com.intellij.platform.pasta.common.createEmptyEditLog
import com.intellij.platform.util.coroutines.childScope
import com.intellij.util.awaitCancellationAndInvoke
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.util.concurrency.ThreadingAssertions
import com.intellij.util.ui.EDT
import fleet.kernel.Durable
import fleet.kernel.change
import fleet.kernel.rete.each
import fleet.kernel.rete.filter
@@ -49,11 +39,11 @@ private val AD_DISPATCHER by lazy {
@Experimental
@Service(Level.APP)
class AdTheManagerV2(private val coroutineScope: CoroutineScope) {
class AdTheManager(private val coroutineScope: CoroutineScope) {
companion object {
@JvmStatic
fun getInstance(): AdTheManagerV2 = service()
fun getInstance(): AdTheManager = service()
}
private val docToHandle = IdentityHashMap<DocumentEx, DocumentEntityHandle>()
@@ -119,7 +109,7 @@ class AdTheManagerV2(private val coroutineScope: CoroutineScope) {
}?.entity()
if (entity != null) {
ThreadLocalRhizomeDB.setThreadLocalDb(ThreadLocalRhizomeDB.lastKnownDb())
return AdDocumentV2(entity)
return AdDocument(entity)
}
}
return null
@@ -157,13 +147,7 @@ class AdTheManagerV2(private val coroutineScope: CoroutineScope) {
createEntity = { entityId ->
change {
shared {
DocumentEntity.new {
it[Durable.Id] = entityId
it[TextAttr] = Text.fromString(text.toString())
it[WritableAttr] = true
it[EditLogAttr] = createEmptyEditLog()
it[SharedAnchorStorageAttr] = AnchorStorage.empty()
}
DocumentEntity.fromText(entityId, text.toString())
}
}
},
@@ -203,7 +187,7 @@ class AdTheManagerV2(private val coroutineScope: CoroutineScope) {
entity
}
if (bindType != BindType.FRONTEND) {
val documentListener = AdDocumentSynchronizerV2(documentId, entityId, cs, entityDeferred)
val documentListener = AdDocumentSynchronizer(documentId, entityId, cs, entityDeferred)
document.addDocumentListener(documentListener)
@Suppress("OPT_IN_USAGE")
cs.awaitCancellationAndInvoke {
@@ -240,7 +224,7 @@ class AdTheManagerV2(private val coroutineScope: CoroutineScope) {
}
private fun isEnabled(): Boolean {
return isRhizomeAdEnabled && Registry.`is`("ijpl.rhizome.ad.v2.enabled", false)
return isRhizomeAdEnabled
}
}
@@ -1,143 +0,0 @@
// 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.openapi.editor.impl.ad
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.isRhizomeAdEnabled
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.Service.Level
import com.intellij.openapi.editor.CaretModel
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.ScrollingModel
import com.intellij.openapi.editor.SelectionModel
import com.intellij.openapi.editor.ex.*
import com.intellij.openapi.editor.highlighter.EditorHighlighter
import com.intellij.openapi.editor.impl.DocumentImpl
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.editor.impl.FocusModeModel
import com.intellij.openapi.util.Disposer
import com.intellij.platform.kernel.withKernel
import com.intellij.platform.util.coroutines.childScope
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.util.concurrency.ThreadingAssertions
import com.jetbrains.rhizomedb.ChangeScope
import com.jetbrains.rhizomedb.asOf
import fleet.kernel.change
import fleet.kernel.lastKnownDb
import fleet.kernel.shared
import fleet.kernel.transactor
import kotlinx.coroutines.*
private val LIMITED_DISPATCHER by lazy {
AppExecutorUtil.createBoundedApplicationPoolExecutor("AD_DISPATCHER", 1).asCoroutineDispatcher()
}
@Service(Level.PROJECT)
internal class AdTheManagerImpl(private val coroutineScope: CoroutineScope) : AdTheManager {
override fun createEditorModel(editor: EditorEx): EditorModel? {
ThreadingAssertions.assertEventDispatchThread()
if (isRhizomeAdEnabled && editor is EditorImpl && isDocumentGuardedByLock(editor.document)) {
val editorScope = this.coroutineScope.childScope("CoroutineScope for $editor", LIMITED_DISPATCHER)
val modStampBefore = editor.document.modificationStamp
@Suppress("RAW_RUN_BLOCKING")
val editorModel = runBlocking {
withKernel {
change {
shared {
createEditorModelImpl(editor, editorScope)
}
}
}
}
ThreadLocalRhizomeDB.setThreadLocalDb(ThreadLocalRhizomeDB.lastKnownDb())
val documentSynchronizer = AdDocumentSynchronizer(editorModel.document as AdDocument, editorScope) {
repaintEditor(editor)
}
editor.document.addDocumentListener(documentSynchronizer, documentSynchronizer)
Disposer.register(editorModel, documentSynchronizer)
Disposer.register(editorModel, editorModel.editorMarkupModel as Disposable)
Disposer.register(editorModel, editorModel.documentMarkupModel as Disposable)
Disposer.register(editorModel, editorModel.inlayModel as Disposable)
Disposer.register(editor.disposable, editorModel)
assert(editor.document.modificationStamp == modStampBefore)
return editorModel
}
return null
}
private fun ChangeScope.createEditorModelImpl(editor: EditorImpl, coroutineScope: CoroutineScope): EditorModel {
register(AdDocumentEntity)
val document = editor.document
val entity = AdDocumentEntity.fromString(
document.immutableCharSequence.toString(),
document.modificationStamp,
)
val adDocument = AdDocument(debugName(document), entity)
fun createMarkup(markupModel: MarkupModelEx) = AdMarkupModel.fromMarkup(
markupModel,
adDocument,
coroutineScope
) { repaintEditor(editor) }
val editorMarkup = createMarkup(editor.markupModel)
val documentMarkup = createMarkup(editor.filteredDocumentMarkupModel)
val inlayModel = AdInlayModel.fromInlays(
editor.inlayModel,
adDocument,
coroutineScope
) { repaintEditor(editor) }
return object : EditorModel {
override fun getDocument(): DocumentEx = adDocument
override fun getEditorMarkupModel(): MarkupModelEx = editorMarkup
override fun getDocumentMarkupModel(): MarkupModelEx = documentMarkup
override fun getHighlighter(): EditorHighlighter = editor.highlighter
override fun getInlayModel(): InlayModelEx = inlayModel
override fun getFoldingModel(): FoldingModelEx = editor.foldingModel
override fun getSoftWrapModel(): SoftWrapModelEx = editor.softWrapModel
override fun getCaretModel(): CaretModel = editor.caretModel
override fun getSelectionModel(): SelectionModel = editor.selectionModel
override fun getScrollingModel(): ScrollingModel = editor.scrollingModel
override fun getFocusModel(): FocusModeModel = editor.focusModeModel
override fun isAd(): Boolean = true
override fun dispose() {
coroutineScope.cancel()
this@AdTheManagerImpl.coroutineScope.launch {
change {
shared {
entity.delete()
}
}
}
}
}
}
private suspend fun repaintEditor(editor: EditorImpl) {
val lastKnownDb = transactor().lastKnownDb
withContext(Dispatchers.EDT) {
if (!editor.isDisposed) {
asOf(lastKnownDb) {
editor.component.repaint()
}
}
}
}
private fun isDocumentGuardedByLock(document: Document): Boolean {
return document is DocumentImpl && document.isWriteThreadOnly
}
private fun debugName(document: Document): String {
val hash = Integer.toHexString(System.identityHashCode(document))
return document.toString().replace("DocumentImpl", "AdDocument@$hash")
}
}
@@ -1,62 +0,0 @@
// 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.openapi.editor.impl.ad.v2
import andel.operation.Operation
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.ex.PrioritizedDocumentListener
import com.intellij.openapi.editor.impl.ad.ThreadLocalRhizomeDB
import com.intellij.platform.pasta.common.DocumentEntity
import fleet.kernel.awaitCommitted
import fleet.kernel.change
import fleet.util.UID
import fleet.util.openmap.OpenMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.jetbrains.annotations.ApiStatus.Experimental
@Experimental
internal data class AdDocumentSynchronizerV2(
private val documentId: Any, // RdDocumentId
private val entityId: UID,
private val coroutineScope: CoroutineScope,
private val entityDeferred: Deferred<DocumentEntity>,
): PrioritizedDocumentListener {
override fun getPriority(): Int = Int.MIN_VALUE + 1
override fun documentChanged(event: DocumentEvent) {
val entityChange = coroutineScope.async {
val operation = operation(event)
val entity = entityDeferred.await()
change { // it is a shared change
entity.mutate(this, OpenMap.empty()) {
edit(operation)
}
}
awaitCommitted()
}
// TODO: pumping events in runWithModalProgressBlocking ruins the models
//@Suppress("HardCodedStringLiteral")
//runWithModalProgressBlocking(
// ModalTaskOwner.guess(),
// "Shared document entity synchronization $entityId",
//) { entityChange.await() }
runBlocking { entityChange.await() }
ThreadLocalRhizomeDB.setThreadLocalDb(ThreadLocalRhizomeDB.lastKnownDb())
}
private fun operation(event: DocumentEvent): Operation {
val oldFragment = event.oldFragment.toString()
val newFragment = event.newFragment.toString()
val lengthBefore = event.document.textLength - newFragment.length + oldFragment.length
return Operation.Companion.replaceAt(
offset = event.offset.toLong(),
oldText = oldFragment,
newText = newFragment,
totalLength = lengthBefore.toLong(),
)
}
}
@@ -1,315 +0,0 @@
// 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.openapi.editor.impl.ad.v2
import andel.text.*
import com.intellij.openapi.Disposable
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.editor.ex.DocumentEx
import com.intellij.openapi.editor.ex.EditReadOnlyListener
import com.intellij.openapi.editor.ex.LineIterator
import com.intellij.openapi.editor.ex.RangeMarkerEx
import com.intellij.platform.pasta.common.DocumentEntity
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.TextRange
import com.intellij.util.Processor
import org.jetbrains.annotations.ApiStatus.Experimental
import java.beans.PropertyChangeListener
import java.util.*
@Experimental
internal class AdDocumentV2(private val entity: DocumentEntity) : DocumentEx {
// region TEXT
override fun getText(range: TextRange): String {
return textView().string(range.startOffset, range.endOffset)
}
override fun getImmutableCharSequence(): CharSequence {
return textView().charSequence()
}
override fun getTextLength(): Int {
return text().charCount
}
// endregion
//region LINES
override fun getLineCount(): Int {
val text = text()
if (text.charCount == 0) {
return 0
}
return text.lineCount.line
}
override fun getLineNumber(offset: Int): Int {
return textView().lineAt(offset).line
}
override fun getLineStartOffset(line: Int): Int {
return textView().lineStartOffset(line.line)
}
override fun getLineEndOffset(line: Int): Int {
return textView().lineEndOffset(line.line)
}
override fun getLineSeparatorLength(line: Int): Int {
// TODO: optimize
return textView().lineEndOffset(line.line, includeLineSeparator=true) -
textView().lineEndOffset(line.line, includeLineSeparator=false)
}
override fun createLineIterator(): LineIterator {
TODO("Not yet implemented")
}
//endregion
//region MUTATE TEXT
override fun insertString(offset: Int, chars: CharSequence) {
TODO("Not yet implemented")
}
override fun deleteString(startOffset: Int, endOffset: Int) {
TODO("Not yet implemented")
}
override fun replaceString(startOffset: Int, endOffset: Int, chars: CharSequence) {
TODO("Not yet implemented")
}
override fun setText(chars: CharSequence) {
TODO("Not yet implemented")
}
override fun replaceText(chars: CharSequence, newModificationStamp: Long) {
TODO("Not yet implemented")
}
//endregion
//region RANGE MARKERS
override fun createRangeMarker(startOffset: Int, endOffset: Int, surviveOnExternalChange: Boolean): RangeMarkerEx {
TODO("Not yet implemented")
}
override fun registerRangeMarker(rangeMarker: RangeMarkerEx, start: Int, end: Int, greedyToLeft: Boolean, greedyToRight: Boolean, layer: Int) {
TODO("Not yet implemented")
}
override fun removeRangeMarker(rangeMarker: RangeMarkerEx): Boolean {
TODO("Not yet implemented")
}
override fun processRangeMarkers(processor: Processor<in RangeMarker>): Boolean {
TODO("Not yet implemented")
}
override fun processRangeMarkersOverlappingWith(start: Int, end: Int, processor: Processor<in RangeMarker>): Boolean {
TODO("Not yet implemented")
}
//endregion
//region DOC LISTENERS
override fun addDocumentListener(listener: DocumentListener, parentDisposable: Disposable) {
//TODO("Not yet implemented")
}
override fun addDocumentListener(listener: DocumentListener) {
//TODO("Not yet implemented")
}
override fun removeDocumentListener(listener: DocumentListener) {
//TODO("Not yet implemented")
}
//endregion
//region PROP LISTENER
override fun addPropertyChangeListener(listener: PropertyChangeListener) {
TODO("Not yet implemented")
}
override fun removePropertyChangeListener(listener: PropertyChangeListener) {
TODO("Not yet implemented")
}
//endregion
//region TRACK MODIFICATIONS
override fun isLineModified(line: Int): Boolean {
TODO("Not yet implemented")
}
override fun getModificationStamp(): Long {
return documentRead {
entity.editLog.timestamp
}
}
override fun setModificationStamp(modificationStamp: Long) {
TODO("Not yet implemented")
}
override fun clearLineModificationFlags() {
TODO("Not yet implemented")
}
override fun getModificationSequence(): Int {
TODO("Not yet implemented")
}
//endregion
//region GUARDED BLOCKS
override fun createGuardedBlock(startOffset: Int, endOffset: Int): RangeMarker {
TODO("Not yet implemented")
}
override fun removeGuardedBlock(block: RangeMarker) {
TODO("Not yet implemented")
}
override fun getOffsetGuard(offset: Int): RangeMarker? {
TODO("Not yet implemented")
}
override fun getRangeGuard(start: Int, end: Int): RangeMarker? {
TODO("Not yet implemented")
}
override fun getGuardedBlocks(): List<RangeMarker> {
// TODO("Not yet implemented")
return Collections.emptyList()
}
override fun startGuardedBlockChecking() {
TODO("Not yet implemented")
}
override fun stopGuardedBlockChecking() {
TODO("Not yet implemented")
}
override fun suppressGuardedExceptions() {
TODO("Not yet implemented")
}
override fun unSuppressGuardedExceptions() {
TODO("Not yet implemented")
}
//endregion
//region BULK MODE
override fun isInBulkUpdate(): Boolean {
return false // TODO("Not yet implemented")
}
@Deprecated("Deprecated in Java")
override fun setInBulkUpdate(value: Boolean) {
TODO("Not yet implemented")
}
override fun isInEventsHandling(): Boolean {
TODO("Not yet implemented")
}
//endregion
//region READ-ONLY MODE
override fun setReadOnly(isReadOnly: Boolean) {
TODO("Not yet implemented")
}
override fun isWritable(): Boolean {
TODO("Not yet implemented")
}
//endregion
//region SPECIAL MODES
override fun setCyclicBufferSize(bufferSize: Int) {
TODO("Not yet implemented")
}
override fun setStripTrailingSpacesEnabled(isEnabled: Boolean) {
TODO("Not yet implemented")
}
//endregion
//region READ ATTEMPT
override fun fireReadOnlyModificationAttempt() {
TODO("Not yet implemented")
}
override fun addEditReadOnlyListener(listener: EditReadOnlyListener) {
TODO("Not yet implemented")
}
override fun removeEditReadOnlyListener(listener: EditReadOnlyListener) {
TODO("Not yet implemented")
}
//endregion
//region USER DATA
override fun <T : Any?> getUserData(key: Key<T>): T? {
TODO("Not yet implemented")
}
override fun <T : Any?> putUserData(key: Key<T>, value: T?) {
TODO("Not yet implemented")
}
//endregion
//region PRIVATE
private fun text(): Text {
return documentRead {
entity.text
}
}
private fun textView(): TextView {
return text().view()
}
private fun <T> documentRead(block: () -> T): T {
return block()
}
//endregion
}
@@ -1,6 +0,0 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@ApiStatus.Experimental
@ApiStatus.Internal
package com.intellij.openapi.editor.impl.ad.v2;
import org.jetbrains.annotations.ApiStatus;
@@ -2481,11 +2481,6 @@ ide.show.files.action=true
ide.showing.scope.compatibility.mode=false
ide.showing.scope.compatibility.mode.description=Turns on compatibility implementation of `showingScope` using newer `launchOnShow`.
ijpl.rhizome.ad.debug.enabled=false
ijpl.rhizome.ad.debug.enabled.description=Debug mode for IJPL-54
ijpl.rhizome.ad.v2.enabled=false
ijpl.rhizome.ad.v2.enabled.description=Second version of IJPL-54 based on common fleet entity. NOTE: must be enabled on FE and BE
thumbnails.toolwindow.enabled=false
thumbnails.toolwindow.enabled.description=Enables the Thumbnails toolwindows and related functionality