@difizen/libro-lsp 0.1.2

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 (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/es/adapters/adapter.d.ts +255 -0
  4. package/es/adapters/adapter.d.ts.map +1 -0
  5. package/es/adapters/adapter.js +647 -0
  6. package/es/adapters/notebook-adapter.d.ts +150 -0
  7. package/es/adapters/notebook-adapter.d.ts.map +1 -0
  8. package/es/adapters/notebook-adapter.js +588 -0
  9. package/es/adapters/status-message.d.ts +48 -0
  10. package/es/adapters/status-message.d.ts.map +1 -0
  11. package/es/adapters/status-message.js +110 -0
  12. package/es/connection-manager.d.ts +199 -0
  13. package/es/connection-manager.d.ts.map +1 -0
  14. package/es/connection-manager.js +685 -0
  15. package/es/connection.d.ts +149 -0
  16. package/es/connection.d.ts.map +1 -0
  17. package/es/connection.js +591 -0
  18. package/es/extractors/index.d.ts +4 -0
  19. package/es/extractors/index.d.ts.map +1 -0
  20. package/es/extractors/index.js +6 -0
  21. package/es/extractors/manager.d.ts +31 -0
  22. package/es/extractors/manager.d.ts.map +1 -0
  23. package/es/extractors/manager.js +90 -0
  24. package/es/extractors/text-extractor.d.ts +56 -0
  25. package/es/extractors/text-extractor.d.ts.map +1 -0
  26. package/es/extractors/text-extractor.js +72 -0
  27. package/es/extractors/types.d.ts +68 -0
  28. package/es/extractors/types.d.ts.map +1 -0
  29. package/es/extractors/types.js +1 -0
  30. package/es/feature.d.ts +29 -0
  31. package/es/feature.d.ts.map +1 -0
  32. package/es/feature.js +85 -0
  33. package/es/index.d.ts +19 -0
  34. package/es/index.d.ts.map +1 -0
  35. package/es/index.js +21 -0
  36. package/es/lsp-app-contribution.d.ts +19 -0
  37. package/es/lsp-app-contribution.d.ts.map +1 -0
  38. package/es/lsp-app-contribution.js +155 -0
  39. package/es/lsp-protocol.d.ts +10 -0
  40. package/es/lsp-protocol.d.ts.map +1 -0
  41. package/es/lsp-protocol.js +1 -0
  42. package/es/lsp.d.ts +136 -0
  43. package/es/lsp.d.ts.map +1 -0
  44. package/es/lsp.js +141 -0
  45. package/es/manager.d.ts +142 -0
  46. package/es/manager.d.ts.map +1 -0
  47. package/es/manager.js +423 -0
  48. package/es/module.d.ts +3 -0
  49. package/es/module.d.ts.map +1 -0
  50. package/es/module.js +21 -0
  51. package/es/plugin.d.ts +56 -0
  52. package/es/plugin.d.ts.map +1 -0
  53. package/es/plugin.js +0 -0
  54. package/es/positioning.d.ts +66 -0
  55. package/es/positioning.d.ts.map +1 -0
  56. package/es/positioning.js +96 -0
  57. package/es/schema.d.ts +240 -0
  58. package/es/schema.d.ts.map +1 -0
  59. package/es/schema.js +0 -0
  60. package/es/tokens.d.ts +677 -0
  61. package/es/tokens.d.ts.map +1 -0
  62. package/es/tokens.js +183 -0
  63. package/es/utils.d.ts +33 -0
  64. package/es/utils.d.ts.map +1 -0
  65. package/es/utils.js +168 -0
  66. package/es/virtual/document.d.ts +546 -0
  67. package/es/virtual/document.d.ts.map +1 -0
  68. package/es/virtual/document.js +1263 -0
  69. package/es/ws-connection/server-capability-registration.d.ts +19 -0
  70. package/es/ws-connection/server-capability-registration.d.ts.map +1 -0
  71. package/es/ws-connection/server-capability-registration.js +51 -0
  72. package/es/ws-connection/types.d.ts +76 -0
  73. package/es/ws-connection/types.d.ts.map +1 -0
  74. package/es/ws-connection/types.js +1 -0
  75. package/es/ws-connection/ws-connection.d.ts +105 -0
  76. package/es/ws-connection/ws-connection.d.ts.map +1 -0
  77. package/es/ws-connection/ws-connection.js +301 -0
  78. package/package.json +67 -0
  79. package/src/adapters/adapter.ts +611 -0
  80. package/src/adapters/notebook-adapter.ts +463 -0
  81. package/src/adapters/status-message.ts +93 -0
  82. package/src/connection-manager.ts +626 -0
  83. package/src/connection.ts +570 -0
  84. package/src/extractors/index.ts +6 -0
  85. package/src/extractors/manager.ts +82 -0
  86. package/src/extractors/text-extractor.ts +94 -0
  87. package/src/extractors/types.ts +78 -0
  88. package/src/feature.ts +60 -0
  89. package/src/index.spec.ts +10 -0
  90. package/src/index.ts +21 -0
  91. package/src/lsp-app-contribution.ts +83 -0
  92. package/src/lsp-protocol.ts +10 -0
  93. package/src/lsp.ts +160 -0
  94. package/src/manager.ts +358 -0
  95. package/src/module.ts +32 -0
  96. package/src/plugin.ts +62 -0
  97. package/src/positioning.ts +121 -0
  98. package/src/schema.ts +249 -0
  99. package/src/tokens.ts +843 -0
  100. package/src/utils.ts +109 -0
  101. package/src/virtual/document.ts +1250 -0
  102. package/src/ws-connection/server-capability-registration.ts +77 -0
  103. package/src/ws-connection/types.ts +102 -0
  104. package/src/ws-connection/ws-connection.ts +320 -0
@@ -0,0 +1,611 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import type { NotebookView } from '@difizen/libro-core';
5
+ import { Emitter } from '@difizen/mana-app';
6
+ import type { Disposable, Event, View } from '@difizen/mana-app';
7
+ import type {} from '@difizen/mana-app';
8
+ import mergeWith from 'lodash.mergewith';
9
+
10
+ import type { ClientCapabilities, LanguageIdentifier } from '../lsp.js';
11
+ import type { IVirtualPosition } from '../positioning.js';
12
+ import type {
13
+ Document,
14
+ IDocumentConnectionData,
15
+ ILSPCodeExtractorsManager,
16
+ ILSPDocumentConnectionManager,
17
+ ILSPFeatureManager,
18
+ ISocketConnectionOptions,
19
+ } from '../tokens.js';
20
+ import type { VirtualDocument } from '../virtual/document.js';
21
+
22
+ /**
23
+ * The values should follow the https://microsoft.github.io/language-server-protocol/specification guidelines
24
+ */
25
+ const MIME_TYPE_LANGUAGE_MAP: Record<string, string> = {
26
+ 'text/x-rsrc': 'r',
27
+ 'text/x-r-source': 'r',
28
+ // currently there are no LSP servers for IPython we are aware of
29
+ 'text/x-ipython': 'python',
30
+ };
31
+
32
+ export interface IEditorChangedData {
33
+ /**
34
+ * The CM editor invoking the change event.
35
+ */
36
+ editor: Document.IEditor;
37
+ }
38
+
39
+ export interface IAdapterOptions {
40
+ /**
41
+ * The LSP document and connection manager instance.
42
+ */
43
+ connectionManager: ILSPDocumentConnectionManager;
44
+
45
+ /**
46
+ * The LSP feature manager instance.
47
+ */
48
+ featureManager: ILSPFeatureManager;
49
+
50
+ /**
51
+ * The LSP foreign code extractor manager.
52
+ */
53
+ foreignCodeExtractorsManager: ILSPCodeExtractorsManager;
54
+ }
55
+
56
+ /**
57
+ * Foreign code: low level adapter is not aware of the presence of foreign languages;
58
+ * it operates on the virtual document and must not attempt to infer the language dependencies
59
+ * as this would make the logic of inspections caching impossible to maintain, thus the WidgetAdapter
60
+ * has to handle that, keeping multiple connections and multiple virtual documents.
61
+ */
62
+ export abstract class WidgetLSPAdapter<T extends NotebookView> implements Disposable {
63
+ // note: it could be using namespace/IOptions pattern,
64
+ // but I do not know how to make it work with the generic type T
65
+ // (other than using 'any' in the IOptions interface)
66
+ constructor(
67
+ public widget: T,
68
+ protected options: IAdapterOptions,
69
+ ) {
70
+ this._connectionManager = options.connectionManager;
71
+ this._isConnected = false;
72
+ // set up signal connections
73
+ this.widget.onSave(this.onSaveState);
74
+ // this.widget.context.saveState.connect(this.onSaveState, this);
75
+ // this.connectionManager.closed.connect(this.onConnectionClosed, this);
76
+ // this.widget.disposed.connect(this.dispose, this);
77
+ }
78
+
79
+ /**
80
+ * Check if the adapter is disposed
81
+ */
82
+ get disposed(): boolean {
83
+ return this._isDisposed;
84
+ }
85
+ /**
86
+ * Check if the document contains multiple editors
87
+ */
88
+ get hasMultipleEditors(): boolean {
89
+ return this.editors.length > 1;
90
+ }
91
+ /**
92
+ * Get the ID of the internal widget.
93
+ */
94
+ get widgetId(): string {
95
+ return this.widget.id;
96
+ }
97
+
98
+ /**
99
+ * Get the language identifier of the document
100
+ */
101
+ get language(): LanguageIdentifier {
102
+ // the values should follow https://microsoft.github.io/language-server-protocol/specification guidelines,
103
+ // see the table in https://microsoft.github.io/language-server-protocol/specification#textDocumentItem
104
+ if (Object.prototype.hasOwnProperty.call(MIME_TYPE_LANGUAGE_MAP, this.mimeType)) {
105
+ return MIME_TYPE_LANGUAGE_MAP[this.mimeType];
106
+ } else {
107
+ const withoutParameters = this.mimeType.split(';')[0];
108
+ const [type, subtype] = withoutParameters.split('/');
109
+ if (type === 'application' || type === 'text') {
110
+ if (subtype.startsWith('x-')) {
111
+ return subtype.substring(2);
112
+ } else {
113
+ return subtype;
114
+ }
115
+ } else {
116
+ return this.mimeType;
117
+ }
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Signal emitted when the adapter is connected.
123
+ */
124
+ get adapterConnected(): Event<IDocumentConnectionData> {
125
+ return this._adapterConnected.event;
126
+ }
127
+
128
+ /**
129
+ * Signal emitted when the active editor have changed.
130
+ */
131
+ get activeEditorChanged(): Event<IEditorChangedData> {
132
+ return this._activeEditorChanged.event;
133
+ }
134
+
135
+ /**
136
+ * Signal emitted when the adapter is disposed.
137
+ */
138
+ get onDispose(): Event<void> {
139
+ return this._disposed.event;
140
+ }
141
+
142
+ /**
143
+ * Signal emitted when the an editor is changed.
144
+ */
145
+ get editorAdded(): Event<IEditorChangedData> {
146
+ return this._editorAdded.event;
147
+ }
148
+
149
+ /**
150
+ * Signal emitted when the an editor is removed.
151
+ */
152
+ get editorRemoved(): Event<IEditorChangedData> {
153
+ return this._editorRemoved.event;
154
+ }
155
+
156
+ /**
157
+ * Get the inner HTMLElement of the document widget.
158
+ */
159
+ abstract get wrapperElement(): HTMLElement;
160
+
161
+ /**
162
+ * Get current path of the document.
163
+ */
164
+ abstract get documentPath(): string;
165
+
166
+ /**
167
+ * Get the mime type of the document.
168
+ */
169
+ abstract get mimeType(): string;
170
+
171
+ /**
172
+ * Get the file extension of the document.
173
+ */
174
+ abstract get languageFileExtension(): string | undefined;
175
+
176
+ /**
177
+ * Get the activated CM editor.
178
+ */
179
+ abstract get activeEditor(): Document.IEditor | undefined;
180
+
181
+ /**
182
+ * Get the list of CM editors in the document, there is only one editor
183
+ * in the case of file editor.
184
+ */
185
+ abstract get editors(): Document.ICodeBlockOptions[];
186
+
187
+ /**
188
+ * Promise that resolves once the adapter is initialized
189
+ */
190
+ abstract get ready(): Promise<void>;
191
+
192
+ /**
193
+ * The virtual document is connected or not
194
+ */
195
+ get isConnected(): boolean {
196
+ return this._isConnected;
197
+ }
198
+
199
+ /**
200
+ * The LSP document and connection manager instance.
201
+ */
202
+ get connectionManager(): ILSPDocumentConnectionManager {
203
+ return this._connectionManager;
204
+ }
205
+
206
+ /**
207
+ * Promise that resolves once the document is updated
208
+ */
209
+ get updateFinished(): Promise<void> {
210
+ return this._updateFinished;
211
+ }
212
+
213
+ /**
214
+ * Internal virtual document of the adapter.
215
+ */
216
+ get virtualDocument(): VirtualDocument | null {
217
+ return this._virtualDocument;
218
+ }
219
+
220
+ /**
221
+ * Callback on connection closed event.
222
+ */
223
+ onConnectionClosed(
224
+ _: ILSPDocumentConnectionManager,
225
+ { virtualDocument }: IDocumentConnectionData,
226
+ ): void {
227
+ if (virtualDocument === this.virtualDocument) {
228
+ this.dispose();
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Dispose the adapter.
234
+ */
235
+ dispose(): void {
236
+ if (this._isDisposed) {
237
+ return;
238
+ }
239
+ this._isDisposed = true;
240
+ this.disconnect();
241
+ this._virtualDocument = null;
242
+ this._disposed.fire();
243
+ }
244
+
245
+ /**
246
+ * Disconnect virtual document from the language server.
247
+ */
248
+ disconnect(): void {
249
+ const uri = this.virtualDocument?.uri;
250
+ if (uri) {
251
+ this.connectionManager.unregisterDocument(uri);
252
+ }
253
+
254
+ // pretend that all editors were removed to trigger the disconnection of even handlers
255
+ // they will be connected again on new connection
256
+ for (const { ceEditor: editor } of this.editors) {
257
+ this._editorRemoved.fire({
258
+ editor: editor,
259
+ });
260
+ }
261
+
262
+ this.virtualDocument?.dispose();
263
+ }
264
+
265
+ /**
266
+ * Update the virtual document.
267
+ */
268
+ updateDocuments(): Promise<void> {
269
+ if (this._isDisposed) {
270
+ console.warn('Cannot update documents: adapter disposed');
271
+ return Promise.reject('Cannot update documents: adapter disposed');
272
+ }
273
+ return this.virtualDocument!.updateManager.updateDocuments(this.editors);
274
+ }
275
+
276
+ /**
277
+ * Callback called on the document changed event.
278
+ */
279
+ documentChanged(virtualDocument: VirtualDocument): void {
280
+ if (this._isDisposed) {
281
+ console.warn('Cannot swap document: adapter disposed');
282
+ return;
283
+ }
284
+
285
+ // TODO only send the difference, using connection.sendSelectiveChange()
286
+ const connection = this.connectionManager.connections.get(virtualDocument.uri);
287
+
288
+ if (!connection?.isReady) {
289
+ console.warn('Skipping document update signal: connection not ready');
290
+ return;
291
+ }
292
+
293
+ connection.sendFullTextChange(virtualDocument.value, virtualDocument.documentInfo);
294
+ }
295
+
296
+ /**
297
+ * (re)create virtual document using current path and language
298
+ */
299
+ protected abstract createVirtualDocument(): VirtualDocument;
300
+
301
+ /**
302
+ * Get the index of editor from the cursor position in the virtual
303
+ * document. Since there is only one editor, this method always return
304
+ * 0
305
+ *
306
+ * @param position - the position of cursor in the virtual document.
307
+ * @return - index of the virtual editor
308
+ */
309
+ abstract getEditorIndexAt(position: IVirtualPosition): number;
310
+
311
+ /**
312
+ * Get the index of input editor
313
+ *
314
+ * @param ceEditor - instance of the code editor
315
+ */
316
+ abstract getEditorIndex(ceEditor: Document.IEditor): number;
317
+
318
+ /**
319
+ * Get the index of input editor
320
+ *
321
+ * @param ceEditor - instance of the code editor
322
+ */
323
+ abstract getCellEditor(cell: View): Document.IEditor | undefined;
324
+
325
+ /**
326
+ * Get the wrapper of input editor.
327
+ *
328
+ * @param ceEditor
329
+ */
330
+ abstract getEditorWrapper(ceEditor: Document.IEditor): HTMLElement | undefined;
331
+
332
+ // equivalent to triggering didClose and didOpen, as per syncing specification,
333
+ // but also reloads the connection; used during file rename (or when it was moved)
334
+ protected reloadConnection(): void {
335
+ // ignore premature calls (before the editor was initialized)
336
+ if (this.virtualDocument === null) {
337
+ return;
338
+ }
339
+
340
+ // disconnect all existing connections (and dispose adapters)
341
+ this.disconnect();
342
+
343
+ // recreate virtual document using current path and language
344
+ // as virtual editor assumes it gets the virtual document at init,
345
+ // just dispose virtual editor (which disposes virtual document too)
346
+ // and re-initialize both virtual editor and document
347
+ this.initVirtual();
348
+
349
+ // reconnect
350
+ this.connectDocument(this.virtualDocument, true).catch(console.warn);
351
+ }
352
+
353
+ /**
354
+ * Callback on document saved event.
355
+ */
356
+ protected onSaveState = (): void => {
357
+ // ignore premature calls (before the editor was initialized)
358
+ if (this.virtualDocument === null) {
359
+ return;
360
+ }
361
+
362
+ const documentsToSave = [this.virtualDocument];
363
+
364
+ for (const virtualDocument of documentsToSave) {
365
+ const connection = this.connectionManager.connections.get(virtualDocument.uri);
366
+ if (!connection) {
367
+ continue;
368
+ }
369
+ connection.sendSaved(virtualDocument.documentInfo);
370
+ for (const foreign of virtualDocument.foreignDocuments.values()) {
371
+ documentsToSave.push(foreign);
372
+ }
373
+ }
374
+ };
375
+
376
+ /**
377
+ * Connect the virtual document with the language server.
378
+ */
379
+ protected async onConnected(data: IDocumentConnectionData): Promise<void> {
380
+ const { virtualDocument } = data;
381
+
382
+ this._adapterConnected.fire(data);
383
+ this._isConnected = true;
384
+
385
+ try {
386
+ await this.updateDocuments();
387
+ } catch (reason) {
388
+ console.warn('Could not update documents', reason);
389
+ return;
390
+ }
391
+
392
+ // refresh the document on the LSP server
393
+ this.documentChanged(virtualDocument);
394
+
395
+ data.connection.serverNotifications['$/logTrace'].event((message) => {
396
+ console.warn(
397
+ data.connection.serverIdentifier,
398
+ 'trace',
399
+ virtualDocument.uri,
400
+ message,
401
+ );
402
+ });
403
+
404
+ data.connection.serverNotifications['window/logMessage'].event((message) => {
405
+ console.warn(data.connection.serverIdentifier + ': ' + message.message);
406
+ });
407
+
408
+ data.connection.serverNotifications['window/showMessage'].event((message) => {
409
+ // void showDialog({
410
+ // title: this.trans.__('Message from ') + connection.serverIdentifier,
411
+ // body: message.message,
412
+ // });
413
+ alert(`Message from ${data.connection.serverIdentifier}: ${message.message}`);
414
+ });
415
+
416
+ data.connection.serverRequests['window/showMessageRequest'].setHandler(
417
+ async (params) => {
418
+ alert(`Message from ${data.connection.serverIdentifier}: ${params.message}`);
419
+ return null;
420
+
421
+ // const actionItems = params.actions;
422
+ // const buttons = actionItems
423
+ // ? actionItems.map(action => {
424
+ // return createButton({
425
+ // label: action.title,
426
+ // });
427
+ // })
428
+ // : [createButton({ label: this.trans.__('Dismiss') })];
429
+ // const result = await showDialog<IButton>({
430
+ // title: this.trans.__('Message from ') + data.connection.serverIdentifier,
431
+ // body: params.message,
432
+ // buttons: buttons,
433
+ // });
434
+ // const choice = buttons.indexOf(result.button);
435
+ // if (choice === -1) {
436
+ // return null;
437
+ // }
438
+ // if (actionItems) {
439
+ // return actionItems[choice];
440
+ // }
441
+ // return null;
442
+ },
443
+ );
444
+ }
445
+
446
+ /**
447
+ * Opens a connection for the document. The connection may or may
448
+ * not be initialized, yet, and depending on when this is called, the client
449
+ * may not be fully connected.
450
+ *
451
+ * @param virtualDocument a VirtualDocument
452
+ * @param sendOpen whether to open the document immediately
453
+ */
454
+ protected async connectDocument(
455
+ virtualDocument: VirtualDocument,
456
+ sendOpen = false,
457
+ ): Promise<void> {
458
+ virtualDocument.foreignDocumentOpened(this.onForeignDocumentOpened, this);
459
+ const connectionContext = await this._connect(virtualDocument).catch(console.error);
460
+
461
+ if (connectionContext && connectionContext.connection) {
462
+ virtualDocument.changed(this.documentChanged, this);
463
+ if (sendOpen) {
464
+ connectionContext.connection.sendOpenWhenReady(virtualDocument.documentInfo);
465
+ }
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Create the virtual document using current path and language.
471
+ */
472
+ protected initVirtual(): void {
473
+ const { model } = this.widget;
474
+ this._virtualDocument?.dispose();
475
+ this._virtualDocument = this.createVirtualDocument();
476
+ model.onSourceChanged?.(() => this._onContentChanged());
477
+ }
478
+
479
+ /**
480
+ * Handler for opening a document contained in a parent document. The assumption
481
+ * is that the editor already exists for this, and as such the document
482
+ * should be queued for immediate opening.
483
+ *
484
+ * @param host the VirtualDocument that contains the VirtualDocument in another language
485
+ * @param context information about the foreign VirtualDocument
486
+ */
487
+ protected async onForeignDocumentOpened(
488
+ context: Document.IForeignContext,
489
+ ): Promise<void> {
490
+ const { foreignDocument } = context;
491
+
492
+ await this.connectDocument(foreignDocument, true);
493
+
494
+ foreignDocument.foreignDocumentClosed(this._onForeignDocumentClosed, this);
495
+ }
496
+
497
+ /**
498
+ * Signal emitted when the adapter is connected.
499
+ */
500
+ protected _adapterConnected = new Emitter<IDocumentConnectionData>();
501
+
502
+ /**
503
+ * Signal emitted when the active editor have changed.
504
+ */
505
+ protected _activeEditorChanged = new Emitter<IEditorChangedData>();
506
+
507
+ /**
508
+ * Signal emitted when an editor is changed.
509
+ */
510
+ protected _editorAdded = new Emitter<IEditorChangedData>();
511
+
512
+ /**
513
+ * Signal emitted when an editor is removed.
514
+ */
515
+ protected _editorRemoved = new Emitter<IEditorChangedData>();
516
+
517
+ /**
518
+ * Signal emitted when the adapter is disposed.
519
+ */
520
+ protected _disposed = new Emitter<void>();
521
+
522
+ protected _isDisposed = false;
523
+
524
+ protected readonly _connectionManager: ILSPDocumentConnectionManager;
525
+
526
+ protected _isConnected: boolean;
527
+ protected _updateFinished: Promise<void>;
528
+ protected _virtualDocument: VirtualDocument | null = null;
529
+
530
+ /**
531
+ * Callback called when a foreign document is closed,
532
+ * the associated signals with this virtual document
533
+ * are disconnected.
534
+ */
535
+ protected _onForeignDocumentClosed(context: Document.IForeignContext): void {
536
+ // const { foreignDocument } = context;
537
+ }
538
+
539
+ /**
540
+ * Detect the capabilities for the document type then
541
+ * open the websocket connection with the language server.
542
+ */
543
+ protected async _connect(virtualDocument: VirtualDocument) {
544
+ const language = virtualDocument.language;
545
+
546
+ let capabilities: ClientCapabilities = {
547
+ textDocument: {
548
+ synchronization: {
549
+ dynamicRegistration: true,
550
+ willSave: false,
551
+ didSave: true,
552
+ willSaveWaitUntil: false,
553
+ },
554
+ },
555
+ workspace: {
556
+ didChangeConfiguration: {
557
+ dynamicRegistration: true,
558
+ },
559
+ },
560
+ };
561
+ capabilities = mergeWith(
562
+ capabilities,
563
+ this.options.featureManager.clientCapabilities(),
564
+ );
565
+
566
+ const options: ISocketConnectionOptions = {
567
+ capabilities,
568
+ virtualDocument,
569
+ language,
570
+ hasLspSupportedFile: virtualDocument.hasLspSupportedFile,
571
+ };
572
+
573
+ const connection = await this.connectionManager.connect(options);
574
+
575
+ if (connection) {
576
+ await this.onConnected({ virtualDocument, connection });
577
+
578
+ return {
579
+ connection,
580
+ virtualDocument,
581
+ };
582
+ } else {
583
+ return undefined;
584
+ }
585
+ }
586
+
587
+ /**
588
+ * Handle content changes and update all virtual documents after a change.
589
+ *
590
+ * #### Notes
591
+ * Update to the state of a notebook may be done without a notice on the
592
+ * CodeMirror level, e.g. when a cell is deleted. Therefore a
593
+ * JupyterLab-specific signal is watched instead.
594
+ *
595
+ * While by not using the change event of CodeMirror editors we lose an easy
596
+ * way to send selective (range) updates this can be still implemented by
597
+ * comparison of before/after states of the virtual documents, which is
598
+ * more resilient and editor-independent.
599
+ */
600
+ protected async _onContentChanged() {
601
+ // Update the virtual documents.
602
+ // Sending the updates to LSP is out of scope here.
603
+ const promise = this.updateDocuments();
604
+ if (!promise) {
605
+ console.warn('Could not update documents');
606
+ return;
607
+ }
608
+ this._updateFinished = promise.catch(console.warn);
609
+ await this.updateFinished;
610
+ }
611
+ }