11package  io.github.ethersync.sync 
22
3- import  com.intellij.openapi.application.EDT 
43import  com.intellij.openapi.editor.LogicalPosition 
54import  com.intellij.openapi.editor.event.CaretEvent 
65import  com.intellij.openapi.editor.event.CaretListener 
76import  com.intellij.openapi.editor.markup.* 
87import  com.intellij.openapi.fileEditor.FileEditorManager 
98import  com.intellij.openapi.fileEditor.TextEditor 
109import  com.intellij.openapi.project.Project 
10+ import  com.intellij.openapi.rd.util.withUiContext 
1111import  com.intellij.ui.JBColor 
1212import  com.intellij.util.io.await 
1313import  io.github.ethersync.protocol.CursorEvent 
1414import  io.github.ethersync.protocol.CursorRequest 
1515import  io.github.ethersync.protocol.RemoteEthersyncClientProtocol 
1616import  kotlinx.coroutines.CoroutineScope 
17- import  kotlinx.coroutines.Dispatchers 
1817import  kotlinx.coroutines.launch 
19- import  kotlinx.coroutines.withContext 
2018import  org.eclipse.lsp4j.Position 
2119import  org.eclipse.lsp4j.Range 
2220import  org.eclipse.lsp4j.jsonrpc.ResponseErrorException 
2321import  java.util.* 
22+ import  kotlin.collections.HashMap 
2423
2524class  Cursortracker (
2625   private  val  project :  Project ,
2726   private  val  cs :  CoroutineScope ,
2827) : CaretListener {
2928
30-    private  val  highlighter =  HashMap <String , List <RangeHighlighter >>()
29+    private  data class  Key (val  documentUri :  String , val  user :  String )
30+    private  val  highlighter =  HashMap <Key , List <RangeHighlighter >>()
3131
3232   var  remoteProxy:  RemoteEthersyncClientProtocol ?  =  null 
3333
@@ -38,14 +38,15 @@ class Cursortracker(
3838         .filter { editor ->  editor.file.canonicalFile !=  null  }
3939         .firstOrNull { editor ->  editor.file.canonicalFile!! .url ==  cursorEvent.documentUri } ? :  return 
4040
41+       val  key =  Key (cursorEvent.documentUri, cursorEvent.userId)
4142      val  editor =  fileEditor.editor
4243
4344      cs.launch {
44-          withContext( Dispatchers . EDT )  {
45+          withUiContext  {
4546            synchronized(highlighter) {
4647               val  markupModel =  editor.markupModel
4748
48-                val  previous =  highlighter.remove(cursorEvent.userId )
49+                val  previous =  highlighter.remove(key )
4950               if  (previous !=  null ) {
5051                  for  (hl in  previous) {
5152                     markupModel.removeHighlighter(hl)
@@ -58,16 +59,12 @@ class Cursortracker(
5859                  val  endPosition =  editor.logicalPositionToOffset(LogicalPosition (range.end.line, range.end.character))
5960
6061                  val  textAttributes =  TextAttributes ().apply  {
61-                      //  foregroundColor = JBColor(JBColor.YELLOW, JBColor.DARK_GRAY)
62- 
63-                      //  TODO: unclear which is the best effect type
6462                     effectType =  EffectType .ROUNDED_BOX 
6563                     effectColor =  JBColor (JBColor .YELLOW , JBColor .DARK_GRAY )
6664                  }
67- 
6865                  val  hl =  markupModel.addRangeHighlighter(
6966                     startPosition,
70-                      endPosition  +   1 ,
67+                      endPosition,
7168                     HighlighterLayer .ADDITIONAL_SYNTAX ,
7269                     textAttributes,
7370                     HighlighterTargetArea .EXACT_RANGE 
@@ -78,7 +75,7 @@ class Cursortracker(
7875
7976                  newHighlighter.add(hl)
8077               }
81-                highlighter[cursorEvent.userId ] =  newHighlighter
78+                highlighter[key ] =  newHighlighter
8279            }
8380         }
8481      }
@@ -87,9 +84,15 @@ class Cursortracker(
8784   override  fun  caretPositionChanged (event :  CaretEvent ) {
8885      val  canonicalFile =  event.editor.virtualFile?.canonicalFile ? :  return 
8986      val  uri =  canonicalFile.url
90-       val  pos =  Position (event.newPosition.line, event.newPosition.column)
91-       val  range =  Range (pos, pos)
92-       launchCursorRequest(CursorRequest (uri, Collections .singletonList(range)))
87+ 
88+       val  ranges =  event.editor.caretModel
89+          .allCarets
90+          .map {caret -> 
91+             val  pos =  Position (caret.logicalPosition.line, caret.logicalPosition.column)
92+             Range (pos, pos)
93+          }
94+ 
95+       launchCursorRequest(CursorRequest (uri, ranges))
9396   }
9497
9598   private  fun  launchCursorRequest (cursorRequest :  CursorRequest ) {
@@ -103,7 +106,24 @@ class Cursortracker(
103106      }
104107   }
105108
106-    fun  clear () {
109+    suspend   fun  clear () {
107110      remoteProxy =  null 
111+       withUiContext {
112+          synchronized(highlighter) {
113+             for  (entry in  highlighter) {
114+                val  fileEditor =  FileEditorManager .getInstance(project)
115+                   .allEditors
116+                   .filterIsInstance<TextEditor >()
117+                   .filter { editor ->  editor.file.canonicalFile !=  null  }
118+                   .firstOrNull { editor ->  editor.file.canonicalFile!! .url ==  entry.key.documentUri } ? :  continue 
119+ 
120+                for  (rangeHighlighter in  entry.value) {
121+                   fileEditor.editor.markupModel.removeHighlighter(rangeHighlighter)
122+                }
123+             }
124+ 
125+             highlighter.clear()
126+          }
127+       }
108128   }
109129}
0 commit comments