11package io.github.ethersync
22
33import com.intellij.openapi.application.EDT
4+ import com.intellij.openapi.command.WriteCommandAction
45import com.intellij.openapi.components.Service
56import com.intellij.openapi.diagnostic.logger
67import com.intellij.openapi.editor.EditorFactory
78import com.intellij.openapi.editor.LogicalPosition
89import com.intellij.openapi.editor.event.*
9- import com.intellij.openapi.editor.impl.DocumentImpl
1010import com.intellij.openapi.editor.markup.*
1111import com.intellij.openapi.fileEditor.FileDocumentManager
1212import com.intellij.openapi.fileEditor.FileEditorManager
@@ -32,9 +32,11 @@ import org.eclipse.lsp4j.jsonrpc.Launcher
3232import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
3333import java.io.BufferedReader
3434import java.io.File
35+ import java.io.IOException
3536import java.io.InputStreamReader
3637import java.util.*
3738import java.util.concurrent.Executors
39+ import java.util.concurrent.atomic.AtomicBoolean
3840
3941private 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" )
0 commit comments