@jupyterlab/lsp 4.0.0-alpha.19 → 4.0.0-alpha.21

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.
@@ -0,0 +1,94 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { IDisposable } from '@lumino/disposable';
5
+ import { ISignal, Signal } from '@lumino/signaling';
6
+
7
+ export class StatusMessage implements IDisposable {
8
+ constructor() {
9
+ this._message = '';
10
+ this._timer = null;
11
+ }
12
+
13
+ /**
14
+ * Signal emitted on status changed event.
15
+ */
16
+ get changed(): ISignal<StatusMessage, void> {
17
+ return this._changed;
18
+ }
19
+
20
+ /**
21
+ * Test whether the object is disposed.
22
+ */
23
+ get isDisposed(): boolean {
24
+ return this._isDisposed;
25
+ }
26
+
27
+ /**
28
+ * Dispose the object.
29
+ */
30
+ dispose(): void {
31
+ if (this.isDisposed) {
32
+ return;
33
+ }
34
+ this._isDisposed = true;
35
+ if (this._timer) {
36
+ window.clearTimeout(this._timer);
37
+ }
38
+ Signal.clearData(this);
39
+ }
40
+
41
+ /**
42
+ * The text message to be shown on the statusbar.
43
+ */
44
+ get message(): string {
45
+ return this._message;
46
+ }
47
+
48
+ /**
49
+ * Set the text message and (optionally) the timeout to remove it.
50
+ * @param message
51
+ * @param timeout - number of ms to until the message is cleaned;
52
+ * -1 if the message should stay up indefinitely;
53
+ * defaults to 3000ms (3 seconds)
54
+ */
55
+ set(message: string, timeout: number = 1000 * 3): void {
56
+ this._expireTimer();
57
+ this._message = message;
58
+ this._changed.emit();
59
+ if (timeout !== -1) {
60
+ this._timer = window.setTimeout(this.clear.bind(this), timeout);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Clear the status message.
66
+ */
67
+ clear(): void {
68
+ this._message = '';
69
+ this._changed.emit();
70
+ }
71
+ /**
72
+ * Timeout reference used to clear the previous `setTimeout` call.
73
+ */
74
+ private _timer: number | null;
75
+
76
+ /**
77
+ * Clear the previous `setTimeout` call.
78
+ */
79
+ private _expireTimer(): void {
80
+ if (this._timer !== null) {
81
+ window.clearTimeout(this._timer);
82
+ this._timer = null;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * The text message to be shown on the statusbar
88
+ */
89
+ private _message: string;
90
+
91
+ private _changed = new Signal<StatusMessage, void>(this);
92
+
93
+ private _isDisposed = false;
94
+ }
@@ -0,0 +1,576 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { ISignal, Signal } from '@lumino/signaling';
5
+
6
+ import {
7
+ ClientNotifications,
8
+ ClientRequests,
9
+ IClientRequestHandler,
10
+ IClientRequestParams,
11
+ IClientResult,
12
+ IDocumentInfo,
13
+ ILSPConnection,
14
+ ILSPOptions,
15
+ IServerRequestHandler,
16
+ IServerRequestParams,
17
+ IServerResult,
18
+ Method,
19
+ ServerNotifications,
20
+ ServerRequests
21
+ } from './tokens';
22
+ import { untilReady } from './utils';
23
+ import {
24
+ registerServerCapability,
25
+ unregisterServerCapability
26
+ } from './ws-connection/server-capability-registration';
27
+ import { LspWsConnection } from './ws-connection/ws-connection';
28
+
29
+ import type * as lsp from 'vscode-languageserver-protocol';
30
+
31
+ import type { MessageConnection } from 'vscode-ws-jsonrpc';
32
+
33
+ /**
34
+ * Helper class to handle client request
35
+ */
36
+ class ClientRequestHandler<
37
+ T extends keyof IClientRequestParams = keyof IClientRequestParams
38
+ > implements IClientRequestHandler
39
+ {
40
+ constructor(
41
+ protected connection: MessageConnection,
42
+ protected method: T,
43
+ protected emitter: LSPConnection
44
+ ) {}
45
+ request(params: IClientRequestParams[T]): Promise<IClientResult[T]> {
46
+ // TODO check if is ready?
47
+ this.emitter.log(MessageKind.clientRequested, {
48
+ method: this.method,
49
+ message: params
50
+ });
51
+ return this.connection
52
+ .sendRequest(this.method, params)
53
+ .then((result: IClientResult[T]) => {
54
+ this.emitter.log(MessageKind.resultForClient, {
55
+ method: this.method,
56
+ message: params
57
+ });
58
+ return result;
59
+ });
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Helper class to handle server responses
65
+ */
66
+ class ServerRequestHandler<
67
+ T extends keyof IServerRequestParams = keyof IServerRequestParams
68
+ > implements IServerRequestHandler
69
+ {
70
+ constructor(
71
+ protected connection: MessageConnection,
72
+ protected method: T,
73
+ protected emitter: LSPConnection
74
+ ) {
75
+ // on request accepts "thenable"
76
+ this.connection.onRequest(method, this._handle.bind(this));
77
+ this._handler = null;
78
+ }
79
+
80
+ setHandler(
81
+ handler: (
82
+ params: IServerRequestParams[T],
83
+ connection?: LSPConnection
84
+ ) => Promise<IServerResult[T]>
85
+ ) {
86
+ this._handler = handler;
87
+ }
88
+
89
+ clearHandler() {
90
+ this._handler = null;
91
+ }
92
+
93
+ private _handler:
94
+ | ((
95
+ params: IServerRequestParams[T],
96
+ connection?: LSPConnection
97
+ ) => Promise<IServerResult[T]>)
98
+ | null;
99
+
100
+ private _handle(
101
+ request: IServerRequestParams[T]
102
+ ): Promise<IServerResult[T] | undefined> {
103
+ this.emitter.log(MessageKind.serverRequested, {
104
+ method: this.method,
105
+ message: request
106
+ });
107
+ if (!this._handler) {
108
+ return new Promise(() => undefined);
109
+ }
110
+ return this._handler(request, this.emitter).then(result => {
111
+ this.emitter.log(MessageKind.responseForServer, {
112
+ method: this.method,
113
+ message: result
114
+ });
115
+ return result;
116
+ });
117
+ }
118
+ }
119
+
120
+ export const Provider: { [key: string]: keyof lsp.ServerCapabilities } = {
121
+ TEXT_DOCUMENT_SYNC: 'textDocumentSync',
122
+ COMPLETION: 'completionProvider',
123
+ HOVER: 'hoverProvider',
124
+ SIGNATURE_HELP: 'signatureHelpProvider',
125
+ DECLARATION: 'declarationProvider',
126
+ DEFINITION: 'definitionProvider',
127
+ TYPE_DEFINITION: 'typeDefinitionProvider',
128
+ IMPLEMENTATION: 'implementationProvider',
129
+ REFERENCES: 'referencesProvider',
130
+ DOCUMENT_HIGHLIGHT: 'documentHighlightProvider',
131
+ DOCUMENT_SYMBOL: 'documentSymbolProvider',
132
+ CODE_ACTION: 'codeActionProvider',
133
+ CODE_LENS: 'codeLensProvider',
134
+ DOCUMENT_LINK: 'documentLinkProvider',
135
+ COLOR: 'colorProvider',
136
+ DOCUMENT_FORMATTING: 'documentFormattingProvider',
137
+ DOCUMENT_RANGE_FORMATTING: 'documentRangeFormattingProvider',
138
+ DOCUMENT_ON_TYPE_FORMATTING: 'documentOnTypeFormattingProvider',
139
+ RENAME: 'renameProvider',
140
+ FOLDING_RANGE: 'foldingRangeProvider',
141
+ EXECUTE_COMMAND: 'executeCommandProvider',
142
+ SELECTION_RANGE: 'selectionRangeProvider',
143
+ WORKSPACE_SYMBOL: 'workspaceSymbolProvider',
144
+ WORKSPACE: 'workspace'
145
+ };
146
+
147
+ type AnyMethodType =
148
+ | typeof Method.ServerNotification
149
+ | typeof Method.ClientNotification
150
+ | typeof Method.ClientRequest
151
+ | typeof Method.ServerRequest;
152
+ type AnyMethod =
153
+ | Method.ServerNotification
154
+ | Method.ClientNotification
155
+ | Method.ClientRequest
156
+ | Method.ServerRequest;
157
+
158
+ /**
159
+ * Create a map between the request method and its handler
160
+ */
161
+ function createMethodMap<T, H, U extends keyof T = keyof T>(
162
+ methods: AnyMethodType,
163
+ handlerFactory: (method: U) => H
164
+ ): T {
165
+ const result: { [key in U]?: H } = {};
166
+ for (let method of Object.values(methods)) {
167
+ result[method as U] = handlerFactory(method as U);
168
+ }
169
+ return result as T;
170
+ }
171
+
172
+ enum MessageKind {
173
+ clientNotifiedServer,
174
+ serverNotifiedClient,
175
+ serverRequested,
176
+ clientRequested,
177
+ resultForClient,
178
+ responseForServer
179
+ }
180
+
181
+ interface IMessageLog<T extends AnyMethod = AnyMethod> {
182
+ method: T;
183
+ message: any;
184
+ }
185
+
186
+ export class LSPConnection extends LspWsConnection implements ILSPConnection {
187
+ constructor(options: ILSPOptions) {
188
+ super(options);
189
+ this._options = options;
190
+ this.logAllCommunication = false;
191
+ this.serverIdentifier = options.serverIdentifier;
192
+ this.serverLanguage = options.languageId;
193
+ this.documentsToOpen = [];
194
+ this.clientNotifications =
195
+ this.constructNotificationHandlers<ClientNotifications>(
196
+ Method.ClientNotification
197
+ );
198
+ this.serverNotifications =
199
+ this.constructNotificationHandlers<ServerNotifications>(
200
+ Method.ServerNotification
201
+ );
202
+ }
203
+
204
+ /**
205
+ * Identifier of the language server
206
+ */
207
+ readonly serverIdentifier?: string;
208
+
209
+ /**
210
+ * Language of the language server
211
+ */
212
+ readonly serverLanguage?: string;
213
+
214
+ /**
215
+ * Notifications comes from the client.
216
+ */
217
+ readonly clientNotifications: ClientNotifications;
218
+
219
+ /**
220
+ * Notifications comes from the server.
221
+ */
222
+ readonly serverNotifications: ServerNotifications;
223
+
224
+ /**
225
+ * Requests comes from the client.
226
+ */
227
+ clientRequests: ClientRequests;
228
+
229
+ /**
230
+ * Responses comes from the server.
231
+ */
232
+ serverRequests: ServerRequests;
233
+
234
+ /**
235
+ * Should log all communication?
236
+ */
237
+ logAllCommunication: boolean;
238
+
239
+ /**
240
+ * Signal emitted when the connection is closed.
241
+ */
242
+ get closeSignal(): ISignal<ILSPConnection, boolean> {
243
+ return this._closeSignal;
244
+ }
245
+
246
+ /**
247
+ * Signal emitted when the connection receives an error
248
+ * message..
249
+ */
250
+ get errorSignal(): ISignal<ILSPConnection, any> {
251
+ return this._errorSignal;
252
+ }
253
+
254
+ /**
255
+ * Signal emitted when the connection is initialized.
256
+ */
257
+ get serverInitialized(): ISignal<
258
+ ILSPConnection,
259
+ lsp.ServerCapabilities<any>
260
+ > {
261
+ return this._serverInitialized;
262
+ }
263
+
264
+ /**
265
+ * Dispose the connection.
266
+ */
267
+ dispose(): void {
268
+ if (this.isDisposed) {
269
+ return;
270
+ }
271
+ Object.values(this.serverRequests).forEach(request =>
272
+ request.clearHandler()
273
+ );
274
+ this.close();
275
+ super.dispose();
276
+ }
277
+
278
+ /**
279
+ * Helper to print the logs to logger, for now we are using
280
+ * directly the browser's console.
281
+ */
282
+ log(kind: MessageKind, message: IMessageLog): void {
283
+ if (this.logAllCommunication) {
284
+ console.log(kind, message);
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Send the open request to the backend when the server is
290
+ * ready.
291
+ */
292
+ sendOpenWhenReady(documentInfo: IDocumentInfo): void {
293
+ if (this.isReady) {
294
+ this.sendOpen(documentInfo);
295
+ } else {
296
+ this.documentsToOpen.push(documentInfo);
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Send the document changes to the server.
302
+ */
303
+ sendSelectiveChange(
304
+ changeEvent: lsp.TextDocumentContentChangeEvent,
305
+ documentInfo: IDocumentInfo
306
+ ): void {
307
+ this._sendChange([changeEvent], documentInfo);
308
+ }
309
+
310
+ /**
311
+ * Send all changes to the server.
312
+ */
313
+ sendFullTextChange(text: string, documentInfo: IDocumentInfo): void {
314
+ this._sendChange([{ text }], documentInfo);
315
+ }
316
+
317
+ /**
318
+ * Check if a provider is available in the registered capabilities.
319
+ */
320
+ provides(provider: keyof lsp.ServerCapabilities): boolean {
321
+ return !!(this.serverCapabilities && this.serverCapabilities[provider]);
322
+ }
323
+
324
+ /**
325
+ * Close the connection to the server.
326
+ */
327
+ close(): void {
328
+ try {
329
+ this._closingManually = true;
330
+ super.close();
331
+ } catch (e) {
332
+ this._closingManually = false;
333
+ }
334
+ }
335
+
336
+ /**
337
+ * initialize a connection over a web socket that speaks the LSP
338
+ */
339
+ connect(socket: WebSocket): void {
340
+ super.connect(socket);
341
+ untilReady(() => {
342
+ return this.isConnected;
343
+ }, -1)
344
+ .then(() => {
345
+ const disposable = this.connection.onClose(() => {
346
+ this._isConnected = false;
347
+ this._closeSignal.emit(this._closingManually);
348
+ });
349
+ this._disposables.push(disposable);
350
+ })
351
+ .catch(() => {
352
+ console.error('Could not connect onClose signal');
353
+ });
354
+ }
355
+
356
+ /**
357
+ * Get send request to the server to get completion results
358
+ * from a completion item
359
+ */
360
+ async getCompletionResolve(
361
+ completionItem: lsp.CompletionItem
362
+ ): Promise<lsp.CompletionItem | undefined> {
363
+ if (!this.isReady) {
364
+ return;
365
+ }
366
+ return this.connection.sendRequest<lsp.CompletionItem>(
367
+ 'completionItem/resolve',
368
+ completionItem
369
+ );
370
+ }
371
+
372
+ /**
373
+ * List of documents waiting to be opened once the connection
374
+ * is ready.
375
+ */
376
+ protected documentsToOpen: IDocumentInfo[];
377
+
378
+ /**
379
+ * Generate the notification handlers
380
+ */
381
+ protected constructNotificationHandlers<
382
+ T extends ServerNotifications | ClientNotifications
383
+ >(
384
+ methods: typeof Method.ServerNotification | typeof Method.ClientNotification
385
+ ): T {
386
+ const factory = () => new Signal<any, any>(this);
387
+ return createMethodMap<T, Signal<any, any>>(methods, factory);
388
+ }
389
+
390
+ /**
391
+ * Generate the client request handler
392
+ */
393
+ protected constructClientRequestHandler<
394
+ T extends ClientRequests,
395
+ U extends keyof T = keyof T
396
+ >(methods: typeof Method.ClientRequest): T {
397
+ return createMethodMap<T, IClientRequestHandler>(
398
+ methods,
399
+ method =>
400
+ new ClientRequestHandler(this.connection, method as U as any, this)
401
+ );
402
+ }
403
+
404
+ /**
405
+ * Generate the server response handler
406
+ */
407
+ protected constructServerRequestHandler<
408
+ T extends ServerRequests,
409
+ U extends keyof T = keyof T
410
+ >(methods: typeof Method.ServerRequest): T {
411
+ return createMethodMap<T, IServerRequestHandler>(
412
+ methods,
413
+ method =>
414
+ new ServerRequestHandler(this.connection, method as U as any, this)
415
+ );
416
+ }
417
+
418
+ /**
419
+ * Initialization parameters to be sent to the language server.
420
+ * Subclasses can overload this when adding more features.
421
+ */
422
+ protected initializeParams(): lsp.InitializeParams {
423
+ return {
424
+ ...super.initializeParams(),
425
+ capabilities: this._options.capabilities,
426
+ initializationOptions: null,
427
+ processId: null,
428
+ workspaceFolders: null
429
+ };
430
+ }
431
+
432
+ /**
433
+ * Callback called when the server is initialized.
434
+ */
435
+ protected onServerInitialized(params: lsp.InitializeResult): void {
436
+ this.afterInitialized();
437
+ super.onServerInitialized(params);
438
+ while (this.documentsToOpen.length) {
439
+ this.sendOpen(this.documentsToOpen.pop()!);
440
+ }
441
+ this._serverInitialized.emit(this.serverCapabilities);
442
+ }
443
+
444
+ /**
445
+ * Once the server is initialized, this method generates the
446
+ * client and server handlers
447
+ */
448
+ protected afterInitialized(): void {
449
+ const disposable = this.connection.onError(e => this._errorSignal.emit(e));
450
+ this._disposables.push(disposable);
451
+ for (const method of Object.values(
452
+ Method.ServerNotification
453
+ ) as (keyof ServerNotifications)[]) {
454
+ const signal = this.serverNotifications[method] as Signal<any, any>;
455
+ const disposable = this.connection.onNotification(method, params => {
456
+ this.log(MessageKind.serverNotifiedClient, {
457
+ method,
458
+ message: params
459
+ });
460
+ signal.emit(params);
461
+ });
462
+ this._disposables.push(disposable);
463
+ }
464
+
465
+ for (const method of Object.values(
466
+ Method.ClientNotification
467
+ ) as (keyof ClientNotifications)[]) {
468
+ const signal = this.clientNotifications[method] as Signal<any, any>;
469
+ signal.connect((emitter, params) => {
470
+ this.log(MessageKind.clientNotifiedServer, {
471
+ method,
472
+ message: params
473
+ });
474
+ this.connection.sendNotification(method, params).catch(console.error);
475
+ });
476
+ }
477
+
478
+ this.clientRequests = this.constructClientRequestHandler<ClientRequests>(
479
+ Method.ClientRequest
480
+ );
481
+ this.serverRequests = this.constructServerRequestHandler<ServerRequests>(
482
+ Method.ServerRequest
483
+ );
484
+
485
+ this.serverRequests['client/registerCapability'].setHandler(
486
+ async (params: lsp.RegistrationParams) => {
487
+ params.registrations.forEach(
488
+ (capabilityRegistration: lsp.Registration) => {
489
+ try {
490
+ const updatedCapabilities = registerServerCapability(
491
+ this.serverCapabilities,
492
+ capabilityRegistration
493
+ );
494
+ if (updatedCapabilities === null) {
495
+ console.error(
496
+ `Failed to register server capability: ${capabilityRegistration}`
497
+ );
498
+ return;
499
+ }
500
+ this.serverCapabilities = updatedCapabilities;
501
+ } catch (err) {
502
+ console.error(err);
503
+ }
504
+ }
505
+ );
506
+ }
507
+ );
508
+
509
+ this.serverRequests['client/unregisterCapability'].setHandler(
510
+ async (params: lsp.UnregistrationParams) => {
511
+ params.unregisterations.forEach(
512
+ (capabilityUnregistration: lsp.Unregistration) => {
513
+ this.serverCapabilities = unregisterServerCapability(
514
+ this.serverCapabilities,
515
+ capabilityUnregistration
516
+ );
517
+ }
518
+ );
519
+ }
520
+ );
521
+
522
+ this.serverRequests['workspace/configuration'].setHandler(async params => {
523
+ return params.items.map(item => {
524
+ // LSP: "If the client can’t provide a configuration setting for a given scope
525
+ // then `null` needs to be present in the returned array."
526
+
527
+ // for now we do not support configuration, but yaml server does not respect
528
+ // client capability so we have a handler just for that
529
+ return null;
530
+ });
531
+ });
532
+ }
533
+
534
+ /**
535
+ * Is the connection is closed manually?
536
+ */
537
+ private _closingManually = false;
538
+
539
+ private _options: ILSPOptions;
540
+
541
+ private _closeSignal: Signal<ILSPConnection, boolean> = new Signal(this);
542
+ private _errorSignal: Signal<ILSPConnection, any> = new Signal(this);
543
+ private _serverInitialized: Signal<
544
+ ILSPConnection,
545
+ lsp.ServerCapabilities<any>
546
+ > = new Signal(this);
547
+
548
+ /**
549
+ * Send the document changed data to the server.
550
+ */
551
+ private _sendChange(
552
+ changeEvents: lsp.TextDocumentContentChangeEvent[],
553
+ documentInfo: IDocumentInfo
554
+ ) {
555
+ if (!this.isReady) {
556
+ return;
557
+ }
558
+ if (documentInfo.uri.length === 0) {
559
+ return;
560
+ }
561
+ if (!this.openedUris.get(documentInfo.uri)) {
562
+ this.sendOpen(documentInfo);
563
+ }
564
+ const textDocumentChange: lsp.DidChangeTextDocumentParams = {
565
+ textDocument: {
566
+ uri: documentInfo.uri,
567
+ version: documentInfo.version
568
+ } as lsp.VersionedTextDocumentIdentifier,
569
+ contentChanges: changeEvents
570
+ };
571
+ this.connection
572
+ .sendNotification('textDocument/didChange', textDocumentChange)
573
+ .catch(console.error);
574
+ documentInfo.version++;
575
+ }
576
+ }