Skip to content

Commit ff8f1bf

Browse files
Sync from Intellij to Others
This commit provides the basic implementation of sending edits from Intellij to the other peers. The other way around it not yet implemented.
1 parent 7b8a7dc commit ff8f1bf

File tree

7 files changed

+115
-28
lines changed

7 files changed

+115
-28
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ interface DaemonOutputNotifier {
1111
Topic.create("ethersync daemon output", DaemonOutputNotifier::class.java)
1212
}
1313

14+
fun clear()
15+
1416
fun logOutput(line: String)
1517
}

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

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,18 @@ import com.intellij.openapi.components.Service
55
import com.intellij.openapi.diagnostic.logger
66
import com.intellij.openapi.editor.EditorFactory
77
import com.intellij.openapi.editor.LogicalPosition
8-
import com.intellij.openapi.editor.event.CaretEvent
9-
import com.intellij.openapi.editor.event.CaretListener
10-
import com.intellij.openapi.editor.event.EditorFactoryEvent
11-
import com.intellij.openapi.editor.event.EditorFactoryListener
8+
import com.intellij.openapi.editor.event.*
9+
import com.intellij.openapi.editor.impl.DocumentImpl
1210
import com.intellij.openapi.editor.markup.*
11+
import com.intellij.openapi.fileEditor.FileDocumentManager
1312
import com.intellij.openapi.fileEditor.FileEditorManager
1413
import com.intellij.openapi.fileEditor.FileEditorManagerListener
1514
import com.intellij.openapi.fileEditor.TextEditor
1615
import com.intellij.openapi.project.Project
1716
import com.intellij.openapi.project.ProjectManager
1817
import com.intellij.openapi.project.ProjectManagerListener
1918
import com.intellij.openapi.vfs.VirtualFile
20-
import com.intellij.platform.diagnostic.telemetry.EDT
19+
import com.intellij.refactoring.suggested.newRange
2120
import com.intellij.ui.JBColor
2221
import com.intellij.util.io.await
2322
import com.intellij.util.io.awaitExit
@@ -34,8 +33,7 @@ import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
3433
import java.io.BufferedReader
3534
import java.io.File
3635
import java.io.InputStreamReader
37-
import java.util.Collections
38-
import java.util.LinkedList
36+
import java.util.*
3937
import java.util.concurrent.Executors
4038

4139
private val LOG = logger<EthersyncServiceImpl>()
@@ -50,6 +48,12 @@ class EthersyncServiceImpl(
5048
private var daemonProcess: Process? = null
5149
private var clientProcess: Process? = null
5250

51+
data class EthersyncRevision(
52+
var daemon: UInt = 0u,
53+
var editor: UInt = 0u,
54+
)
55+
val revisions: HashMap<String, EthersyncRevision> = HashMap()
56+
5357
init {
5458
val bus = project.messageBus.connect()
5559
bus.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener {
@@ -71,42 +75,82 @@ class EthersyncServiceImpl(
7175
}
7276
}
7377

78+
val documentListener = object : DocumentListener {
79+
override fun documentChanged(event: DocumentEvent) {
80+
val file = FileDocumentManager.getInstance().getFile(event.document)!!
81+
val fileEditor = FileEditorManager.getInstance(project).getEditors(file)
82+
.filterIsInstance<TextEditor>()
83+
.first()
84+
85+
val editor = fileEditor.editor
86+
87+
val uri = file.url
88+
89+
val rev = revisions.getOrPut(uri) { EthersyncRevision() };
90+
rev.editor += 1u
91+
92+
// TODO: this calc doesn't seem right because there are some odd changes on the Neovim instance
93+
val start = editor.offsetToLogicalPosition(event.newRange.startOffset)
94+
val end = editor.offsetToLogicalPosition(event.newRange.endOffset)
95+
96+
launchEditRequest(
97+
EditRequest(
98+
uri,
99+
rev.daemon,
100+
Collections.singletonList(Delta(
101+
Range(
102+
Position(start.line, start.column),
103+
Position(end.line, end.column)
104+
),
105+
event.newFragment.toString()
106+
))
107+
)
108+
)
109+
}
110+
}
111+
74112
for (editor in FileEditorManager.getInstance(project).allEditors) {
75113
if (editor is TextEditor) {
76114
editor.editor.caretModel.addCaretListener(caretListener)
115+
editor.editor.document.addDocumentListener(documentListener)
77116
}
78117
}
79118

80119
EditorFactory.getInstance().addEditorFactoryListener(object : EditorFactoryListener {
81120
override fun editorCreated(event: EditorFactoryEvent) {
82121
event.editor.caretModel.addCaretListener(caretListener)
122+
event.editor.document.addDocumentListener(documentListener)
83123
}
84124

85125
override fun editorReleased(event: EditorFactoryEvent) {
86126
event.editor.caretModel.removeCaretListener(caretListener)
127+
event.editor.document.removeDocumentListener(documentListener)
87128
}
88129
}, project)
89130

90131
ProjectManager.getInstance().addProjectManagerListener(project, object: ProjectManagerListener {
91132
override fun projectClosingBeforeSave(project: Project) {
92-
if (clientProcess != null) {
93-
cs.launch {
94-
clientProcess!!.destroy()
95-
clientProcess!!.awaitExit()
96-
clientProcess = null
97-
}
98-
}
99-
if (daemonProcess != null) {
100-
cs.launch {
101-
daemonProcess!!.destroy()
102-
daemonProcess!!.awaitExit()
103-
daemonProcess = null
104-
}
133+
cs.launch {
134+
shutdown()
105135
}
106136
}
107137
})
108138
}
109139

140+
suspend fun shutdown() {
141+
clientProcess?.let {
142+
it.destroy()
143+
it.awaitExit()
144+
clientProcess = null
145+
}
146+
daemonProcess?.let {
147+
it.destroy()
148+
it.awaitExit()
149+
daemonProcess = null
150+
}
151+
revisions.clear()
152+
}
153+
110154
override fun connectToPeer(peer: String) {
111155
val projectDirectory = File(project.basePath!!)
112156
val ethersyncDirectory = File(projectDirectory, ".ethersync")
@@ -118,14 +162,18 @@ class EthersyncServiceImpl(
118162
ethersyncDirectory.mkdir()
119163
}
120164

165+
val notifier = project.messageBus.syncPublisher(DaemonOutputNotifier.CHANGE_ACTION_TOPIC)
166+
if (daemonProcess != null || clientProcess != null) {
167+
notifier.clear()
168+
shutdown()
169+
}
170+
121171
LOG.info("Starting ethersync daemon")
122172
val daemonProcessBuilder = ProcessBuilder("ethersync", "daemon", "--peer", peer, "--socket-name", socket)
123173
.directory(projectDirectory)
124174
daemonProcess = daemonProcessBuilder.start()
125175
val daemonProcess = daemonProcess!!
126176

127-
val notifier = project.messageBus.syncPublisher(DaemonOutputNotifier.CHANGE_ACTION_TOPIC)
128-
129177
cs.launch {
130178
val stdout = BufferedReader(InputStreamReader(daemonProcess.inputStream))
131179
stdout.use {
@@ -229,9 +277,7 @@ class EthersyncServiceImpl(
229277
}
230278

231279
private fun launchEthersyncClient(socket: String, projectDirectory: File) {
232-
233280
cs.launch {
234-
235281
LOG.info("Starting ethersync client")
236282
val clientProcessBuilder = ProcessBuilder("ethersync", "client", "--socket-name", socket)
237283
.directory(projectDirectory)
@@ -302,4 +348,15 @@ class EthersyncServiceImpl(
302348
}
303349
}
304350
}
351+
352+
fun launchEditRequest(editRequest: EditRequest) {
353+
val launcher = launcher ?: return
354+
cs.launch {
355+
try {
356+
launcher.remoteProxy.edit(editRequest).await()
357+
} catch (e: ResponseErrorException) {
358+
TODO("not yet implemented: notify about an protocol error")
359+
}
360+
}
361+
}
305362
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class ToolWindowFactory : ToolWindowFactory {
3535
project.messageBus.connect().subscribe(
3636
DaemonOutputNotifier.CHANGE_ACTION_TOPIC,
3737
object : DaemonOutputNotifier {
38+
override fun clear() {
39+
body.clear()
40+
logTextArea.text = document.html()
41+
}
42+
3843
override fun logOutput(line: String) {
3944
body.append(utilsAnsiHtml.convertAnsiToHtml(line))
4045
body.append("<br>")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.ethersync.protocol
2+
3+
import com.google.gson.annotations.SerializedName
4+
import org.eclipse.lsp4j.Range
5+
6+
data class Delta(
7+
@SerializedName("range")
8+
val range: Range,
9+
@SerializedName("replacement")
10+
val replacement: String
11+
)
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 EditRequest(
6+
@SerializedName("uri")
7+
val documentUri: String,
8+
@SerializedName("revision")
9+
val revision: UInt,
10+
@SerializedName("delta")
11+
val delta: List<Delta>,
12+
)
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package io.github.ethersync.protocol
22

3-
import com.google.gson.JsonObject
43
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification
5-
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest
6-
import java.util.concurrent.CompletableFuture
74

85
interface EthersyncEditorProtocol {
6+
97
@JsonNotification("cursor")
108
fun cursor(cursorEvent: CursorEvent)
11-
129
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ interface RemoteEthersyncClientProtocol {
1212
@JsonRequest
1313
fun open(documentRequest: DocumentRequest): CompletableFuture<JsonObject>
1414

15+
@JsonRequest
16+
fun edit(editRequest: EditRequest): CompletableFuture<JsonObject>
17+
1518
@JsonNotification
1619
fun close(documentRequest: DocumentRequest)
1720
}

0 commit comments

Comments
 (0)