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
36import com.intellij.openapi.components.Service
4- import com.intellij.openapi.components.service
57import com.intellij.openapi.diagnostic.logger
8+ import com.intellij.openapi.editor.EditorFactory
9+ import com.intellij.openapi.editor.LogicalPosition
10+ import com.intellij.openapi.editor.event.CaretEvent
11+ import com.intellij.openapi.editor.event.CaretListener
12+ import com.intellij.openapi.editor.event.EditorFactoryEvent
13+ import com.intellij.openapi.editor.event.EditorFactoryListener
14+ import com.intellij.openapi.editor.markup.HighlighterLayer
15+ import com.intellij.openapi.editor.markup.HighlighterTargetArea
16+ import com.intellij.openapi.editor.markup.RangeHighlighter
17+ import com.intellij.openapi.editor.markup.TextAttributes
18+ import com.intellij.openapi.fileEditor.FileEditorManager
19+ import com.intellij.openapi.fileEditor.FileEditorManagerListener
20+ import com.intellij.openapi.fileEditor.TextEditor
621import com.intellij.openapi.project.Project
22+ import com.intellij.openapi.vfs.VirtualFile
23+ import com.intellij.testFramework.utils.editor.getVirtualFile
24+ import com.intellij.ui.JBColor
725import com.intellij.util.io.awaitExit
826import 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
930import io.github.ethersync.protocol.EthersyncEditorProtocol
1031import kotlinx.coroutines.CoroutineScope
1132import kotlinx.coroutines.launch
33+ import org.eclipse.lsp4j.Position
34+ import org.eclipse.lsp4j.Range
1235import org.eclipse.lsp4j.jsonrpc.Launcher
1336import java.io.BufferedReader
1437import java.io.File
1538import java.io.InputStreamReader
39+ import java.util.Collections
40+ import java.util.LinkedList
41+ import java.util.concurrent.CompletableFuture
1642import java.util.concurrent.Executors
1743
1844private val LOG = logger<EthersyncServiceImpl >()
@@ -70,15 +96,74 @@ class EthersyncServiceImpl(
7096 }
7197 }
7298
99+ private fun createProtocolHandler (): EthersyncEditorProtocol {
100+ val highlighter = LinkedList <RangeHighlighter >()
101+
102+ return object : EthersyncEditorProtocol {
103+ override fun cursor (cursorEvent : CursorEvent ) {
104+ val fileEditorManager = FileEditorManager .getInstance(project)
105+
106+ val fileEditor = fileEditorManager.allEditors
107+ .first { editor -> editor.file.url == cursorEvent.documentUri } ? : return
108+
109+ if (fileEditor is TextEditor ) {
110+ 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()
119+
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))
123+
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)
129+ }
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)
140+ }
141+ }
142+ }, ModalityState .nonModal())
143+ }
144+ }
145+
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+
154+ }
155+ }
156+
73157 private fun launchEthersyncClient (socket : String , projectDirectory : File ) {
158+
74159 cs.launch {
75160
76161 LOG .info(" Starting ethersync client" )
77162 val clientProcessBuilder = ProcessBuilder (" ethersync" , " client" , " --socket-name" , socket)
78163 .directory(projectDirectory)
79164 val clientProcess = clientProcessBuilder.start()
80165
81- val ethersyncEditorProtocol = project.service< EthersyncEditorProtocol > ()
166+ val ethersyncEditorProtocol = createProtocolHandler ()
82167 val launcher = Launcher .createIoLauncher(
83168 ethersyncEditorProtocol,
84169 EthersyncEditorProtocol ::class .java,
@@ -89,8 +174,47 @@ class EthersyncServiceImpl(
89174 { _ -> run {} }
90175 )
91176
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+ }
182+
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+ }
195+ }
196+
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+
92208 val listening = launcher.startListening()
93209
210+ cs.launch {
211+ val fileEditorManager = FileEditorManager .getInstance(project)
212+
213+ for (file in fileEditorManager.openFiles) {
214+ ethersyncEditorProtocol.open(DocumentRequest (file.url))
215+ }
216+ }
217+
94218 clientProcess.awaitExit()
95219
96220 listening.cancel(true )
0 commit comments