11package 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
64import com.intellij.openapi.components.Service
75import com.intellij.openapi.diagnostic.logger
86import com.intellij.openapi.editor.EditorFactory
@@ -19,36 +17,92 @@ import com.intellij.openapi.fileEditor.FileEditorManager
1917import com.intellij.openapi.fileEditor.FileEditorManagerListener
2018import com.intellij.openapi.fileEditor.TextEditor
2119import com.intellij.openapi.project.Project
20+ import com.intellij.openapi.project.ProjectManager
21+ import com.intellij.openapi.project.ProjectManagerListener
2222import com.intellij.openapi.vfs.VirtualFile
23- import com.intellij.testFramework.utils.editor.getVirtualFile
2423import com.intellij.ui.JBColor
24+ import com.intellij.util.io.await
2525import com.intellij.util.io.awaitExit
2626import 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.*
3128import kotlinx.coroutines.CoroutineScope
29+ import kotlinx.coroutines.Dispatchers
3230import kotlinx.coroutines.launch
31+ import kotlinx.coroutines.withContext
3332import org.eclipse.lsp4j.Position
3433import org.eclipse.lsp4j.Range
3534import org.eclipse.lsp4j.jsonrpc.Launcher
35+ import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
3636import java.io.BufferedReader
3737import java.io.File
3838import java.io.InputStreamReader
3939import java.util.Collections
4040import java.util.LinkedList
41- import java.util.concurrent.CompletableFuture
4241import java.util.concurrent.Executors
4342
4443private val LOG = logger<EthersyncServiceImpl >()
4544
4645@Service(Service .Level .PROJECT )
4746class 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 }
0 commit comments