[editor] IJPL-54 Sync entity->documentImpl

GitOrigin-RevId: f43b36fcbab6b9b65dccd403e53c5264ffb71431
This commit is contained in:
Alexander Trushev
2026-02-04 20:35:47 +00:00
committed by intellij-monorepo-bot
parent ad9e12e51a
commit 1fba937893
4 changed files with 97 additions and 32 deletions
@@ -6,6 +6,7 @@ import andel.editor.AnchorLifetime
import andel.editor.DocumentComponent
import andel.editor.DocumentComponentKey
import andel.editor.DocumentMeta
import andel.editor.DocumentMetaKey
import andel.editor.MutableDocument
import andel.editor.RangeMarkerId
import andel.intervals.AnchorStorage
@@ -28,6 +29,11 @@ import fleet.util.openmap.MutableBoundedOpenMap
import fleet.util.openmap.MutableOpenMap
object DocToEntityUpdate : DocumentMetaKey<Boolean>
internal val DbMutableDocument.docToDb: Boolean?
get() = meta[DocToEntityUpdate]
internal class DbMutableDocument(
val dbDocument: DocumentEntity,
private val changeScope: ChangeScope,
@@ -79,7 +85,9 @@ internal class DbMutableDocument(
ChangeDocument(
operationId = UID.random(),
documentId = dbDocument.eid,
operation = versionedOperation.rebase(this@DbMutableDocument)),
operation = versionedOperation.rebase(this@DbMutableDocument),
docToDb = docToDb ?: false,
),
)
}
val textAfter = text
@@ -27,6 +27,7 @@ data class ChangeDocument(
val documentId: EID,
val operationId: UID,
val operation: Operation,
val docToDb: Boolean,
override val seed: Long = Random.nextLong(),
) : Instruction {
@@ -49,6 +50,7 @@ data class ChangeDocument(
documentId = documentUID,
operationId = instruction.operationId,
operation = instruction.operation,
docToDb = instruction.docToDb,
seed = instruction.seed,
)
)
@@ -65,6 +67,7 @@ data class ChangeDocument(
documentId = documentEID,
operationId = sharedInstruction.operationId,
operation = sharedInstruction.operation,
docToDb = sharedInstruction.docToDb,
seed = sharedInstruction.seed,
))
}
@@ -12,6 +12,7 @@ data class SharedChangeDocument(
val documentId: UID,
val operationId: UID,
val operation: Operation,
val docToDb: Boolean,
val seed: Long,
)
@@ -1,8 +1,13 @@
// Copyright 2000-2026 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.document
import andel.operation.Op
import andel.operation.Operation
import andel.operation.compose
import andel.operation.isNotIdentity
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.edtWriteAction
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.Service.Level
import com.intellij.openapi.components.service
@@ -11,17 +16,27 @@ import com.intellij.openapi.editor.ex.DocumentEx
import com.intellij.openapi.editor.ex.PrioritizedDocumentListener
import com.intellij.openapi.editor.impl.ad.AdTheManager
import com.intellij.openapi.editor.impl.ad.util.ThreadLocalRhizomeDB
import com.intellij.platform.pasta.common.ChangeDocument
import com.intellij.platform.pasta.common.DocToEntityUpdate
import com.intellij.platform.pasta.common.DocumentEntity
import com.intellij.platform.util.coroutines.childScope
import com.intellij.util.ui.EDT
import com.jetbrains.rhizomedb.asOf
import fleet.kernel.SubscriptionEvent
import fleet.kernel.byUidOrNull
import fleet.kernel.change
import fleet.kernel.rebase.TransactionResult
import fleet.kernel.rebase.TransactionResultKey
import fleet.kernel.rebase.awaitCommitted
import fleet.kernel.rebase.shared
import fleet.kernel.transactor
import fleet.util.openmap.OpenMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.lang.ref.Reference
import java.lang.ref.WeakReference
@Service(Level.APP)
@@ -42,6 +57,7 @@ class AdDocumentSynchronizer(private val coroutineScope: CoroutineScope): Dispos
DocToEntitySynchronizer(
debugName,
entity,
WeakReference(document),
cs,
)
)
@@ -52,51 +68,57 @@ class AdDocumentSynchronizer(private val coroutineScope: CoroutineScope): Dispos
private class DocToEntitySynchronizer(
private val debugName: String,
private val entity: DocumentEntity,
//private val document: DocumentEx,
private val documentRef: Reference<DocumentEx>,
private val coroutineScope: CoroutineScope
) : PrioritizedDocumentListener {
//private var documentChanging = false
//init {
// coroutineScope.launch {
// var initial = true
// entity.asQuery()[DocumentEntity.TextAttr].collect { text ->
// // TODO do we need to process first change in some cases (e.g. on the frontend)?
// if (initial) {
// initial = false
// return@collect
// }
//
// writeAction {
// documentChanging = true
// try {
// document.setText(text.view().charSequence())
// }
// finally {
// documentChanging = false
// }
// }
// }
// }
//}
init {
coroutineScope.launch {
transactor().log.collect { e ->
if (documentRef.get() == null) {
cancel()
return@collect
}
if (e is SubscriptionEvent.Next) {
val change = e.change
val tx = change.meta[TransactionResultKey]
if (tx is TransactionResult.TransactionApplied) {
val op = tx.tx.instructions.asSequence()
.filter { it.name == ChangeDocument.instructionName }
.map { it.instruction.get(ChangeDocument.serializer) }
.filter {
asOf(change.dbAfter) {
val changedEntity = byUidOrNull<DocumentEntity>(it.documentId)
changedEntity?.eid == entity.eid
}
}
.filter { !it.docToDb }
.map { it.operation }
.toList()
.compose()
if (op.isNotIdentity()) {
entityToDocumentImplChange(op)
}
}
}
}
}
}
override fun getPriority(): Int = Int.MIN_VALUE + 1
override fun documentChanged(event: DocumentEvent) {
//if (documentChanging) return
val entityChange = coroutineScope.async {
val operation = operation(event)
change {
// shared should not be used here, otherwise an exception is going to be thrown during rebase
// `mutate` should decide when to use `shared`
// TODO check that markups work // shared to mutate shared document components (e.g., AdMarkupModel)
shared { // shared to mutate shared document components (e.g., AdMarkupModel)
entity.mutate(this, OpenMap.empty()) {
//shared { // shared to mutate shared document components (e.g., AdMarkupModel)
entity.mutate(this, OpenMap { set(DocToEntityUpdate, true) }) {
edit(operation)
}
}
//}
}
//TODO we should not wait for
awaitCommitted()
@@ -108,6 +130,37 @@ class AdDocumentSynchronizer(private val coroutineScope: CoroutineScope): Dispos
}
}
private suspend fun entityToDocumentImplChange(op: Operation) {
edtWriteAction {
CommandProcessor.getInstance().executeCommand(
null,
Runnable {
val document = documentRef.get()
if (document != null) {
var offset = 0
for (item in op.ops) {
when (item) {
is Op.Replace -> {
document.replaceString(
offset,
offset + item.delete.length,
item.insert,
)
offset += item.insert.length
}
is Op.Retain -> {
offset += item.len.toInt()
}
}
}
}
},
null,
null,
)
}
}
private fun operation(event: DocumentEvent): Operation {
val oldFragment = event.oldFragment.toString()
val newFragment = event.newFragment.toString()