@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,626 @@
1
+ /* eslint-disable @typescript-eslint/no-shadow */
2
+ /* eslint-disable @typescript-eslint/no-namespace */
3
+ /* eslint-disable @typescript-eslint/no-use-before-define */
4
+ // Copyright (c) Jupyter Development Team.
5
+ // Distributed under the terms of the Modified BSD License.
6
+
7
+ // import { PageConfig, URL } from '@jupyterlab/coreutils';
8
+ // import type { IDocumentWidget } from '@jupyterlab/docregistry';
9
+
10
+ import { URL } from '@difizen/libro-common';
11
+ import type { NotebookView } from '@difizen/libro-core';
12
+ import { PageConfig } from '@difizen/libro-kernel';
13
+ import type { Event } from '@difizen/mana-app';
14
+ import { Emitter, inject, singleton } from '@difizen/mana-app';
15
+ import type * as protocol from 'vscode-languageserver-protocol';
16
+
17
+ import type { WidgetLSPAdapter } from './adapters/adapter.js';
18
+ import { LSPConnection } from './connection.js';
19
+ import type { ClientCapabilities } from './lsp.js';
20
+ import type { AskServersToSendTraceNotifications } from './plugin.js';
21
+ import type {
22
+ Document,
23
+ IDocumentConnectionData,
24
+ ILanguageServerManager,
25
+ ILSPConnection,
26
+ ISocketConnectionOptions,
27
+ TLanguageServerConfigurations,
28
+ TLanguageServerId,
29
+ TServerKeys,
30
+ } from './tokens.js';
31
+ import {
32
+ ILSPDocumentConnectionManager,
33
+ ILanguageServerManagerFactory,
34
+ } from './tokens.js';
35
+ import { expandDottedPaths, sleep, untilReady } from './utils.js';
36
+ import type { VirtualDocument } from './virtual/document.js';
37
+
38
+ /**
39
+ * Each Widget with a document (whether file or a notebook) has the same DocumentConnectionManager
40
+ * (see JupyterLabWidgetAdapter). Using id_path instead of uri led to documents being overwritten
41
+ * as two identical id_paths could be created for two different notebooks.
42
+ */
43
+ @singleton({ token: ILSPDocumentConnectionManager })
44
+ export class DocumentConnectionManager implements ILSPDocumentConnectionManager {
45
+ constructor(
46
+ @inject(ILanguageServerManagerFactory)
47
+ languageServerManagerFactory: ILanguageServerManagerFactory,
48
+ ) {
49
+ this.connections = new Map();
50
+ this.documents = new Map();
51
+ this.adapters = new Map();
52
+ this._ignoredLanguages = new Set();
53
+ this.languageServerManager = languageServerManagerFactory({});
54
+ Private.setLanguageServerManager(this.languageServerManager);
55
+ }
56
+
57
+ /**
58
+ * Map between the URI of the virtual document and its connection
59
+ * to the language server
60
+ */
61
+ readonly connections: Map<VirtualDocument.uri, LSPConnection>;
62
+
63
+ /**
64
+ * Map between the path of the document and its adapter
65
+ */
66
+ readonly adapters: Map<string, WidgetLSPAdapter<NotebookView>>;
67
+
68
+ /**
69
+ * Map between the URI of the virtual document and the document itself.
70
+ */
71
+ readonly documents: Map<VirtualDocument.uri, VirtualDocument>;
72
+ /**
73
+ * The language server manager plugin.
74
+ */
75
+ readonly languageServerManager: ILanguageServerManager;
76
+
77
+ /**
78
+ * Initial configuration for the language servers.
79
+ */
80
+ initialConfigurations: TLanguageServerConfigurations;
81
+
82
+ /**
83
+ * Signal emitted when the manager is initialized.
84
+ */
85
+ get initialized(): Event<IDocumentConnectionData> {
86
+ return this._initialized.event;
87
+ }
88
+
89
+ /**
90
+ * Signal emitted when the manager is connected to the server
91
+ */
92
+ get connected(): Event<IDocumentConnectionData> {
93
+ return this._connected.event;
94
+ }
95
+
96
+ /**
97
+ * Connection temporarily lost or could not be fully established; a re-connection will be attempted;
98
+ */
99
+ get disconnected(): Event<IDocumentConnectionData> {
100
+ return this._disconnected.event;
101
+ }
102
+
103
+ /**
104
+ * Connection was closed permanently and no-reconnection will be attempted, e.g.:
105
+ * - there was a serious server error
106
+ * - user closed the connection,
107
+ * - re-connection attempts exceeded,
108
+ */
109
+ get closed(): Event<IDocumentConnectionData> {
110
+ return this._closed.event;
111
+ }
112
+
113
+ /**
114
+ * Signal emitted when the document is changed.
115
+ */
116
+ get documentsChanged(): Event<Map<VirtualDocument.uri, VirtualDocument>> {
117
+ return this._documentsChanged.event;
118
+ }
119
+
120
+ /**
121
+ * Promise resolved when the language server manager is ready.
122
+ */
123
+ get ready(): Promise<void> {
124
+ return Private.getLanguageServerManager().ready;
125
+ }
126
+
127
+ /**
128
+ * Helper to connect various virtual document signal with callbacks of
129
+ * this class.
130
+ *
131
+ * @param virtualDocument - virtual document to be connected.
132
+ */
133
+ connectDocumentSignals(virtualDocument: VirtualDocument): void {
134
+ virtualDocument.foreignDocumentOpened(this.onForeignDocumentOpened, this);
135
+
136
+ virtualDocument.foreignDocumentClosed(this.onForeignDocumentClosed, this);
137
+ this.documents.set(virtualDocument.uri, virtualDocument);
138
+ this._documentsChanged.fire(this.documents);
139
+ }
140
+
141
+ /**
142
+ * Helper to disconnect various virtual document signal with callbacks of
143
+ * this class.
144
+ *
145
+ * @param virtualDocument - virtual document to be disconnected.
146
+ */
147
+ disconnectDocumentSignals(virtualDocument: VirtualDocument, emit = true): void {
148
+ this.documents.delete(virtualDocument.uri);
149
+ for (const foreign of virtualDocument.foreignDocuments.values()) {
150
+ this.disconnectDocumentSignals(foreign, false);
151
+ }
152
+
153
+ if (emit) {
154
+ this._documentsChanged.fire(this.documents);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Handle foreign document opened event.
160
+ */
161
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
162
+ onForeignDocumentOpened(context: Document.IForeignContext): void {
163
+ /** no-op */
164
+ }
165
+
166
+ /**
167
+ * Handle foreign document closed event.
168
+ */
169
+ onForeignDocumentClosed(context: Document.IForeignContext): void {
170
+ const { foreignDocument } = context;
171
+ this.unregisterDocument(foreignDocument.uri, false);
172
+ this.disconnectDocumentSignals(foreignDocument);
173
+ }
174
+
175
+ /**
176
+ * Register a widget adapter with this manager
177
+ *
178
+ * @param path - path to the inner document of the adapter
179
+ * @param adapter - the adapter to be registered
180
+ */
181
+ registerAdapter(path: string, adapter: WidgetLSPAdapter<NotebookView>): void {
182
+ this.adapters.set(path, adapter);
183
+ adapter.onDispose(() => {
184
+ if (adapter.virtualDocument) {
185
+ this.documents.delete(adapter.virtualDocument.uri);
186
+ }
187
+ this.adapters.delete(path);
188
+ });
189
+ }
190
+
191
+ /**
192
+ * Handles the settings that do not require an existing connection
193
+ * with a language server (or can influence to which server the
194
+ * connection will be created, e.g. `rank`).
195
+ *
196
+ * This function should be called **before** initialization of servers.
197
+ */
198
+ updateConfiguration(allServerSettings: TLanguageServerConfigurations): void {
199
+ this.languageServerManager.setConfiguration(allServerSettings);
200
+ }
201
+
202
+ /**
203
+ * Handles the settings that the language servers accept using
204
+ * `onDidChangeConfiguration` messages, which should be passed under
205
+ * the "serverSettings" keyword in the setting registry.
206
+ * Other configuration options are handled by `updateConfiguration` instead.
207
+ *
208
+ * This function should be called **after** initialization of servers.
209
+ */
210
+ updateServerConfigurations(allServerSettings: TLanguageServerConfigurations): void {
211
+ let languageServerId: TServerKeys;
212
+
213
+ for (languageServerId in allServerSettings) {
214
+ if (!Object.prototype.hasOwnProperty.call(allServerSettings, languageServerId)) {
215
+ continue;
216
+ }
217
+ const rawSettings = allServerSettings[languageServerId]!;
218
+
219
+ const parsedSettings = expandDottedPaths(rawSettings.configuration || {});
220
+
221
+ const serverSettings: protocol.DidChangeConfigurationParams = {
222
+ settings: parsedSettings,
223
+ };
224
+
225
+ Private.updateServerConfiguration(languageServerId, serverSettings);
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Fired the first time a connection is opened. These _should_ be the only
231
+ * invocation of `.on` (once remaining LSPFeature.connection_handlers are made
232
+ * singletons).
233
+ */
234
+ onNewConnection = (connection: LSPConnection): void => {
235
+ const errorSignalSlot = (e: any): void => {
236
+ console.error(e);
237
+ const error: Error = e.length && e.length >= 1 ? e[0] : new Error();
238
+ if (error.message.indexOf('code = 1005') !== -1) {
239
+ console.error(`Connection failed for ${connection}`);
240
+ this._forEachDocumentOfConnection(connection, (virtualDocument) => {
241
+ console.error('disconnecting ' + virtualDocument.uri);
242
+ this._closed.fire({ connection, virtualDocument });
243
+ this._ignoredLanguages.add(virtualDocument.language);
244
+ console.error(
245
+ `Cancelling further attempts to connect ${virtualDocument.uri} and other documents for this language (no support from the server)`,
246
+ );
247
+ });
248
+ } else if (error.message.indexOf('code = 1006') !== -1) {
249
+ console.error('Connection closed by the server');
250
+ } else {
251
+ console.error('Connection error:', e);
252
+ }
253
+ };
254
+ connection.errorSignal(errorSignalSlot);
255
+
256
+ const serverInitializedSlot = (): void => {
257
+ // Initialize using settings stored in the SettingRegistry
258
+ this._forEachDocumentOfConnection(connection, (virtualDocument) => {
259
+ // TODO: is this still necessary, e.g. for status bar to update responsively?
260
+ this._initialized.fire({ connection, virtualDocument });
261
+ });
262
+ this.updateServerConfigurations(this.initialConfigurations);
263
+ };
264
+ connection.serverInitialized(serverInitializedSlot);
265
+
266
+ const closeSignalSlot = (closedManually: boolean) => {
267
+ if (!closedManually) {
268
+ console.error('Connection unexpectedly disconnected');
269
+ } else {
270
+ console.warn('Connection closed');
271
+ this._forEachDocumentOfConnection(connection, (virtualDocument) => {
272
+ this._closed.fire({ connection, virtualDocument });
273
+ });
274
+ }
275
+ };
276
+ connection.closeSignal(closeSignalSlot);
277
+ };
278
+
279
+ /**
280
+ * Retry to connect to the server each `reconnectDelay` seconds
281
+ * and for `retrialsLeft` times.
282
+ * TODO: presently no longer referenced. A failing connection would close
283
+ * the socket, triggering the language server on the other end to exit.
284
+ */
285
+ async retryToConnect(
286
+ options: ISocketConnectionOptions,
287
+ reconnectDelay: number,
288
+ retrialsLeft = -1,
289
+ ): Promise<void> {
290
+ const { virtualDocument } = options;
291
+
292
+ if (this._ignoredLanguages.has(virtualDocument.language)) {
293
+ return;
294
+ }
295
+
296
+ let interval = reconnectDelay * 1000;
297
+ let success = false;
298
+
299
+ while (retrialsLeft !== 0 && !success) {
300
+ await this.connect(options)
301
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
302
+ .then(() => {
303
+ success = true;
304
+ return;
305
+ })
306
+ .catch((e) => {
307
+ console.warn(e);
308
+ });
309
+
310
+ console.warn('will attempt to re-connect in ' + interval / 1000 + ' seconds');
311
+ await sleep(interval);
312
+
313
+ // gradually increase the time delay, up to 5 sec
314
+ interval = interval < 5 * 1000 ? interval + 500 : interval;
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Disconnect the connection to the language server of the requested
320
+ * language.
321
+ */
322
+ disconnect(languageId: TLanguageServerId): void {
323
+ Private.disconnect(languageId);
324
+ }
325
+
326
+ /**
327
+ * Create a new connection to the language server
328
+ * @return A promise of the LSP connection
329
+ */
330
+ async connect(
331
+ options: ISocketConnectionOptions,
332
+ firstTimeoutSeconds = 30,
333
+ secondTimeoutMinutes = 5,
334
+ ): Promise<ILSPConnection | undefined> {
335
+ const connection = await this._connectSocket(options);
336
+ const { virtualDocument } = options;
337
+ if (!connection) {
338
+ return;
339
+ }
340
+ if (!connection.isReady) {
341
+ try {
342
+ // user feedback hinted that 40 seconds was too short and some users are willing to wait more;
343
+ // to make the best of both worlds we first check frequently (6.6 times a second) for the first
344
+ // 30 seconds, and show the warning early in case if something is wrong; we then continue retrying
345
+ // for another 5 minutes, but only once per second.
346
+ await untilReady(
347
+ () => connection.isReady,
348
+ Math.round((firstTimeoutSeconds * 1000) / 150),
349
+ 150,
350
+ );
351
+ } catch {
352
+ console.warn(
353
+ `Connection to ${virtualDocument.uri} timed out after ${firstTimeoutSeconds} seconds, will continue retrying for another ${secondTimeoutMinutes} minutes`,
354
+ );
355
+ try {
356
+ await untilReady(() => connection.isReady, 60 * secondTimeoutMinutes, 1000);
357
+ } catch {
358
+ console.warn(
359
+ `Connection to ${virtualDocument.uri} timed out again after ${secondTimeoutMinutes} minutes, giving up`,
360
+ );
361
+ return;
362
+ }
363
+ }
364
+ }
365
+
366
+ this._connected.fire({ connection, virtualDocument });
367
+
368
+ return connection;
369
+ }
370
+
371
+ /**
372
+ * Disconnect the signals of requested virtual document uri.
373
+ */
374
+ unregisterDocument(uri: string, emit = true): void {
375
+ const connection = this.connections.get(uri);
376
+ if (connection) {
377
+ this.connections.delete(uri);
378
+ const allConnection = new Set(this.connections.values());
379
+
380
+ if (!allConnection.has(connection)) {
381
+ this.disconnect(connection.serverIdentifier as TLanguageServerId);
382
+ connection.dispose();
383
+ }
384
+ if (emit) {
385
+ this._documentsChanged.fire(this.documents);
386
+ }
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Enable or disable the logging feature of the language servers
392
+ */
393
+ updateLogging(
394
+ logAllCommunication: boolean,
395
+ setTrace: AskServersToSendTraceNotifications,
396
+ ): void {
397
+ for (const connection of this.connections.values()) {
398
+ connection.logAllCommunication = logAllCommunication;
399
+ if (setTrace !== null) {
400
+ connection.clientNotifications['$/setTrace'].fire({ value: setTrace });
401
+ }
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Create the LSP connection for requested virtual document.
407
+ *
408
+ * @return Return the promise of the LSP connection.
409
+ */
410
+
411
+ protected async _connectSocket(
412
+ options: ISocketConnectionOptions,
413
+ ): Promise<LSPConnection | undefined> {
414
+ const { language, capabilities, virtualDocument } = options;
415
+
416
+ this.connectDocumentSignals(virtualDocument);
417
+
418
+ const uris = DocumentConnectionManager.solveUris(virtualDocument, language);
419
+ const matchingServers = this.languageServerManager.getMatchingServers({
420
+ language,
421
+ });
422
+
423
+ // for now use only the server with the highest rank.
424
+ const languageServerId = matchingServers.length === 0 ? null : matchingServers[0];
425
+
426
+ // lazily load 1) the underlying library (1.5mb) and/or 2) a live WebSocket-
427
+ // like connection: either already connected or potentially in the process
428
+ // of connecting.
429
+ if (!uris) {
430
+ return;
431
+ }
432
+ const connection = await Private.connection(
433
+ language,
434
+ languageServerId!,
435
+ uris,
436
+ this.onNewConnection,
437
+ capabilities,
438
+ );
439
+
440
+ // if connecting for the first time, all documents subsequent documents will
441
+ // be re-opened and synced
442
+ this.connections.set(virtualDocument.uri, connection);
443
+
444
+ return connection;
445
+ }
446
+
447
+ /**
448
+ * Helper to apply callback on all documents of a connection.
449
+ */
450
+ protected _forEachDocumentOfConnection(
451
+ connection: ILSPConnection,
452
+ callback: (virtualDocument: VirtualDocument) => void,
453
+ ) {
454
+ for (const [virtualDocumentUri, currentConnection] of this.connections.entries()) {
455
+ if (connection !== currentConnection) {
456
+ continue;
457
+ }
458
+ callback(this.documents.get(virtualDocumentUri)!);
459
+ }
460
+ }
461
+
462
+ protected _initialized = new Emitter<IDocumentConnectionData>();
463
+
464
+ protected _connected = new Emitter<IDocumentConnectionData>();
465
+
466
+ protected _disconnected = new Emitter<IDocumentConnectionData>();
467
+
468
+ protected _closed = new Emitter<IDocumentConnectionData>();
469
+
470
+ protected _documentsChanged = new Emitter<
471
+ Map<VirtualDocument.uri, VirtualDocument>
472
+ >();
473
+
474
+ /**
475
+ * Set of ignored languages
476
+ */
477
+ protected _ignoredLanguages: Set<string>;
478
+ }
479
+
480
+ export namespace DocumentConnectionManager {
481
+ export interface IOptions {
482
+ /**
483
+ * The language server manager instance.
484
+ */
485
+ languageServerManager: ILanguageServerManager;
486
+ }
487
+
488
+ /**
489
+ * Generate the URI of a virtual document from input
490
+ *
491
+ * @param virtualDocument - the virtual document
492
+ * @param language - language of the document
493
+ */
494
+ export function solveUris(
495
+ virtualDocument: VirtualDocument,
496
+ language: string,
497
+ ): IURIs | undefined {
498
+ const wsBase = PageConfig.getBaseUrl().replace(/^http/, 'ws');
499
+ const rootUri = PageConfig.getOption('rootUri');
500
+ const virtualDocumentsUri = PageConfig.getOption('virtualDocumentsUri');
501
+
502
+ const baseUri = virtualDocument.hasLspSupportedFile ? rootUri : virtualDocumentsUri;
503
+
504
+ // for now take the best match only
505
+ const matchingServers = Private.getLanguageServerManager().getMatchingServers({
506
+ language,
507
+ });
508
+ const languageServerId = matchingServers.length === 0 ? null : matchingServers[0];
509
+
510
+ if (languageServerId === null) {
511
+ return;
512
+ }
513
+
514
+ // workaround url-parse bug(s) (see https://github.com/jupyter-lsp/jupyterlab-lsp/issues/595)
515
+ let documentUri = URL.join(baseUri, virtualDocument.uri);
516
+ if (!documentUri.startsWith('file:///') && documentUri.startsWith('file://')) {
517
+ documentUri = documentUri.replace('file://', 'file:///');
518
+ if (
519
+ documentUri.startsWith('file:///users/') &&
520
+ baseUri.startsWith('file:///Users/')
521
+ ) {
522
+ documentUri = documentUri.replace('file:///users/', 'file:///Users/');
523
+ }
524
+ }
525
+
526
+ return {
527
+ base: baseUri,
528
+ document: documentUri,
529
+ server: URL.join('ws://jupyter-lsp', language),
530
+ socket: URL.join(wsBase, 'lsp', 'ws', languageServerId),
531
+ };
532
+ }
533
+
534
+ export interface IURIs {
535
+ /**
536
+ * The root URI set by server.
537
+ *
538
+ */
539
+ base: string;
540
+
541
+ /**
542
+ * The URI to the virtual document.
543
+ *
544
+ */
545
+ document: string;
546
+
547
+ /**
548
+ * Address of websocket endpoint for LSP services.
549
+ *
550
+ */
551
+ server: string;
552
+
553
+ /**
554
+ * Address of websocket endpoint for the language server.
555
+ *
556
+ */
557
+ socket: string;
558
+ }
559
+ }
560
+
561
+ /**
562
+ * Namespace primarily for language-keyed cache of LSPConnections
563
+ */
564
+ namespace Private {
565
+ const _connections: Map<TLanguageServerId, LSPConnection> = new Map();
566
+ let _languageServerManager: ILanguageServerManager;
567
+
568
+ export function getLanguageServerManager(): ILanguageServerManager {
569
+ return _languageServerManager;
570
+ }
571
+ export function setLanguageServerManager(
572
+ languageServerManager: ILanguageServerManager,
573
+ ): void {
574
+ _languageServerManager = languageServerManager;
575
+ }
576
+
577
+ export function disconnect(languageServerId: TLanguageServerId): void {
578
+ const connection = _connections.get(languageServerId);
579
+ if (connection) {
580
+ connection.close();
581
+ _connections.delete(languageServerId);
582
+ }
583
+ }
584
+
585
+ /**
586
+ * Return (or create and initialize) the WebSocket associated with the language
587
+ */
588
+ export async function connection(
589
+ language: string,
590
+ languageServerId: TLanguageServerId,
591
+ uris: DocumentConnectionManager.IURIs,
592
+ onCreate: (connection: LSPConnection) => void,
593
+ capabilities: ClientCapabilities,
594
+ ): Promise<LSPConnection> {
595
+ let connection = _connections.get(languageServerId);
596
+ if (!connection) {
597
+ const socket = new WebSocket(uris.socket);
598
+
599
+ const connection = new LSPConnection({
600
+ languageId: language,
601
+ serverUri: uris.server,
602
+ rootUri: uris.base,
603
+ serverIdentifier: languageServerId,
604
+ capabilities: capabilities,
605
+ });
606
+
607
+ _connections.set(languageServerId, connection);
608
+ connection.connect(socket);
609
+ onCreate(connection);
610
+ }
611
+
612
+ connection = _connections.get(languageServerId)!;
613
+
614
+ return connection;
615
+ }
616
+
617
+ export function updateServerConfiguration(
618
+ languageServerId: TLanguageServerId,
619
+ settings: protocol.DidChangeConfigurationParams,
620
+ ): void {
621
+ const connection = _connections.get(languageServerId);
622
+ if (connection) {
623
+ connection.sendConfigurationChange(settings);
624
+ }
625
+ }
626
+ }