@@ -8,14 +8,21 @@ import { WorkbenchEditorService } from '@opensumi/ide-editor';
8
8
import { EditorActiveResourceStateChangedEvent , EditorGroupCloseEvent } from '@opensumi/ide-editor/lib/browser' ;
9
9
import { WorkbenchEditorServiceImpl } from '@opensumi/ide-editor/lib/browser/workbench-editor.service' ;
10
10
import { ITextModel , ICodeEditor } from '@opensumi/ide-monaco' ;
11
+ import { ICSSStyleService } from '@opensumi/ide-theme' ;
11
12
12
13
import {
13
14
CollaborationServiceForClientPath ,
14
15
ICollaborationService ,
15
16
ICollaborationServiceForClient ,
16
17
ROOM_NAME ,
18
+ UserInfo ,
19
+ UserInfoForCollaborationContribution ,
20
+ Y_REMOTE_SELECTION ,
21
+ Y_REMOTE_SELECTION_HEAD ,
17
22
} from '../common' ;
18
23
24
+ import { getColorByClientID } from './color' ;
25
+ import { CursorWidgetRegistry } from './cursor-widget' ;
19
26
import { TextModelBinding } from './textmodel-binding' ;
20
27
21
28
import './styles.less' ;
@@ -36,6 +43,16 @@ export class CollaborationService extends WithEventBus implements ICollaboration
36
43
@Autowired ( WorkbenchEditorService )
37
44
private workbenchEditorService : WorkbenchEditorServiceImpl ;
38
45
46
+ @Autowired ( ICSSStyleService )
47
+ private cssManager : ICSSStyleService ;
48
+
49
+ private clientIDStyleAddedSet : Set < number > = new Set ( ) ;
50
+
51
+ // hold editor => registry
52
+ private cursorRegistryMap : Map < ICodeEditor , CursorWidgetRegistry > = new Map ( ) ;
53
+
54
+ private userInfo : UserInfo ;
55
+
39
56
private yDoc : Y . Doc ;
40
57
41
58
private yWebSocketProvider : WebsocketProvider ;
@@ -76,15 +93,45 @@ export class CollaborationService extends WithEventBus implements ICollaboration
76
93
this . yTextMap = this . yDoc . getMap ( ) ;
77
94
this . yWebSocketProvider = new WebsocketProvider ( 'ws://127.0.0.1:12345' , ROOM_NAME , this . yDoc ) ; // TODO configurable uri and room name
78
95
this . yTextMap . observe ( this . yMapObserver ) ;
96
+
97
+ // add userInfo to awareness field
98
+ this . yWebSocketProvider . awareness . setLocalStateField ( 'user-info' , this . userInfo ) ;
99
+
79
100
this . logger . debug ( 'Collaboration initialized' ) ;
101
+
102
+ this . yWebSocketProvider . awareness . on ( 'update' , this . updateCSSManagerWhenAwarenessUpdated ) ;
80
103
}
81
104
82
105
destroy ( ) {
106
+ this . yWebSocketProvider . awareness . off ( 'update' , this . updateCSSManagerWhenAwarenessUpdated ) ;
107
+ this . clientIDStyleAddedSet . forEach ( ( clientID ) => {
108
+ this . cssManager . removeClass ( `${ Y_REMOTE_SELECTION } -${ clientID } ` ) ;
109
+ this . cssManager . removeClass ( `${ Y_REMOTE_SELECTION_HEAD } -${ clientID } ` ) ;
110
+ this . cssManager . removeClass ( `${ Y_REMOTE_SELECTION_HEAD } -${ clientID } ::after` ) ;
111
+ } ) ;
83
112
this . yTextMap . unobserve ( this . yMapObserver ) ;
84
113
this . yWebSocketProvider . disconnect ( ) ;
85
114
this . bindingMap . forEach ( ( binding ) => binding . dispose ( ) ) ;
86
115
}
87
116
117
+ getUseInfo ( ) : UserInfo {
118
+ if ( ! this . userInfo ) {
119
+ throw new Error ( 'User info is not registered' ) ;
120
+ }
121
+
122
+ return this . userInfo ;
123
+ }
124
+
125
+ setUserInfo ( contribution : UserInfoForCollaborationContribution ) {
126
+ if ( this . userInfo ) {
127
+ throw new Error ( 'User info is already registered' ) ;
128
+ }
129
+
130
+ if ( contribution . info ) {
131
+ this . userInfo = contribution . info ;
132
+ }
133
+ }
134
+
88
135
undoOnCurrentResource ( ) {
89
136
const uri = this . workbenchEditorService . currentResource ?. uri . toString ( ) ;
90
137
if ( uri && this . bindingMap . has ( uri ) ) {
@@ -131,11 +178,56 @@ export class CollaborationService extends WithEventBus implements ICollaboration
131
178
if ( binding ) {
132
179
binding . dispose ( ) ;
133
180
this . bindingMap . delete ( uri ) ;
134
- // todo ref = ref - 1 (through back service)
135
181
this . logger . debug ( 'Removed binding' ) ;
136
182
}
137
183
}
138
184
185
+ public getCursorWidgetRegistry ( editor : ICodeEditor ) {
186
+ return this . cursorRegistryMap . get ( editor ) ;
187
+ }
188
+
189
+ private updateCSSManagerWhenAwarenessUpdated = ( changes : {
190
+ added : number [ ] ;
191
+ updated : number [ ] ;
192
+ removed : number [ ] ;
193
+ } ) => {
194
+ if ( changes . removed . length > 0 ) {
195
+ changes . removed . forEach ( ( clientID ) => {
196
+ this . cssManager . removeClass ( `${ Y_REMOTE_SELECTION } -${ clientID } ` ) ;
197
+ this . cssManager . removeClass ( `${ Y_REMOTE_SELECTION_HEAD } -${ clientID } ` ) ;
198
+ this . cssManager . removeClass ( `${ Y_REMOTE_SELECTION_HEAD } -${ clientID } ::after` ) ;
199
+ this . clientIDStyleAddedSet . delete ( clientID ) ;
200
+ } ) ;
201
+ }
202
+ if ( changes . added . length > 0 || changes . updated . length > 0 ) {
203
+ changes . added . forEach ( ( clientID ) => {
204
+ if ( ! this . clientIDStyleAddedSet . has ( clientID ) ) {
205
+ const color = getColorByClientID ( clientID ) ;
206
+ this . cssManager . addClass ( `${ Y_REMOTE_SELECTION } -${ clientID } ` , {
207
+ backgroundColor : color ,
208
+ opacity : '0.25' ,
209
+ } ) ;
210
+ this . cssManager . addClass ( `${ Y_REMOTE_SELECTION_HEAD } -${ clientID } ` , {
211
+ position : 'absolute' ,
212
+ borderLeft : `${ color } solid 2px` ,
213
+ borderBottom : `${ color } solid 2px` ,
214
+ borderTop : `${ color } solid 2px` ,
215
+ height : '100%' ,
216
+ boxSizing : 'border-box' ,
217
+ } ) ;
218
+ this . cssManager . addClass ( `${ Y_REMOTE_SELECTION_HEAD } -${ clientID } ::after` , {
219
+ position : 'absolute' ,
220
+ content : ' ' ,
221
+ border : `3px solid ${ color } ` ,
222
+ left : '-4px' ,
223
+ top : '-5px' ,
224
+ } ) ;
225
+ this . clientIDStyleAddedSet . add ( clientID ) ;
226
+ }
227
+ } ) ;
228
+ }
229
+ } ;
230
+
139
231
@OnEvent ( EditorGroupCloseEvent )
140
232
private groupCloseHandler ( e : EditorGroupCloseEvent ) {
141
233
this . logger . debug ( 'Group close tabs' , e ) ;
@@ -167,6 +259,15 @@ export class CollaborationService extends WithEventBus implements ICollaboration
167
259
const monacoEditor = this . workbenchEditorService . currentCodeEditor ?. monacoEditor ;
168
260
const binding = this . getBinding ( uri ) ;
169
261
262
+ // check if editor has its widgetRegistry
263
+ if ( monacoEditor && ! this . cursorRegistryMap . has ( monacoEditor ) ) {
264
+ const registry = this . injector . get ( CursorWidgetRegistry , [ monacoEditor , this . yWebSocketProvider . awareness ] ) ;
265
+ this . cursorRegistryMap . set ( monacoEditor , registry ) ;
266
+ monacoEditor . onDidDispose ( ( ) => {
267
+ registry . destroy ( ) ;
268
+ } ) ;
269
+ }
270
+
170
271
// check if there exists any binding
171
272
if ( ! binding ) {
172
273
if ( this . yTextMap . has ( uri ) ) {
0 commit comments