@theia/collaboration 1.67.0-next.56 → 1.67.0-next.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/lib/browser/collaboration-color-service.d.ts +27 -0
  2. package/lib/browser/collaboration-color-service.d.ts.map +1 -0
  3. package/lib/browser/collaboration-color-service.js +76 -0
  4. package/lib/browser/collaboration-color-service.js.map +1 -0
  5. package/lib/browser/collaboration-file-system-provider.d.ts +37 -0
  6. package/lib/browser/collaboration-file-system-provider.d.ts.map +1 -0
  7. package/lib/browser/collaboration-file-system-provider.js +107 -0
  8. package/lib/browser/collaboration-file-system-provider.js.map +1 -0
  9. package/lib/browser/collaboration-frontend-contribution.d.ts +50 -0
  10. package/lib/browser/collaboration-frontend-contribution.d.ts.map +1 -0
  11. package/lib/browser/collaboration-frontend-contribution.js +432 -0
  12. package/lib/browser/collaboration-frontend-contribution.js.map +1 -0
  13. package/lib/browser/collaboration-frontend-module.d.ts +4 -0
  14. package/lib/browser/collaboration-frontend-module.d.ts.map +1 -0
  15. package/lib/browser/collaboration-frontend-module.js +40 -0
  16. package/lib/browser/collaboration-frontend-module.js.map +1 -0
  17. package/lib/browser/collaboration-instance.d.ts +94 -0
  18. package/lib/browser/collaboration-instance.d.ts.map +1 -0
  19. package/lib/browser/collaboration-instance.js +806 -0
  20. package/lib/browser/collaboration-instance.js.map +1 -0
  21. package/lib/browser/collaboration-utils.d.ts +8 -0
  22. package/lib/browser/collaboration-utils.d.ts.map +1 -0
  23. package/lib/browser/collaboration-utils.js +63 -0
  24. package/lib/browser/collaboration-utils.js.map +1 -0
  25. package/lib/browser/collaboration-workspace-service.d.ts +12 -0
  26. package/lib/browser/collaboration-workspace-service.d.ts.map +1 -0
  27. package/lib/browser/collaboration-workspace-service.js +67 -0
  28. package/lib/browser/collaboration-workspace-service.js.map +1 -0
  29. package/lib/common/collaboration-preferences.d.ts +3 -0
  30. package/lib/common/collaboration-preferences.d.ts.map +1 -0
  31. package/lib/common/collaboration-preferences.js +31 -0
  32. package/lib/common/collaboration-preferences.js.map +1 -0
  33. package/lib/node/collaboration-backend-module.d.ts +4 -0
  34. package/lib/node/collaboration-backend-module.d.ts.map +1 -0
  35. package/lib/node/collaboration-backend-module.js +24 -0
  36. package/lib/node/collaboration-backend-module.js.map +1 -0
  37. package/lib/package.spec.d.ts +1 -0
  38. package/lib/package.spec.d.ts.map +1 -0
  39. package/lib/package.spec.js +26 -0
  40. package/lib/package.spec.js.map +1 -0
  41. package/package.json +7 -7
@@ -0,0 +1,806 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2024 TypeFox and others.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.CollaborationInstance = exports.COLLABORATION_SELECTION_INVERTED = exports.COLLABORATION_SELECTION_MARKER = exports.COLLABORATION_SELECTION = exports.createCollaborationInstanceContainer = exports.CollaborationInstanceOptions = exports.CollaborationInstanceFactory = void 0;
19
+ const tslib_1 = require("tslib");
20
+ const types = require("open-collaboration-protocol");
21
+ const Y = require("yjs");
22
+ const awarenessProtocol = require("y-protocols/awareness");
23
+ const core_1 = require("@theia/core");
24
+ const inversify_1 = require("@theia/core/shared/inversify");
25
+ const application_shell_1 = require("@theia/core/lib/browser/shell/application-shell");
26
+ const editor_manager_1 = require("@theia/editor/lib/browser/editor-manager");
27
+ const file_service_1 = require("@theia/filesystem/lib/browser/file-service");
28
+ const monaco_text_model_service_1 = require("@theia/monaco/lib/browser/monaco-text-model-service");
29
+ const collaboration_workspace_service_1 = require("./collaboration-workspace-service");
30
+ const monaco_editor_core_1 = require("@theia/monaco-editor-core");
31
+ const monaco_editor_1 = require("@theia/monaco/lib/browser/monaco-editor");
32
+ const promise_util_1 = require("@theia/core/lib/common/promise-util");
33
+ const browser_1 = require("@theia/editor/lib/browser");
34
+ const browser_2 = require("@theia/core/lib/browser");
35
+ const collaboration_file_system_provider_1 = require("./collaboration-file-system-provider");
36
+ const collaboration_color_service_1 = require("./collaboration-color-service");
37
+ const buffer_1 = require("@theia/core/lib/common/buffer");
38
+ const open_collaboration_yjs_1 = require("open-collaboration-yjs");
39
+ const mutex_1 = require("lib0/mutex");
40
+ const collaboration_utils_1 = require("./collaboration-utils");
41
+ const debounce = require("@theia/core/shared/lodash.debounce");
42
+ exports.CollaborationInstanceFactory = Symbol('CollaborationInstanceFactory');
43
+ exports.CollaborationInstanceOptions = Symbol('CollaborationInstanceOptions');
44
+ function createCollaborationInstanceContainer(parent, options) {
45
+ const child = new inversify_1.Container();
46
+ child.parent = parent;
47
+ child.bind(CollaborationInstance).toSelf().inTransientScope();
48
+ child.bind(exports.CollaborationInstanceOptions).toConstantValue(options);
49
+ return child;
50
+ }
51
+ exports.createCollaborationInstanceContainer = createCollaborationInstanceContainer;
52
+ exports.COLLABORATION_SELECTION = 'theia-collaboration-selection';
53
+ exports.COLLABORATION_SELECTION_MARKER = 'theia-collaboration-selection-marker';
54
+ exports.COLLABORATION_SELECTION_INVERTED = 'theia-collaboration-selection-inverted';
55
+ let CollaborationInstance = class CollaborationInstance {
56
+ constructor() {
57
+ this.identity = new promise_util_1.Deferred();
58
+ this.peers = new Map();
59
+ this.yjs = new Y.Doc();
60
+ this.yjsAwareness = new awarenessProtocol.Awareness(this.yjs);
61
+ this.colorIndex = 0;
62
+ this.editorDecorations = new Map();
63
+ this.permissions = {
64
+ readonly: false
65
+ };
66
+ this.onDidCloseEmitter = new core_1.Emitter();
67
+ this.toDispose = new core_1.DisposableCollection();
68
+ this._readonly = false;
69
+ this.yjsMutex = (0, mutex_1.createMutex)();
70
+ }
71
+ get onDidClose() {
72
+ return this.onDidCloseEmitter.event;
73
+ }
74
+ get readonly() {
75
+ return this._readonly;
76
+ }
77
+ set readonly(value) {
78
+ var _a;
79
+ if (value !== this.readonly) {
80
+ if (this.options.role === 'guest' && this.fileSystem) {
81
+ this.fileSystem.readonly = value;
82
+ }
83
+ else if (this.options.role === 'host') {
84
+ this.options.connection.room.updatePermissions({
85
+ ...((_a = this.permissions) !== null && _a !== void 0 ? _a : {}),
86
+ readonly: value
87
+ });
88
+ }
89
+ if (this.permissions) {
90
+ this.permissions.readonly = value;
91
+ }
92
+ this._readonly = value;
93
+ }
94
+ }
95
+ get isHost() {
96
+ return this.options.role === 'host';
97
+ }
98
+ get host() {
99
+ return Array.from(this.peers.values()).find(e => e.peer.host).peer;
100
+ }
101
+ init() {
102
+ const connection = this.options.connection;
103
+ connection.onDisconnect(() => this.dispose());
104
+ connection.onConnectionError(message => {
105
+ this.messageService.error(message);
106
+ this.dispose();
107
+ });
108
+ this.yjsProvider = new open_collaboration_yjs_1.OpenCollaborationYjsProvider(connection, this.yjs, this.yjsAwareness);
109
+ this.yjsProvider.connect();
110
+ this.toDispose.push(core_1.Disposable.create(() => this.yjs.destroy()));
111
+ this.toDispose.push(this.yjsProvider);
112
+ this.toDispose.push(connection);
113
+ this.toDispose.push(this.onDidCloseEmitter);
114
+ this.registerProtocolEvents(connection);
115
+ this.registerEditorEvents(connection);
116
+ this.registerFileSystemEvents(connection);
117
+ if (this.isHost) {
118
+ this.registerFileSystemChanges();
119
+ }
120
+ }
121
+ registerProtocolEvents(connection) {
122
+ connection.peer.onJoinRequest(async (_, user) => {
123
+ var _a, _b;
124
+ const allow = core_1.nls.localizeByDefault('Allow');
125
+ const deny = core_1.nls.localizeByDefault('Deny');
126
+ const result = await this.messageService.info(core_1.nls.localize('theia/collaboration/userWantsToJoin', "User '{0}' wants to join the collaboration room", user.email ? `${user.name} (${user.email})` : user.name), allow, deny);
127
+ if (result === allow) {
128
+ const roots = await this.workspaceService.roots;
129
+ return {
130
+ workspace: {
131
+ name: (_b = (_a = this.workspaceService.workspace) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : core_1.nls.localize('theia/collaboration/collaboration', 'Collaboration'),
132
+ folders: roots.map(e => e.name)
133
+ }
134
+ };
135
+ }
136
+ else {
137
+ return undefined;
138
+ }
139
+ });
140
+ connection.room.onJoin(async (_, peer) => {
141
+ var _a, _b;
142
+ this.addPeer(peer);
143
+ if (this.isHost) {
144
+ const roots = await this.workspaceService.roots;
145
+ const data = {
146
+ protocol: types.VERSION,
147
+ host: await this.identity.promise,
148
+ guests: Array.from(this.peers.values()).map(e => e.peer),
149
+ capabilities: {},
150
+ permissions: this.permissions,
151
+ workspace: {
152
+ name: (_b = (_a = this.workspaceService.workspace) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : core_1.nls.localize('theia/collaboration/collaboration', 'Collaboration'),
153
+ folders: roots.map(e => e.name)
154
+ }
155
+ };
156
+ connection.peer.init(peer.id, data);
157
+ }
158
+ });
159
+ connection.room.onLeave((_, peer) => {
160
+ var _a;
161
+ (_a = this.peers.get(peer.id)) === null || _a === void 0 ? void 0 : _a.dispose();
162
+ });
163
+ connection.room.onClose(() => {
164
+ this.dispose();
165
+ });
166
+ connection.room.onPermissions((_, permissions) => {
167
+ if (this.fileSystem) {
168
+ this.fileSystem.readonly = permissions.readonly;
169
+ }
170
+ });
171
+ connection.peer.onInfo((_, peer) => {
172
+ this.yjsAwareness.setLocalStateField('peer', peer.id);
173
+ this.identity.resolve(peer);
174
+ });
175
+ connection.peer.onInit(async (_, data) => {
176
+ await this.initialize(data);
177
+ });
178
+ }
179
+ registerEditorEvents(connection) {
180
+ for (const model of this.monacoModelService.models) {
181
+ if (this.isSharedResource(new core_1.URI(model.uri))) {
182
+ this.registerModelUpdate(model);
183
+ }
184
+ }
185
+ this.toDispose.push(this.monacoModelService.onDidCreate(newModel => {
186
+ if (this.isSharedResource(new core_1.URI(newModel.uri))) {
187
+ this.registerModelUpdate(newModel);
188
+ }
189
+ }));
190
+ this.toDispose.push(this.editorManager.onCreated(widget => {
191
+ if (this.isSharedResource(widget.getResourceUri())) {
192
+ this.registerPresenceUpdate(widget);
193
+ }
194
+ }));
195
+ this.getOpenEditors().forEach(widget => {
196
+ if (this.isSharedResource(widget.getResourceUri())) {
197
+ this.registerPresenceUpdate(widget);
198
+ }
199
+ });
200
+ this.shell.onDidChangeActiveWidget(e => {
201
+ if (e.newValue instanceof browser_1.EditorWidget) {
202
+ this.updateEditorPresence(e.newValue);
203
+ }
204
+ });
205
+ this.yjsAwareness.on('change', () => {
206
+ this.rerenderPresence();
207
+ });
208
+ connection.editor.onOpen(async (_, path) => {
209
+ const uri = this.utils.getResourceUri(path);
210
+ if (uri) {
211
+ await this.openUri(uri);
212
+ }
213
+ else {
214
+ throw new Error('Could find file: ' + path);
215
+ }
216
+ return undefined;
217
+ });
218
+ }
219
+ isSharedResource(resource) {
220
+ if (!resource) {
221
+ return false;
222
+ }
223
+ return this.isHost ? resource.scheme === 'file' : resource.scheme === collaboration_file_system_provider_1.CollaborationURI.scheme;
224
+ }
225
+ registerFileSystemEvents(connection) {
226
+ connection.fs.onReadFile(async (_, path) => {
227
+ const uri = this.utils.getResourceUri(path);
228
+ if (uri) {
229
+ const content = await this.fileService.readFile(uri);
230
+ return {
231
+ content: content.value.buffer
232
+ };
233
+ }
234
+ else {
235
+ throw new Error('Could find file: ' + path);
236
+ }
237
+ });
238
+ connection.fs.onReaddir(async (_, path) => {
239
+ const uri = this.utils.getResourceUri(path);
240
+ if (uri) {
241
+ const resolved = await this.fileService.resolve(uri);
242
+ if (resolved.children) {
243
+ const dir = {};
244
+ for (const child of resolved.children) {
245
+ dir[child.name] = child.isDirectory ? types.FileType.Directory : types.FileType.File;
246
+ }
247
+ return dir;
248
+ }
249
+ else {
250
+ return {};
251
+ }
252
+ }
253
+ else {
254
+ throw new Error('Could find directory: ' + path);
255
+ }
256
+ });
257
+ connection.fs.onStat(async (_, path) => {
258
+ const uri = this.utils.getResourceUri(path);
259
+ if (uri) {
260
+ const content = await this.fileService.resolve(uri, {
261
+ resolveMetadata: true
262
+ });
263
+ return {
264
+ type: content.isDirectory ? types.FileType.Directory : types.FileType.File,
265
+ ctime: content.ctime,
266
+ mtime: content.mtime,
267
+ size: content.size,
268
+ permissions: content.isReadonly ? types.FilePermission.Readonly : undefined
269
+ };
270
+ }
271
+ else {
272
+ throw new Error('Could find file: ' + path);
273
+ }
274
+ });
275
+ connection.fs.onWriteFile(async (_, path, data) => {
276
+ const uri = this.utils.getResourceUri(path);
277
+ if (uri) {
278
+ const model = this.getModel(uri);
279
+ if (model) {
280
+ const content = new TextDecoder().decode(data.content);
281
+ if (content !== model.getText()) {
282
+ model.textEditorModel.setValue(content);
283
+ }
284
+ await model.save({ saveReason: browser_2.SaveReason.Manual });
285
+ }
286
+ else {
287
+ await this.fileService.createFile(uri, buffer_1.BinaryBuffer.wrap(data.content));
288
+ }
289
+ }
290
+ else {
291
+ throw new Error('Could find file: ' + path);
292
+ }
293
+ });
294
+ connection.fs.onMkdir(async (_, path) => {
295
+ const uri = this.utils.getResourceUri(path);
296
+ if (uri) {
297
+ await this.fileService.createFolder(uri);
298
+ }
299
+ else {
300
+ throw new Error('Could find path: ' + path);
301
+ }
302
+ });
303
+ connection.fs.onDelete(async (_, path) => {
304
+ const uri = this.utils.getResourceUri(path);
305
+ if (uri) {
306
+ await this.fileService.delete(uri);
307
+ }
308
+ else {
309
+ throw new Error('Could find entry: ' + path);
310
+ }
311
+ });
312
+ connection.fs.onRename(async (_, from, to) => {
313
+ const fromUri = this.utils.getResourceUri(from);
314
+ const toUri = this.utils.getResourceUri(to);
315
+ if (fromUri && toUri) {
316
+ await this.fileService.move(fromUri, toUri);
317
+ }
318
+ else {
319
+ throw new Error('Could find entries: ' + from + ' -> ' + to);
320
+ }
321
+ });
322
+ connection.fs.onChange(async (_, event) => {
323
+ // Only guests need to handle file system changes
324
+ if (!this.isHost && this.fileSystem) {
325
+ const changes = [];
326
+ for (const change of event.changes) {
327
+ const uri = this.utils.getResourceUri(change.path);
328
+ if (uri) {
329
+ changes.push({
330
+ type: change.type === types.FileChangeEventType.Create
331
+ ? 1 /* FileChangeType.ADDED */
332
+ : change.type === types.FileChangeEventType.Update
333
+ ? 0 /* FileChangeType.UPDATED */
334
+ : 2 /* FileChangeType.DELETED */,
335
+ resource: uri
336
+ });
337
+ }
338
+ }
339
+ this.fileSystem.triggerEvent(changes);
340
+ }
341
+ });
342
+ }
343
+ rerenderPresence(...widgets) {
344
+ const decorations = new Map();
345
+ const states = this.yjsAwareness.getStates();
346
+ for (const [clientID, state] of states.entries()) {
347
+ if (clientID === this.yjs.clientID) {
348
+ // Ignore own awareness state
349
+ continue;
350
+ }
351
+ const peer = state.peer;
352
+ if (!state.selection || !this.peers.has(peer)) {
353
+ continue;
354
+ }
355
+ if (!types.ClientTextSelection.is(state.selection)) {
356
+ continue;
357
+ }
358
+ const { path, textSelections } = state.selection;
359
+ const selection = textSelections[0];
360
+ if (!selection) {
361
+ continue;
362
+ }
363
+ const uri = this.utils.getResourceUri(path);
364
+ if (uri) {
365
+ const model = this.getModel(uri);
366
+ if (model) {
367
+ let existing = decorations.get(path);
368
+ if (!existing) {
369
+ existing = [];
370
+ decorations.set(path, existing);
371
+ }
372
+ const forward = selection.direction === types.SelectionDirection.LeftToRight;
373
+ let startIndex = Y.createAbsolutePositionFromRelativePosition(selection.start, this.yjs);
374
+ let endIndex = Y.createAbsolutePositionFromRelativePosition(selection.end, this.yjs);
375
+ if (startIndex && endIndex) {
376
+ if (startIndex.index > endIndex.index) {
377
+ [startIndex, endIndex] = [endIndex, startIndex];
378
+ }
379
+ const start = model.positionAt(startIndex.index);
380
+ const end = model.positionAt(endIndex.index);
381
+ const inverted = (forward && end.line === 0) || (!forward && start.line === 0);
382
+ const range = {
383
+ start,
384
+ end
385
+ };
386
+ const contentClassNames = [exports.COLLABORATION_SELECTION_MARKER, `${exports.COLLABORATION_SELECTION_MARKER}-${peer}`];
387
+ if (inverted) {
388
+ contentClassNames.push(exports.COLLABORATION_SELECTION_INVERTED);
389
+ }
390
+ const item = {
391
+ range,
392
+ options: {
393
+ className: `${exports.COLLABORATION_SELECTION} ${exports.COLLABORATION_SELECTION}-${peer}`,
394
+ beforeContentClassName: !forward ? contentClassNames.join(' ') : undefined,
395
+ afterContentClassName: forward ? contentClassNames.join(' ') : undefined,
396
+ stickiness: browser_1.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
397
+ }
398
+ };
399
+ existing.push(item);
400
+ }
401
+ }
402
+ }
403
+ }
404
+ this.rerenderPresenceDecorations(decorations, ...widgets);
405
+ }
406
+ rerenderPresenceDecorations(decorations, ...widgets) {
407
+ var _a, _b;
408
+ for (const editor of new Set(this.getOpenEditors().concat(widgets))) {
409
+ const uri = editor.getResourceUri();
410
+ const path = this.utils.getProtocolPath(uri);
411
+ if (path) {
412
+ const old = (_a = this.editorDecorations.get(editor)) !== null && _a !== void 0 ? _a : [];
413
+ this.editorDecorations.set(editor, editor.editor.deltaDecorations({
414
+ newDecorations: (_b = decorations.get(path)) !== null && _b !== void 0 ? _b : [],
415
+ oldDecorations: old
416
+ }));
417
+ }
418
+ }
419
+ }
420
+ registerFileSystemChanges() {
421
+ // Event listener for disk based events
422
+ this.fileService.onDidFilesChange(event => {
423
+ const changes = [];
424
+ for (const change of event.changes) {
425
+ const path = this.utils.getProtocolPath(change.resource);
426
+ if (path) {
427
+ let type;
428
+ if (change.type === 1 /* FileChangeType.ADDED */) {
429
+ type = types.FileChangeEventType.Create;
430
+ }
431
+ else if (change.type === 2 /* FileChangeType.DELETED */) {
432
+ type = types.FileChangeEventType.Delete;
433
+ }
434
+ // Updates to files on disk are not sent
435
+ if (type !== undefined) {
436
+ changes.push({
437
+ path,
438
+ type
439
+ });
440
+ }
441
+ }
442
+ }
443
+ if (changes.length) {
444
+ this.options.connection.fs.change({ changes });
445
+ }
446
+ });
447
+ // Event listener for user based events
448
+ this.fileService.onDidRunOperation(operation => {
449
+ const path = this.utils.getProtocolPath(operation.resource);
450
+ if (!path) {
451
+ return;
452
+ }
453
+ let type = types.FileChangeEventType.Update;
454
+ if (operation.isOperation(0 /* FileOperation.CREATE */) || operation.isOperation(3 /* FileOperation.COPY */)) {
455
+ type = types.FileChangeEventType.Create;
456
+ }
457
+ else if (operation.isOperation(1 /* FileOperation.DELETE */)) {
458
+ type = types.FileChangeEventType.Delete;
459
+ }
460
+ this.options.connection.fs.change({
461
+ changes: [{
462
+ path,
463
+ type
464
+ }]
465
+ });
466
+ });
467
+ }
468
+ async registerPresenceUpdate(widget) {
469
+ const uri = widget.getResourceUri();
470
+ const path = this.utils.getProtocolPath(uri);
471
+ if (path) {
472
+ if (!this.isHost) {
473
+ this.options.connection.editor.open(this.host.id, path);
474
+ }
475
+ let currentSelection = widget.editor.selection;
476
+ // // Update presence information when the selection changes
477
+ const selectionChange = widget.editor.onSelectionChanged(selection => {
478
+ if (!this.rangeEqual(currentSelection, selection)) {
479
+ this.updateEditorPresence(widget);
480
+ currentSelection = selection;
481
+ }
482
+ });
483
+ const widgetDispose = widget.onDidDispose(() => {
484
+ var _a;
485
+ widgetDispose.dispose();
486
+ selectionChange.dispose();
487
+ // Remove presence information when the editor closes
488
+ const state = this.yjsAwareness.getLocalState();
489
+ if (((_a = state === null || state === void 0 ? void 0 : state.currentSelection) === null || _a === void 0 ? void 0 : _a.path) === path) {
490
+ delete state.currentSelection;
491
+ }
492
+ this.yjsAwareness.setLocalState(state);
493
+ });
494
+ this.toDispose.push(selectionChange);
495
+ this.toDispose.push(widgetDispose);
496
+ this.rerenderPresence(widget);
497
+ }
498
+ }
499
+ updateEditorPresence(widget) {
500
+ const uri = widget.getResourceUri();
501
+ const path = this.utils.getProtocolPath(uri);
502
+ if (path) {
503
+ const ytext = this.yjs.getText(path);
504
+ const selection = widget.editor.selection;
505
+ let start = widget.editor.document.offsetAt(selection.start);
506
+ let end = widget.editor.document.offsetAt(selection.end);
507
+ if (start > end) {
508
+ [start, end] = [end, start];
509
+ }
510
+ const direction = selection.direction === 'ltr'
511
+ ? types.SelectionDirection.LeftToRight
512
+ : types.SelectionDirection.RightToLeft;
513
+ const editorSelection = {
514
+ start: Y.createRelativePositionFromTypeIndex(ytext, start),
515
+ end: Y.createRelativePositionFromTypeIndex(ytext, end),
516
+ direction
517
+ };
518
+ const textSelection = {
519
+ path,
520
+ textSelections: [editorSelection]
521
+ };
522
+ this.setSharedSelection(textSelection);
523
+ }
524
+ }
525
+ setSharedSelection(selection) {
526
+ this.yjsAwareness.setLocalStateField('selection', selection);
527
+ }
528
+ rangeEqual(a, b) {
529
+ return a.start.line === b.start.line
530
+ && a.start.character === b.start.character
531
+ && a.end.line === b.end.line
532
+ && a.end.character === b.end.character;
533
+ }
534
+ async initialize(data) {
535
+ this.permissions = data.permissions;
536
+ this.readonly = data.permissions.readonly;
537
+ for (const peer of [...data.guests, data.host]) {
538
+ this.addPeer(peer);
539
+ }
540
+ this.fileSystem = new collaboration_file_system_provider_1.CollaborationFileSystemProvider(this.options.connection, data.host, this.yjs);
541
+ this.fileSystem.readonly = this.readonly;
542
+ this.toDispose.push(this.fileService.registerProvider(collaboration_file_system_provider_1.CollaborationURI.scheme, this.fileSystem));
543
+ const workspaceDisposable = await this.workspaceService.setHostWorkspace(data.workspace, this.options.connection);
544
+ this.toDispose.push(workspaceDisposable);
545
+ }
546
+ addPeer(peer) {
547
+ const collection = new core_1.DisposableCollection();
548
+ collection.push(this.createPeerStyleSheet(peer));
549
+ collection.push(core_1.Disposable.create(() => this.peers.delete(peer.id)));
550
+ const disposablePeer = {
551
+ peer,
552
+ dispose: () => collection.dispose()
553
+ };
554
+ this.peers.set(peer.id, disposablePeer);
555
+ }
556
+ createPeerStyleSheet(peer) {
557
+ const style = browser_2.DecorationStyle.createStyleElement(`${peer.id}-collaboration-selection`);
558
+ const colors = this.collaborationColorService.getColors();
559
+ const sheet = style.sheet;
560
+ const color = colors[this.colorIndex++ % colors.length];
561
+ const colorString = `rgb(${color.r}, ${color.g}, ${color.b})`;
562
+ sheet.insertRule(`
563
+ .${exports.COLLABORATION_SELECTION}-${peer.id} {
564
+ opacity: 0.2;
565
+ background: ${colorString};
566
+ }
567
+ `);
568
+ sheet.insertRule(`
569
+ .${exports.COLLABORATION_SELECTION_MARKER}-${peer.id} {
570
+ background: ${colorString};
571
+ border-color: ${colorString};
572
+ }`);
573
+ sheet.insertRule(`
574
+ .${exports.COLLABORATION_SELECTION_MARKER}-${peer.id}::after {
575
+ content: "${peer.name}";
576
+ background: ${colorString};
577
+ color: ${this.collaborationColorService.requiresDarkFont(color)
578
+ ? this.collaborationColorService.dark
579
+ : this.collaborationColorService.light};
580
+ z-index: ${(100 + this.colorIndex).toFixed()}
581
+ }`);
582
+ return core_1.Disposable.create(() => style.remove());
583
+ }
584
+ getOpenEditors(uri) {
585
+ const widgets = this.shell.widgets;
586
+ let editors = widgets.filter(e => e instanceof browser_1.EditorWidget);
587
+ if (uri) {
588
+ const uriString = uri.toString();
589
+ editors = editors.filter(e => { var _a; return ((_a = e.getResourceUri()) === null || _a === void 0 ? void 0 : _a.toString()) === uriString; });
590
+ }
591
+ return editors;
592
+ }
593
+ createSelectionFromRelative(selection, model) {
594
+ const start = Y.createAbsolutePositionFromRelativePosition(selection.start, this.yjs);
595
+ const end = Y.createAbsolutePositionFromRelativePosition(selection.end, this.yjs);
596
+ if (start && end) {
597
+ return {
598
+ start: model.positionAt(start.index),
599
+ end: model.positionAt(end.index),
600
+ direction: selection.direction === types.SelectionDirection.LeftToRight ? 'ltr' : 'rtl'
601
+ };
602
+ }
603
+ return undefined;
604
+ }
605
+ createRelativeSelection(selection, model, ytext) {
606
+ const start = Y.createRelativePositionFromTypeIndex(ytext, model.offsetAt(selection.start));
607
+ const end = Y.createRelativePositionFromTypeIndex(ytext, model.offsetAt(selection.end));
608
+ return {
609
+ start,
610
+ end,
611
+ direction: selection.direction === 'ltr'
612
+ ? types.SelectionDirection.LeftToRight
613
+ : types.SelectionDirection.RightToLeft
614
+ };
615
+ }
616
+ registerModelUpdate(model) {
617
+ let updating = false;
618
+ const modelPath = this.utils.getProtocolPath(new core_1.URI(model.uri));
619
+ if (!modelPath) {
620
+ return;
621
+ }
622
+ const unknownModel = !this.yjs.share.has(modelPath);
623
+ const ytext = this.yjs.getText(modelPath);
624
+ const modelText = model.textEditorModel.getValue();
625
+ if (this.isHost && unknownModel) {
626
+ // If we are hosting the room, set the initial content
627
+ // First off, reset the shared content to be empty
628
+ // This has the benefit of effectively clearing the memory of the shared content across all peers
629
+ // This is important because the shared content accumulates changes/memory usage over time
630
+ this.resetYjsText(ytext, modelText);
631
+ }
632
+ else {
633
+ this.options.connection.editor.open(this.host.id, modelPath);
634
+ }
635
+ // The Ytext instance is our source of truth for the model content
636
+ // Sometimes (especially after a lot of sequential undo/redo operations) our model content can get out of sync
637
+ // This resyncs the model content with the Ytext content after a delay
638
+ const resyncDebounce = debounce(() => {
639
+ this.yjsMutex(() => {
640
+ const newContent = ytext.toString();
641
+ if (model.textEditorModel.getValue() !== newContent) {
642
+ updating = true;
643
+ this.softReplaceModel(model, newContent);
644
+ updating = false;
645
+ }
646
+ });
647
+ }, 200);
648
+ const disposable = new core_1.DisposableCollection();
649
+ disposable.push(model.onDidChangeContent(e => {
650
+ if (updating) {
651
+ return;
652
+ }
653
+ this.yjsMutex(() => {
654
+ this.yjs.transact(() => {
655
+ for (const change of e.contentChanges) {
656
+ ytext.delete(change.rangeOffset, change.rangeLength);
657
+ ytext.insert(change.rangeOffset, change.text);
658
+ }
659
+ });
660
+ resyncDebounce();
661
+ });
662
+ }));
663
+ const observer = (textEvent) => {
664
+ if (textEvent.transaction.local || model.getText() === ytext.toString()) {
665
+ // Ignore local changes and changes that are already reflected in the model
666
+ return;
667
+ }
668
+ this.yjsMutex(() => {
669
+ updating = true;
670
+ try {
671
+ let index = 0;
672
+ const operations = [];
673
+ textEvent.delta.forEach(delta => {
674
+ if (delta.retain !== undefined) {
675
+ index += delta.retain;
676
+ }
677
+ else if (delta.insert !== undefined) {
678
+ const pos = model.textEditorModel.getPositionAt(index);
679
+ const range = new monaco_editor_core_1.Range(pos.lineNumber, pos.column, pos.lineNumber, pos.column);
680
+ const insert = delta.insert;
681
+ operations.push({ range, text: insert });
682
+ index += insert.length;
683
+ }
684
+ else if (delta.delete !== undefined) {
685
+ const pos = model.textEditorModel.getPositionAt(index);
686
+ const endPos = model.textEditorModel.getPositionAt(index + delta.delete);
687
+ const range = new monaco_editor_core_1.Range(pos.lineNumber, pos.column, endPos.lineNumber, endPos.column);
688
+ operations.push({ range, text: '' });
689
+ }
690
+ });
691
+ this.pushChangesToModel(model, operations);
692
+ }
693
+ catch (err) {
694
+ console.error(err);
695
+ }
696
+ resyncDebounce();
697
+ updating = false;
698
+ });
699
+ };
700
+ ytext.observe(observer);
701
+ disposable.push(core_1.Disposable.create(() => ytext.unobserve(observer)));
702
+ model.onDispose(() => disposable.dispose());
703
+ }
704
+ resetYjsText(yjsText, text) {
705
+ this.yjs.transact(() => {
706
+ yjsText.delete(0, yjsText.length);
707
+ yjsText.insert(0, text);
708
+ });
709
+ }
710
+ getModel(uri) {
711
+ const existing = this.monacoModelService.models.find(e => e.uri === uri.toString());
712
+ if (existing) {
713
+ return existing;
714
+ }
715
+ else {
716
+ return undefined;
717
+ }
718
+ }
719
+ pushChangesToModel(model, changes) {
720
+ var _a;
721
+ const editor = monaco_editor_1.MonacoEditor.findByDocument(this.editorManager, model)[0];
722
+ const cursorState = (_a = editor === null || editor === void 0 ? void 0 : editor.getControl().getSelections()) !== null && _a !== void 0 ? _a : [];
723
+ model.textEditorModel.pushStackElement();
724
+ try {
725
+ model.textEditorModel.pushEditOperations(cursorState, changes, () => cursorState);
726
+ model.textEditorModel.pushStackElement();
727
+ }
728
+ catch (err) {
729
+ console.error(err);
730
+ }
731
+ }
732
+ softReplaceModel(model, text) {
733
+ this.pushChangesToModel(model, [{
734
+ range: model.textEditorModel.getFullModelRange(),
735
+ text,
736
+ forceMoveMarkers: false
737
+ }]);
738
+ }
739
+ async openUri(uri) {
740
+ const ref = await this.monacoModelService.createModelReference(uri);
741
+ if (ref.object) {
742
+ this.toDispose.push(ref);
743
+ }
744
+ else {
745
+ ref.dispose();
746
+ }
747
+ }
748
+ dispose() {
749
+ for (const peer of this.peers.values()) {
750
+ peer.dispose();
751
+ }
752
+ this.onDidCloseEmitter.fire();
753
+ this.toDispose.dispose();
754
+ }
755
+ };
756
+ exports.CollaborationInstance = CollaborationInstance;
757
+ tslib_1.__decorate([
758
+ (0, inversify_1.inject)(core_1.MessageService),
759
+ tslib_1.__metadata("design:type", core_1.MessageService)
760
+ ], CollaborationInstance.prototype, "messageService", void 0);
761
+ tslib_1.__decorate([
762
+ (0, inversify_1.inject)(collaboration_workspace_service_1.CollaborationWorkspaceService),
763
+ tslib_1.__metadata("design:type", collaboration_workspace_service_1.CollaborationWorkspaceService)
764
+ ], CollaborationInstance.prototype, "workspaceService", void 0);
765
+ tslib_1.__decorate([
766
+ (0, inversify_1.inject)(file_service_1.FileService),
767
+ tslib_1.__metadata("design:type", file_service_1.FileService)
768
+ ], CollaborationInstance.prototype, "fileService", void 0);
769
+ tslib_1.__decorate([
770
+ (0, inversify_1.inject)(monaco_text_model_service_1.MonacoTextModelService),
771
+ tslib_1.__metadata("design:type", monaco_text_model_service_1.MonacoTextModelService)
772
+ ], CollaborationInstance.prototype, "monacoModelService", void 0);
773
+ tslib_1.__decorate([
774
+ (0, inversify_1.inject)(editor_manager_1.EditorManager),
775
+ tslib_1.__metadata("design:type", editor_manager_1.EditorManager)
776
+ ], CollaborationInstance.prototype, "editorManager", void 0);
777
+ tslib_1.__decorate([
778
+ (0, inversify_1.inject)(browser_2.OpenerService),
779
+ tslib_1.__metadata("design:type", Object)
780
+ ], CollaborationInstance.prototype, "openerService", void 0);
781
+ tslib_1.__decorate([
782
+ (0, inversify_1.inject)(application_shell_1.ApplicationShell),
783
+ tslib_1.__metadata("design:type", application_shell_1.ApplicationShell)
784
+ ], CollaborationInstance.prototype, "shell", void 0);
785
+ tslib_1.__decorate([
786
+ (0, inversify_1.inject)(exports.CollaborationInstanceOptions),
787
+ tslib_1.__metadata("design:type", Object)
788
+ ], CollaborationInstance.prototype, "options", void 0);
789
+ tslib_1.__decorate([
790
+ (0, inversify_1.inject)(collaboration_color_service_1.CollaborationColorService),
791
+ tslib_1.__metadata("design:type", collaboration_color_service_1.CollaborationColorService)
792
+ ], CollaborationInstance.prototype, "collaborationColorService", void 0);
793
+ tslib_1.__decorate([
794
+ (0, inversify_1.inject)(collaboration_utils_1.CollaborationUtils),
795
+ tslib_1.__metadata("design:type", collaboration_utils_1.CollaborationUtils)
796
+ ], CollaborationInstance.prototype, "utils", void 0);
797
+ tslib_1.__decorate([
798
+ (0, inversify_1.postConstruct)(),
799
+ tslib_1.__metadata("design:type", Function),
800
+ tslib_1.__metadata("design:paramtypes", []),
801
+ tslib_1.__metadata("design:returntype", void 0)
802
+ ], CollaborationInstance.prototype, "init", null);
803
+ exports.CollaborationInstance = CollaborationInstance = tslib_1.__decorate([
804
+ (0, inversify_1.injectable)()
805
+ ], CollaborationInstance);
806
+ //# sourceMappingURL=collaboration-instance.js.map