Skip to content

Commit 22557f8

Browse files
Refactorings without Testing
This commit makes the code more idiomatic to the use of Eclipse JSONRPC and also makes use of some Kotlin coroutines. Also, the event listeners are initialized in such a way that users don't have to watch out that files have to be opened after the ethersync client connection has to established first.
1 parent 480f30c commit 22557f8

File tree

3 files changed

+157
-98
lines changed

3 files changed

+157
-98
lines changed

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

Lines changed: 140 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package io.github.ethersync
22

3-
import com.google.gson.JsonObject
4-
import com.intellij.openapi.application.ApplicationManager
5-
import com.intellij.openapi.application.ModalityState
3+
import com.intellij.openapi.application.EDT
64
import com.intellij.openapi.components.Service
75
import com.intellij.openapi.diagnostic.logger
86
import com.intellij.openapi.editor.EditorFactory
@@ -19,36 +17,92 @@ import com.intellij.openapi.fileEditor.FileEditorManager
1917
import com.intellij.openapi.fileEditor.FileEditorManagerListener
2018
import com.intellij.openapi.fileEditor.TextEditor
2119
import com.intellij.openapi.project.Project
20+
import com.intellij.openapi.project.ProjectManager
21+
import com.intellij.openapi.project.ProjectManagerListener
2222
import com.intellij.openapi.vfs.VirtualFile
23-
import com.intellij.testFramework.utils.editor.getVirtualFile
2423
import com.intellij.ui.JBColor
24+
import com.intellij.util.io.await
2525
import com.intellij.util.io.awaitExit
2626
import com.intellij.util.io.readLineAsync
27-
import io.github.ethersync.protocol.CursorEvent
28-
import io.github.ethersync.protocol.CursorRequest
29-
import io.github.ethersync.protocol.DocumentRequest
30-
import io.github.ethersync.protocol.EthersyncEditorProtocol
27+
import io.github.ethersync.protocol.*
3128
import kotlinx.coroutines.CoroutineScope
29+
import kotlinx.coroutines.Dispatchers
3230
import kotlinx.coroutines.launch
31+
import kotlinx.coroutines.withContext
3332
import org.eclipse.lsp4j.Position
3433
import org.eclipse.lsp4j.Range
3534
import org.eclipse.lsp4j.jsonrpc.Launcher
35+
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
3636
import java.io.BufferedReader
3737
import java.io.File
3838
import java.io.InputStreamReader
3939
import java.util.Collections
4040
import java.util.LinkedList
41-
import java.util.concurrent.CompletableFuture
4241
import java.util.concurrent.Executors
4342

4443
private val LOG = logger<EthersyncServiceImpl>()
4544

4645
@Service(Service.Level.PROJECT)
4746
class EthersyncServiceImpl(
4847
private val project: Project,
49-
private val cs: CoroutineScope
48+
private val cs: CoroutineScope,
5049
) : EthersyncService {
5150

51+
private var launcher: Launcher<RemoteEthersyncClientProtocol>? = null
52+
private var daemonProcess: Process? = null
53+
private var clientProcess: Process? = null
54+
55+
init {
56+
val bus = project.messageBus.connect()
57+
bus.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener {
58+
override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
59+
launchDocumentOpenRequest(file.url)
60+
}
61+
62+
override fun fileClosed(source: FileEditorManager, file: VirtualFile) {
63+
launchDocumentCloseNotification(file.url)
64+
}
65+
})
66+
67+
val caretListener = object : CaretListener {
68+
override fun caretPositionChanged(event: CaretEvent) {
69+
val uri = event.editor.virtualFile.url
70+
val pos = Position(event.newPosition.line, event.newPosition.column)
71+
val range = Range(pos, pos)
72+
launchCursorRequest(CursorRequest(uri, Collections.singletonList(range)))
73+
}
74+
}
75+
76+
EditorFactory.getInstance().addEditorFactoryListener(object : EditorFactoryListener {
77+
override fun editorCreated(event: EditorFactoryEvent) {
78+
event.editor.caretModel.addCaretListener(caretListener)
79+
}
80+
81+
override fun editorReleased(event: EditorFactoryEvent) {
82+
event.editor.caretModel.removeCaretListener(caretListener)
83+
}
84+
}, project)
85+
86+
bus.subscribe(ProjectManager.TOPIC, object: ProjectManagerListener {
87+
override fun projectClosing(project: Project) {
88+
if (clientProcess != null) {
89+
cs.launch {
90+
clientProcess!!.destroy()
91+
clientProcess!!.awaitExit()
92+
clientProcess = null
93+
}
94+
}
95+
if (daemonProcess != null) {
96+
cs.launch {
97+
daemonProcess!!.destroy()
98+
daemonProcess!!.awaitExit()
99+
daemonProcess = null
100+
}
101+
}
102+
}
103+
})
104+
}
105+
52106
override fun connectToPeer(peer: String) {
53107
val projectDirectory = File(project.basePath!!)
54108
val ethersyncDirectory = File(projectDirectory, ".ethersync")
@@ -63,7 +117,8 @@ class EthersyncServiceImpl(
63117
LOG.info("Starting ethersync daemon")
64118
val daemonProcessBuilder = ProcessBuilder("ethersync", "daemon", "--peer", peer, "--socket-name", socket)
65119
.directory(projectDirectory)
66-
val daemonProcess = daemonProcessBuilder.start()
120+
daemonProcess = daemonProcessBuilder.start()
121+
val daemonProcess = daemonProcess!!
67122

68123
val notifier = project.messageBus.syncPublisher(DaemonOutputNotifier.CHANGE_ACTION_TOPIC)
69124

@@ -108,49 +163,44 @@ class EthersyncServiceImpl(
108163

109164
if (fileEditor is TextEditor) {
110165
val editor = fileEditor.editor
111-
ApplicationManager.getApplication().invokeLater({
112-
synchronized(highlighter) {
113-
val markupModel = editor.markupModel
114-
115-
for (hl in highlighter) {
116-
markupModel.removeHighlighter(hl)
117-
}
118-
highlighter.clear()
119166

120-
for(range in cursorEvent.ranges) {
121-
val startPosition = editor.logicalPositionToOffset(LogicalPosition(range.start.line, range.start.character))
122-
val endPosition = editor.logicalPositionToOffset(LogicalPosition(range.end.line, range.end.character))
167+
cs.launch {
168+
withContext(Dispatchers.EDT) {
169+
synchronized(highlighter) {
170+
val markupModel = editor.markupModel
123171

124-
val textAttributes = TextAttributes().apply {
125-
backgroundColor = JBColor(JBColor.YELLOW, JBColor.DARK_GRAY)
126-
// TODO: unclear which is the best effect type
127-
// effectType = EffectType.LINE_UNDERSCORE
128-
// effectColor = JBColor(JBColor.YELLOW, JBColor.DARK_GRAY)
172+
for (hl in highlighter) {
173+
markupModel.removeHighlighter(hl)
174+
}
175+
highlighter.clear()
176+
177+
for(range in cursorEvent.ranges) {
178+
val startPosition = editor.logicalPositionToOffset(LogicalPosition(range.start.line, range.start.character))
179+
val endPosition = editor.logicalPositionToOffset(LogicalPosition(range.end.line, range.end.character))
180+
181+
val textAttributes = TextAttributes().apply {
182+
backgroundColor = JBColor(JBColor.YELLOW, JBColor.DARK_GRAY)
183+
// TODO: unclear which is the best effect type
184+
// effectType = EffectType.LINE_UNDERSCORE
185+
// effectColor = JBColor(JBColor.YELLOW, JBColor.DARK_GRAY)
186+
}
187+
188+
val hl = markupModel.addRangeHighlighter(
189+
startPosition,
190+
endPosition + 1,
191+
HighlighterLayer.ADDITIONAL_SYNTAX,
192+
textAttributes,
193+
HighlighterTargetArea.EXACT_RANGE
194+
)
195+
196+
highlighter.add(hl)
129197
}
130-
131-
val hl = markupModel.addRangeHighlighter(
132-
startPosition,
133-
endPosition + 1,
134-
HighlighterLayer.ADDITIONAL_SYNTAX,
135-
textAttributes,
136-
HighlighterTargetArea.EXACT_RANGE
137-
)
138-
139-
highlighter.add(hl)
140198
}
141199
}
142-
}, ModalityState.nonModal())
200+
}
143201
}
144202
}
145203

146-
override fun open(documentRequest: DocumentRequest): CompletableFuture<JsonObject> {
147-
return CompletableFuture.completedFuture(JsonObject())
148-
}
149-
150-
override fun close(documentRequest: DocumentRequest): CompletableFuture<JsonObject> {
151-
return CompletableFuture.completedFuture(JsonObject())
152-
}
153-
154204
}
155205
}
156206

@@ -161,73 +211,70 @@ class EthersyncServiceImpl(
161211
LOG.info("Starting ethersync client")
162212
val clientProcessBuilder = ProcessBuilder("ethersync", "client", "--socket-name", socket)
163213
.directory(projectDirectory)
164-
val clientProcess = clientProcessBuilder.start()
214+
clientProcess = clientProcessBuilder.start()
215+
val clientProcess = clientProcess!!
165216

166217
val ethersyncEditorProtocol = createProtocolHandler()
167-
val launcher = Launcher.createIoLauncher(
218+
launcher = Launcher.createIoLauncher(
168219
ethersyncEditorProtocol,
169-
EthersyncEditorProtocol::class.java,
220+
RemoteEthersyncClientProtocol::class.java,
170221
clientProcess.inputStream,
171222
clientProcess.outputStream,
172223
Executors.newCachedThreadPool(),
173224
{ c -> c },
174225
{ _ -> run {} }
175226
)
176227

177-
val bus = project.messageBus.connect()
178-
bus.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener {
179-
override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
180-
ethersyncEditorProtocol.open(DocumentRequest(file.url))
181-
}
228+
val listening = launcher!!.startListening()
182229

183-
override fun fileClosed(source: FileEditorManager, file: VirtualFile) {
184-
ethersyncEditorProtocol.close(DocumentRequest(file.url))
185-
}
186-
})
187-
188-
val caretListener = object : CaretListener {
189-
override fun caretPositionChanged(event: CaretEvent) {
190-
val uri = event.editor.virtualFile.url
191-
val pos = Position(event.newPosition.line, event.newPosition.column)
192-
val range = Range(pos, pos)
193-
launcher.remoteEndpoint.notify("cursor", CursorRequest(uri, Collections.singletonList(range)))
194-
}
230+
val fileEditorManager = FileEditorManager.getInstance(project)
231+
for (file in fileEditorManager.openFiles) {
232+
launchDocumentOpenRequest(file.url)
195233
}
196234

197-
EditorFactory.getInstance().addEditorFactoryListener(object : EditorFactoryListener {
198-
override fun editorCreated(event: EditorFactoryEvent) {
199-
event.editor.caretModel.addCaretListener(caretListener)
200-
}
201-
202-
override fun editorReleased(event: EditorFactoryEvent) {
203-
event.editor.caretModel.removeCaretListener(caretListener)
204-
}
205-
}, project)
206-
207-
208-
val listening = launcher.startListening()
235+
clientProcess.awaitExit()
209236

210-
cs.launch {
211-
val fileEditorManager = FileEditorManager.getInstance(project)
237+
listening.cancel(true)
238+
listening.await()
212239

213-
for (file in fileEditorManager.openFiles) {
214-
ethersyncEditorProtocol.open(DocumentRequest(file.url))
240+
if (clientProcess.exitValue() != 0) {
241+
val stderr = BufferedReader(InputStreamReader(clientProcess.errorStream))
242+
stderr.use {
243+
while (true) {
244+
val line = stderr.readLineAsync() ?: break;
245+
LOG.trace(line)
246+
System.out.println(line)
247+
}
215248
}
216249
}
250+
}
251+
}
217252

218-
clientProcess.awaitExit()
253+
fun launchDocumentCloseNotification(fileUri: String) {
254+
val launcher = launcher ?: return
255+
cs.launch {
256+
launcher.remoteProxy.close(DocumentRequest(fileUri))
257+
}
258+
}
219259

220-
listening.cancel(true)
260+
fun launchDocumentOpenRequest(fileUri: String) {
261+
val launcher = launcher ?: return
262+
cs.launch {
263+
try {
264+
launcher.remoteProxy.open(DocumentRequest(fileUri)).await()
265+
} catch (e: ResponseErrorException) {
266+
TODO("not yet implemented: notify about an protocol error")
267+
}
268+
}
269+
}
221270

222-
if (clientProcess.exitValue() != 0) {
223-
val stderr = BufferedReader(InputStreamReader(clientProcess.errorStream))
224-
stderr.use {
225-
while (true) {
226-
val line = stderr.readLineAsync() ?: break;
227-
LOG.trace(line)
228-
System.out.println(line)
229-
}
230-
}
271+
fun launchCursorRequest(cursorRequest: CursorRequest) {
272+
val launcher = launcher ?: return
273+
cs.launch {
274+
try {
275+
launcher.remoteProxy.cursor(cursorRequest).await()
276+
} catch (e: ResponseErrorException) {
277+
TODO("not yet implemented: notify about an protocol error")
231278
}
232279
}
233280
}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,4 @@ interface EthersyncEditorProtocol {
99
@JsonNotification("cursor")
1010
fun cursor(cursorEvent: CursorEvent)
1111

12-
@JsonRequest
13-
fun open(documentRequest: DocumentRequest): CompletableFuture<JsonObject>
14-
15-
@JsonRequest
16-
fun close(documentRequest: DocumentRequest): CompletableFuture<JsonObject>
1712
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.github.ethersync.protocol
2+
3+
import com.google.gson.JsonObject
4+
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification
5+
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest
6+
import java.util.concurrent.CompletableFuture
7+
8+
interface RemoteEthersyncClientProtocol {
9+
@JsonRequest
10+
fun cursor(cursorRequest: CursorRequest): CompletableFuture<JsonObject>
11+
12+
@JsonRequest
13+
fun open(documentRequest: DocumentRequest): CompletableFuture<JsonObject>
14+
15+
@JsonNotification
16+
fun close(documentRequest: DocumentRequest)
17+
}

0 commit comments

Comments
 (0)