@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.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/es/adapters/adapter.d.ts +255 -0
- package/es/adapters/adapter.d.ts.map +1 -0
- package/es/adapters/adapter.js +647 -0
- package/es/adapters/notebook-adapter.d.ts +150 -0
- package/es/adapters/notebook-adapter.d.ts.map +1 -0
- package/es/adapters/notebook-adapter.js +588 -0
- package/es/adapters/status-message.d.ts +48 -0
- package/es/adapters/status-message.d.ts.map +1 -0
- package/es/adapters/status-message.js +110 -0
- package/es/connection-manager.d.ts +199 -0
- package/es/connection-manager.d.ts.map +1 -0
- package/es/connection-manager.js +685 -0
- package/es/connection.d.ts +149 -0
- package/es/connection.d.ts.map +1 -0
- package/es/connection.js +591 -0
- package/es/extractors/index.d.ts +4 -0
- package/es/extractors/index.d.ts.map +1 -0
- package/es/extractors/index.js +6 -0
- package/es/extractors/manager.d.ts +31 -0
- package/es/extractors/manager.d.ts.map +1 -0
- package/es/extractors/manager.js +90 -0
- package/es/extractors/text-extractor.d.ts +56 -0
- package/es/extractors/text-extractor.d.ts.map +1 -0
- package/es/extractors/text-extractor.js +72 -0
- package/es/extractors/types.d.ts +68 -0
- package/es/extractors/types.d.ts.map +1 -0
- package/es/extractors/types.js +1 -0
- package/es/feature.d.ts +29 -0
- package/es/feature.d.ts.map +1 -0
- package/es/feature.js +85 -0
- package/es/index.d.ts +19 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +21 -0
- package/es/lsp-app-contribution.d.ts +19 -0
- package/es/lsp-app-contribution.d.ts.map +1 -0
- package/es/lsp-app-contribution.js +155 -0
- package/es/lsp-protocol.d.ts +10 -0
- package/es/lsp-protocol.d.ts.map +1 -0
- package/es/lsp-protocol.js +1 -0
- package/es/lsp.d.ts +136 -0
- package/es/lsp.d.ts.map +1 -0
- package/es/lsp.js +141 -0
- package/es/manager.d.ts +142 -0
- package/es/manager.d.ts.map +1 -0
- package/es/manager.js +423 -0
- package/es/module.d.ts +3 -0
- package/es/module.d.ts.map +1 -0
- package/es/module.js +21 -0
- package/es/plugin.d.ts +56 -0
- package/es/plugin.d.ts.map +1 -0
- package/es/plugin.js +0 -0
- package/es/positioning.d.ts +66 -0
- package/es/positioning.d.ts.map +1 -0
- package/es/positioning.js +96 -0
- package/es/schema.d.ts +240 -0
- package/es/schema.d.ts.map +1 -0
- package/es/schema.js +0 -0
- package/es/tokens.d.ts +677 -0
- package/es/tokens.d.ts.map +1 -0
- package/es/tokens.js +183 -0
- package/es/utils.d.ts +33 -0
- package/es/utils.d.ts.map +1 -0
- package/es/utils.js +168 -0
- package/es/virtual/document.d.ts +546 -0
- package/es/virtual/document.d.ts.map +1 -0
- package/es/virtual/document.js +1263 -0
- package/es/ws-connection/server-capability-registration.d.ts +19 -0
- package/es/ws-connection/server-capability-registration.d.ts.map +1 -0
- package/es/ws-connection/server-capability-registration.js +51 -0
- package/es/ws-connection/types.d.ts +76 -0
- package/es/ws-connection/types.d.ts.map +1 -0
- package/es/ws-connection/types.js +1 -0
- package/es/ws-connection/ws-connection.d.ts +105 -0
- package/es/ws-connection/ws-connection.d.ts.map +1 -0
- package/es/ws-connection/ws-connection.js +301 -0
- package/package.json +67 -0
- package/src/adapters/adapter.ts +611 -0
- package/src/adapters/notebook-adapter.ts +463 -0
- package/src/adapters/status-message.ts +93 -0
- package/src/connection-manager.ts +626 -0
- package/src/connection.ts +570 -0
- package/src/extractors/index.ts +6 -0
- package/src/extractors/manager.ts +82 -0
- package/src/extractors/text-extractor.ts +94 -0
- package/src/extractors/types.ts +78 -0
- package/src/feature.ts +60 -0
- package/src/index.spec.ts +10 -0
- package/src/index.ts +21 -0
- package/src/lsp-app-contribution.ts +83 -0
- package/src/lsp-protocol.ts +10 -0
- package/src/lsp.ts +160 -0
- package/src/manager.ts +358 -0
- package/src/module.ts +32 -0
- package/src/plugin.ts +62 -0
- package/src/positioning.ts +121 -0
- package/src/schema.ts +249 -0
- package/src/tokens.ts +843 -0
- package/src/utils.ts +109 -0
- package/src/virtual/document.ts +1250 -0
- package/src/ws-connection/server-capability-registration.ts +77 -0
- package/src/ws-connection/types.ts +102 -0
- 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
|
+
}
|