Skip to content

Commit a5b5de6

Browse files
Working Synchronization Between Editors
This commit provides the first end to end sync among editors. However, there is still a weird synchronization issue, when the Intellij peer inserts a newline (see https://github.com/user-attachments/assets/765f0ea4-abb4-4559-a5c6-39082a335fe5, 0:24min) because the newline isn't sent to the other peer.
1 parent ff8f1bf commit a5b5de6

File tree

3 files changed

+71
-6
lines changed

3 files changed

+71
-6
lines changed

src/main/kotlin/io/github/ethersync/EthersyncServiceImpl.kt

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package io.github.ethersync
22

33
import com.intellij.openapi.application.EDT
4+
import com.intellij.openapi.command.WriteCommandAction
45
import com.intellij.openapi.components.Service
56
import com.intellij.openapi.diagnostic.logger
67
import com.intellij.openapi.editor.EditorFactory
78
import com.intellij.openapi.editor.LogicalPosition
89
import com.intellij.openapi.editor.event.*
9-
import com.intellij.openapi.editor.impl.DocumentImpl
1010
import com.intellij.openapi.editor.markup.*
1111
import com.intellij.openapi.fileEditor.FileDocumentManager
1212
import com.intellij.openapi.fileEditor.FileEditorManager
@@ -32,9 +32,11 @@ import org.eclipse.lsp4j.jsonrpc.Launcher
3232
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
3333
import java.io.BufferedReader
3434
import java.io.File
35+
import java.io.IOException
3536
import java.io.InputStreamReader
3637
import java.util.*
3738
import java.util.concurrent.Executors
39+
import java.util.concurrent.atomic.AtomicBoolean
3840

3941
private val LOG = logger<EthersyncServiceImpl>()
4042

@@ -48,11 +50,15 @@ class EthersyncServiceImpl(
4850
private var daemonProcess: Process? = null
4951
private var clientProcess: Process? = null
5052

51-
data class EthersyncRevision(
53+
private val ignoreChangeEvent = AtomicBoolean(false)
54+
55+
data class FileRevision(
56+
// Number of operations the daemon has made.
5257
var daemon: UInt = 0u,
58+
// Number of operations we have made.
5359
var editor: UInt = 0u,
5460
)
55-
val revisions: HashMap<String, EthersyncRevision> = HashMap()
61+
val revisions: HashMap<String, FileRevision> = HashMap()
5662

5763
init {
5864
val bus = project.messageBus.connect()
@@ -77,6 +83,10 @@ class EthersyncServiceImpl(
7783

7884
val documentListener = object : DocumentListener {
7985
override fun documentChanged(event: DocumentEvent) {
86+
if (ignoreChangeEvent.get()) {
87+
return
88+
}
89+
8090
val file = FileDocumentManager.getInstance().getFile(event.document)!!
8191
val fileEditor = FileEditorManager.getInstance(project).getEditors(file)
8292
.filterIsInstance<TextEditor>()
@@ -86,7 +96,7 @@ class EthersyncServiceImpl(
8696

8797
val uri = file.url
8898

89-
val rev = revisions.getOrPut(uri) { EthersyncRevision() };
99+
val rev = revisions[uri]!!
90100
rev.editor += 1u
91101

92102
// TODO: this calc doesn't seem right because there are some odd changes on the Neovim instance
@@ -102,6 +112,7 @@ class EthersyncServiceImpl(
102112
Position(start.line, start.column),
103113
Position(end.line, end.column)
104114
),
115+
// TODO: I remember UTF-16/32… did not test a none ASCII file yet
105116
event.newFragment.toString()
106117
))
107118
)
@@ -178,7 +189,12 @@ class EthersyncServiceImpl(
178189
val stdout = BufferedReader(InputStreamReader(daemonProcess.inputStream))
179190
stdout.use {
180191
while (true) {
181-
val line = stdout.readLineAsync() ?: break;
192+
val line = try {
193+
stdout.readLineAsync() ?: break;
194+
} catch (e: IOException) {
195+
LOG.error(e)
196+
break
197+
}
182198
LOG.trace(line)
183199
cs.launch {
184200
withContext(Dispatchers.EDT) {
@@ -273,6 +289,38 @@ class EthersyncServiceImpl(
273289
}
274290
}
275291

292+
override fun edit(editEvent: EditEvent) {
293+
val revision = revisions[editEvent.documentUri]!!
294+
295+
// Check if operation is up-to-date to our content.
296+
// If it's not, ignore it! The daemon will send a transformed one later.
297+
if (editEvent.editorRevision == revision.editor) {
298+
ignoreChangeEvent.set(true)
299+
300+
val fileEditorManager = FileEditorManager.getInstance(project)
301+
302+
val fileEditor = fileEditorManager.allEditors
303+
.first { editor -> editor.file.url == editEvent.documentUri } ?: return
304+
305+
if (fileEditor is TextEditor) {
306+
val editor = fileEditor.editor
307+
308+
WriteCommandAction.runWriteCommandAction(project, {
309+
for(delta in editEvent.delta) {
310+
val start = editor.logicalPositionToOffset(LogicalPosition(delta.range.start.line, delta.range.start.character))
311+
val end = editor.logicalPositionToOffset(LogicalPosition(delta.range.end.line, delta.range.end.character))
312+
313+
editor.document.replaceString(start, end, delta.replacement)
314+
}
315+
})
316+
317+
revision.daemon += 1u
318+
319+
ignoreChangeEvent.set(false)
320+
}
321+
}
322+
}
323+
276324
}
277325
}
278326

@@ -324,13 +372,15 @@ class EthersyncServiceImpl(
324372
val launcher = launcher ?: return
325373
cs.launch {
326374
launcher.remoteProxy.close(DocumentRequest(fileUri))
375+
revisions.remove(fileUri)
327376
}
328377
}
329378

330379
fun launchDocumentOpenRequest(fileUri: String) {
331380
val launcher = launcher ?: return
332381
cs.launch {
333382
try {
383+
revisions[fileUri] = FileRevision();
334384
launcher.remoteProxy.open(DocumentRequest(fileUri)).await()
335385
} catch (e: ResponseErrorException) {
336386
TODO("not yet implemented: notify about an protocol error")
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.github.ethersync.protocol
2+
3+
import com.google.gson.annotations.SerializedName
4+
5+
data class EditEvent(
6+
@SerializedName("uri")
7+
val documentUri: String,
8+
@SerializedName("revision")
9+
val editorRevision: UInt,
10+
@SerializedName("delta")
11+
val delta: List<Delta>,
12+
)

src/main/kotlin/io/github/ethersync/protocol/EthersyncEditorProtocol.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import org.eclipse.lsp4j.jsonrpc.services.JsonNotification
44

55
interface EthersyncEditorProtocol {
66

7-
@JsonNotification("cursor")
7+
@JsonNotification
88
fun cursor(cursorEvent: CursorEvent)
9+
10+
@JsonNotification
11+
fun edit(editEvent: EditEvent)
912
}

0 commit comments

Comments
 (0)