@jupyterlab/lsp 4.6.0 → 4.7.0-alpha.0
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/lib/adapters/adapter.d.ts +4 -0
- package/lib/adapters/adapter.js +62 -7
- package/lib/adapters/adapter.js.map +1 -1
- package/lib/adapters/editorAdapter.d.ts +3 -3
- package/lib/adapters/editorAdapter.js +0 -1
- package/lib/adapters/editorAdapter.js.map +1 -1
- package/lib/connection.d.ts +5 -1
- package/lib/connection.js +64 -0
- package/lib/connection.js.map +1 -1
- package/lib/connection_manager.d.ts +3 -2
- package/lib/connection_manager.js +13 -4
- package/lib/connection_manager.js.map +1 -1
- package/lib/manager.d.ts +38 -1
- package/lib/manager.js +176 -40
- package/lib/manager.js.map +1 -1
- package/lib/plugin.d.ts +2 -2
- package/lib/tokens.d.ts +124 -3
- package/lib/tokens.js +1 -0
- package/lib/tokens.js.map +1 -1
- package/package.json +9 -9
- package/src/adapters/adapter.ts +87 -14
- package/src/adapters/editorAdapter.ts +4 -5
- package/src/connection.ts +97 -0
- package/src/connection_manager.ts +14 -4
- package/src/manager.ts +229 -44
- package/src/plugin.ts +3 -2
- package/src/tokens.ts +147 -3
package/src/adapters/adapter.ts
CHANGED
|
@@ -525,21 +525,40 @@ export abstract class WidgetLSPAdapter<
|
|
|
525
525
|
virtualDocument: VirtualDocument,
|
|
526
526
|
sendOpen = false
|
|
527
527
|
): Promise<void> {
|
|
528
|
-
virtualDocument.
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
);
|
|
532
|
-
const connectionContext = await this._connect(virtualDocument).catch(
|
|
533
|
-
console.error
|
|
534
|
-
);
|
|
528
|
+
if (this._isDisposed || virtualDocument.isDisposed) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
535
531
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
532
|
+
const pendingConnection =
|
|
533
|
+
this._pendingDocumentConnections.get(virtualDocument);
|
|
534
|
+
if (pendingConnection) {
|
|
535
|
+
return pendingConnection;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const connectionPromise = (async () => {
|
|
539
|
+
virtualDocument.foreignDocumentOpened.connect(
|
|
540
|
+
this.onForeignDocumentOpened,
|
|
541
|
+
this
|
|
542
|
+
);
|
|
543
|
+
const connectionContext = await this._connect(virtualDocument).catch(
|
|
544
|
+
console.error
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
if (connectionContext && connectionContext.connection) {
|
|
548
|
+
virtualDocument.changed.connect(this.documentChanged, this);
|
|
549
|
+
if (sendOpen) {
|
|
550
|
+
connectionContext.connection.sendOpenWhenReady(
|
|
551
|
+
virtualDocument.documentInfo
|
|
552
|
+
);
|
|
553
|
+
}
|
|
542
554
|
}
|
|
555
|
+
})();
|
|
556
|
+
this._pendingDocumentConnections.set(virtualDocument, connectionPromise);
|
|
557
|
+
|
|
558
|
+
try {
|
|
559
|
+
await connectionPromise;
|
|
560
|
+
} finally {
|
|
561
|
+
this._pendingDocumentConnections.delete(virtualDocument);
|
|
543
562
|
}
|
|
544
563
|
}
|
|
545
564
|
|
|
@@ -549,7 +568,7 @@ export abstract class WidgetLSPAdapter<
|
|
|
549
568
|
protected initVirtual(): void {
|
|
550
569
|
this._virtualDocument?.dispose();
|
|
551
570
|
this._virtualDocument = this.createVirtualDocument();
|
|
552
|
-
this.
|
|
571
|
+
this._toggleVirtualDocumentUpdates();
|
|
553
572
|
}
|
|
554
573
|
|
|
555
574
|
/**
|
|
@@ -616,6 +635,11 @@ export abstract class WidgetLSPAdapter<
|
|
|
616
635
|
private _updateFinished: Promise<void>;
|
|
617
636
|
private _virtualDocument: VirtualDocument | null = null;
|
|
618
637
|
private _editorToAdapter: WeakMap<Document.IEditor, EditorAdapter>;
|
|
638
|
+
private _pendingDocumentConnections = new WeakMap<
|
|
639
|
+
VirtualDocument,
|
|
640
|
+
Promise<void>
|
|
641
|
+
>();
|
|
642
|
+
private _queuedConnectionRetries = new WeakSet<VirtualDocument>();
|
|
619
643
|
|
|
620
644
|
private _onEditorAdded(
|
|
621
645
|
sender: WidgetLSPAdapter<T>,
|
|
@@ -697,6 +721,10 @@ export abstract class WidgetLSPAdapter<
|
|
|
697
721
|
|
|
698
722
|
let connection = await this.connectionManager.connect(options);
|
|
699
723
|
|
|
724
|
+
if (this._isDisposed || virtualDocument.isDisposed) {
|
|
725
|
+
return undefined;
|
|
726
|
+
}
|
|
727
|
+
|
|
700
728
|
if (connection) {
|
|
701
729
|
await this.onConnected({ virtualDocument, connection });
|
|
702
730
|
|
|
@@ -755,6 +783,10 @@ export abstract class WidgetLSPAdapter<
|
|
|
755
783
|
* the LSP server status changed or when a LSP feature is registered.
|
|
756
784
|
*/
|
|
757
785
|
private _onLspSessionOrFeatureChanged(): void {
|
|
786
|
+
this._toggleVirtualDocumentUpdates(true);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
private _toggleVirtualDocumentUpdates(retryConnection = false): void {
|
|
758
790
|
if (!this._virtualDocument) {
|
|
759
791
|
return;
|
|
760
792
|
}
|
|
@@ -763,8 +795,49 @@ export abstract class WidgetLSPAdapter<
|
|
|
763
795
|
|
|
764
796
|
if (this._shouldUpdateVirtualDocument()) {
|
|
765
797
|
model.contentChanged.connect(this._onContentChanged, this);
|
|
798
|
+
if (retryConnection) {
|
|
799
|
+
this._connectVirtualDocumentIfNeeded(this._virtualDocument);
|
|
800
|
+
}
|
|
766
801
|
} else {
|
|
767
802
|
model.contentChanged.disconnect(this._onContentChanged, this);
|
|
768
803
|
}
|
|
769
804
|
}
|
|
805
|
+
|
|
806
|
+
private _connectVirtualDocumentIfNeeded(
|
|
807
|
+
virtualDocument: VirtualDocument
|
|
808
|
+
): void {
|
|
809
|
+
if (
|
|
810
|
+
this._isDisposed ||
|
|
811
|
+
virtualDocument.isDisposed ||
|
|
812
|
+
this.connectionManager.connections.has(virtualDocument.uri)
|
|
813
|
+
) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const pendingConnection =
|
|
818
|
+
this._pendingDocumentConnections.get(virtualDocument);
|
|
819
|
+
if (pendingConnection) {
|
|
820
|
+
if (this._queuedConnectionRetries.has(virtualDocument)) {
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
this._queuedConnectionRetries.add(virtualDocument);
|
|
824
|
+
void pendingConnection
|
|
825
|
+
.finally(() => {
|
|
826
|
+
if (!this._queuedConnectionRetries.delete(virtualDocument)) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (
|
|
830
|
+
this._virtualDocument === virtualDocument &&
|
|
831
|
+
this._shouldUpdateVirtualDocument() &&
|
|
832
|
+
!this.connectionManager.connections.has(virtualDocument.uri)
|
|
833
|
+
) {
|
|
834
|
+
this._connectVirtualDocumentIfNeeded(virtualDocument);
|
|
835
|
+
}
|
|
836
|
+
})
|
|
837
|
+
.catch(console.warn);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
this.connectDocument(virtualDocument, false).catch(console.warn);
|
|
842
|
+
}
|
|
770
843
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
3
|
|
|
5
4
|
import type {
|
|
6
5
|
IConfigurableExtension,
|
|
@@ -74,7 +73,7 @@ export class EditorAdapter implements IDisposable {
|
|
|
74
73
|
});
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
private _widgetAdapter: WidgetLSPAdapter
|
|
76
|
+
private _widgetAdapter: WidgetLSPAdapter;
|
|
78
77
|
private _extensions: EditorAdapter.ILSPEditorExtensionFactory[];
|
|
79
78
|
}
|
|
80
79
|
|
|
@@ -103,7 +102,7 @@ export namespace EditorAdapter {
|
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
export interface ILSPEditorExtensionFactory extends Omit<
|
|
106
|
-
IEditorExtensionFactory<
|
|
105
|
+
IEditorExtensionFactory<unknown>,
|
|
107
106
|
'factory'
|
|
108
107
|
> {
|
|
109
108
|
/**
|
|
@@ -114,7 +113,7 @@ export namespace EditorAdapter {
|
|
|
114
113
|
*/
|
|
115
114
|
readonly factory: (
|
|
116
115
|
options: IFactoryOptions
|
|
117
|
-
) => IConfigurableExtension<
|
|
116
|
+
) => IConfigurableExtension<unknown> | null;
|
|
118
117
|
}
|
|
119
118
|
|
|
120
119
|
export interface IFactoryOptions extends IEditorExtensionFactory.IOptions {
|
|
@@ -129,6 +128,6 @@ export namespace EditorAdapter {
|
|
|
129
128
|
/**
|
|
130
129
|
* The widget lsp adapter.
|
|
131
130
|
*/
|
|
132
|
-
widgetAdapter: WidgetLSPAdapter
|
|
131
|
+
widgetAdapter: WidgetLSPAdapter;
|
|
133
132
|
}
|
|
134
133
|
}
|
package/src/connection.ts
CHANGED
|
@@ -183,6 +183,42 @@ interface IMessageLog<T extends AnyMethod = AnyMethod> {
|
|
|
183
183
|
message: any;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
interface ICancellationDisposable {
|
|
187
|
+
dispose(): void;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
interface ISignalCancellationToken {
|
|
191
|
+
readonly isCancellationRequested: boolean;
|
|
192
|
+
onCancellationRequested(
|
|
193
|
+
listener: (event: unknown) => void,
|
|
194
|
+
thisArgs?: unknown,
|
|
195
|
+
disposables?: ICancellationDisposable[]
|
|
196
|
+
): ICancellationDisposable;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function cancellationTokenFromSignal(
|
|
200
|
+
signal: AbortSignal
|
|
201
|
+
): ISignalCancellationToken {
|
|
202
|
+
return {
|
|
203
|
+
get isCancellationRequested(): boolean {
|
|
204
|
+
return signal.aborted;
|
|
205
|
+
},
|
|
206
|
+
onCancellationRequested: (listener, thisArgs, disposables) => {
|
|
207
|
+
const onAbort = () => {
|
|
208
|
+
listener.call(thisArgs, undefined);
|
|
209
|
+
};
|
|
210
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
211
|
+
const disposable = {
|
|
212
|
+
dispose: () => {
|
|
213
|
+
signal.removeEventListener('abort', onAbort);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
disposables?.push(disposable);
|
|
217
|
+
return disposable;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
186
222
|
export class LSPConnection extends LspWsConnection implements ILSPConnection {
|
|
187
223
|
constructor(options: ILSPOptions) {
|
|
188
224
|
super(options);
|
|
@@ -324,6 +360,67 @@ export class LSPConnection extends LspWsConnection implements ILSPConnection {
|
|
|
324
360
|
return !!(this.serverCapabilities && this.serverCapabilities[capability]);
|
|
325
361
|
}
|
|
326
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Send a request to the language server.
|
|
365
|
+
*/
|
|
366
|
+
async request<M extends Method.ClientRequest>(
|
|
367
|
+
method: M,
|
|
368
|
+
params: IClientRequestParams[M],
|
|
369
|
+
options: ILSPConnection.IRequestOptions = {}
|
|
370
|
+
): Promise<IClientResult[M]> {
|
|
371
|
+
if (this.isDisposed) {
|
|
372
|
+
throw new Error('Cannot send an LSP request on a disposed connection.');
|
|
373
|
+
}
|
|
374
|
+
if (!this.isReady) {
|
|
375
|
+
throw new Error(
|
|
376
|
+
'Cannot send an LSP request before the connection is ready.'
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const { signal } = options;
|
|
381
|
+
if (signal?.aborted) {
|
|
382
|
+
throw (
|
|
383
|
+
signal.reason ??
|
|
384
|
+
new DOMException('The request was aborted.', 'AbortError')
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const cancellation = signal
|
|
389
|
+
? cancellationTokenFromSignal(signal)
|
|
390
|
+
: undefined;
|
|
391
|
+
let rejectOnAbort: ((reason?: unknown) => void) | undefined;
|
|
392
|
+
const aborted = signal
|
|
393
|
+
? new Promise<never>((_, reject) => {
|
|
394
|
+
rejectOnAbort = reject;
|
|
395
|
+
})
|
|
396
|
+
: undefined;
|
|
397
|
+
const cancel = () => {
|
|
398
|
+
rejectOnAbort?.(
|
|
399
|
+
signal?.reason ??
|
|
400
|
+
new DOMException('The request was aborted.', 'AbortError')
|
|
401
|
+
);
|
|
402
|
+
};
|
|
403
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
404
|
+
|
|
405
|
+
this.log(MessageKind.clientRequested, { method, message: params });
|
|
406
|
+
try {
|
|
407
|
+
const response = cancellation
|
|
408
|
+
? this.connection.sendRequest<IClientResult[M]>(
|
|
409
|
+
method,
|
|
410
|
+
params,
|
|
411
|
+
cancellation
|
|
412
|
+
)
|
|
413
|
+
: this.connection.sendRequest<IClientResult[M]>(method, params);
|
|
414
|
+
const result = aborted
|
|
415
|
+
? await Promise.race([response, aborted])
|
|
416
|
+
: await response;
|
|
417
|
+
this.log(MessageKind.resultForClient, { method, message: result });
|
|
418
|
+
return result;
|
|
419
|
+
} finally {
|
|
420
|
+
signal?.removeEventListener('abort', cancel);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
327
424
|
/**
|
|
328
425
|
* Close the connection to the server.
|
|
329
426
|
*/
|
|
@@ -370,7 +370,8 @@ export class DocumentConnectionManager implements ILSPDocumentConnectionManager
|
|
|
370
370
|
|
|
371
371
|
/**
|
|
372
372
|
* Create a new connection to the language server
|
|
373
|
-
*
|
|
373
|
+
*
|
|
374
|
+
* Returns a promise of the LSP connection.
|
|
374
375
|
*/
|
|
375
376
|
async connect(
|
|
376
377
|
options: ISocketConnectionOptions,
|
|
@@ -487,7 +488,7 @@ export class DocumentConnectionManager implements ILSPDocumentConnectionManager
|
|
|
487
488
|
/**
|
|
488
489
|
* Create the LSP connection for requested virtual document.
|
|
489
490
|
*
|
|
490
|
-
*
|
|
491
|
+
* Returns a promise of the LSP connection.
|
|
491
492
|
*/
|
|
492
493
|
|
|
493
494
|
private async _connectSocket(
|
|
@@ -725,8 +726,17 @@ namespace Private {
|
|
|
725
726
|
): Promise<LSPConnection> {
|
|
726
727
|
let connection = _connections.get(languageServerId);
|
|
727
728
|
if (!connection) {
|
|
728
|
-
const
|
|
729
|
-
const
|
|
729
|
+
const serverManager = Private.getLanguageServerManager();
|
|
730
|
+
const { settings } = serverManager;
|
|
731
|
+
const transportFactory =
|
|
732
|
+
serverManager.getTransportFactory(languageServerId);
|
|
733
|
+
const socket = transportFactory
|
|
734
|
+
? transportFactory({
|
|
735
|
+
languageServerId,
|
|
736
|
+
socketUrl: uris.socket,
|
|
737
|
+
settings
|
|
738
|
+
})
|
|
739
|
+
: new settings.WebSocket(uris.socket);
|
|
730
740
|
const connection = new LSPConnection({
|
|
731
741
|
languageId: language,
|
|
732
742
|
serverUri: uris.server,
|
package/src/manager.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
3
|
|
|
5
4
|
import { URLExt } from '@jupyterlab/coreutils';
|
|
6
5
|
import { ServerConnection } from '@jupyterlab/services';
|
|
6
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
7
|
+
import { DisposableDelegate } from '@lumino/disposable';
|
|
8
|
+
import type { IDisposable } from '@lumino/disposable';
|
|
7
9
|
import type { ISignal } from '@lumino/signaling';
|
|
8
10
|
import { Signal } from '@lumino/signaling';
|
|
9
11
|
|
|
10
12
|
import type {
|
|
13
|
+
ILanguageServerProvider,
|
|
11
14
|
TLanguageServerConfigurations,
|
|
12
15
|
TLanguageServerId,
|
|
13
16
|
TSessionMap,
|
|
@@ -15,7 +18,11 @@ import type {
|
|
|
15
18
|
} from './tokens';
|
|
16
19
|
import { ILanguageServerManager } from './tokens';
|
|
17
20
|
import type { ServerSpecProperties } from './schema';
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
interface IProviderRegistration {
|
|
23
|
+
provider: ILanguageServerProvider;
|
|
24
|
+
onSessionsChanged: () => void;
|
|
25
|
+
}
|
|
19
26
|
|
|
20
27
|
export class LanguageServerManager implements ILanguageServerManager {
|
|
21
28
|
constructor(options: ILanguageServerManager.IOptions) {
|
|
@@ -106,6 +113,10 @@ export class LanguageServerManager implements ILanguageServerManager {
|
|
|
106
113
|
disable(): void {
|
|
107
114
|
this._enabled = false;
|
|
108
115
|
this._sessions = new Map();
|
|
116
|
+
this._specs = new Map();
|
|
117
|
+
this._serverSessions = new Map();
|
|
118
|
+
this._serverSpecs = new Map();
|
|
119
|
+
this._transports.clear();
|
|
109
120
|
this._sessionsChanged.emit(void 0);
|
|
110
121
|
}
|
|
111
122
|
|
|
@@ -118,6 +129,18 @@ export class LanguageServerManager implements ILanguageServerManager {
|
|
|
118
129
|
}
|
|
119
130
|
this._isDisposed = true;
|
|
120
131
|
|
|
132
|
+
for (const registration of this._providers.values()) {
|
|
133
|
+
if (registration.provider.sessionsChanged) {
|
|
134
|
+
registration.provider.sessionsChanged.disconnect(
|
|
135
|
+
registration.onSessionsChanged
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
this._providers.clear();
|
|
140
|
+
this._serverSessions.clear();
|
|
141
|
+
this._serverSpecs.clear();
|
|
142
|
+
this._transports.clear();
|
|
143
|
+
|
|
121
144
|
Signal.clearData(this);
|
|
122
145
|
}
|
|
123
146
|
|
|
@@ -128,6 +151,52 @@ export class LanguageServerManager implements ILanguageServerManager {
|
|
|
128
151
|
this._configuration = configuration;
|
|
129
152
|
}
|
|
130
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Register a runtime language server provider.
|
|
156
|
+
*/
|
|
157
|
+
registerProvider(provider: ILanguageServerProvider): IDisposable {
|
|
158
|
+
if (this._providers.has(provider.id)) {
|
|
159
|
+
console.warn(
|
|
160
|
+
`Language server provider with id ${provider.id} is already registered`
|
|
161
|
+
);
|
|
162
|
+
return new DisposableDelegate(() => undefined);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const registration = {
|
|
166
|
+
provider,
|
|
167
|
+
onSessionsChanged: () => {
|
|
168
|
+
void this.fetchSessions().catch(console.error);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
this._providers.set(provider.id, registration);
|
|
172
|
+
|
|
173
|
+
if (provider.sessionsChanged) {
|
|
174
|
+
provider.sessionsChanged.connect(registration.onSessionsChanged);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
void this.fetchSessions().catch(console.error);
|
|
178
|
+
|
|
179
|
+
return new DisposableDelegate(() => {
|
|
180
|
+
if (this._providers.get(provider.id) !== registration) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (provider.sessionsChanged) {
|
|
184
|
+
provider.sessionsChanged.disconnect(registration.onSessionsChanged);
|
|
185
|
+
}
|
|
186
|
+
this._providers.delete(provider.id);
|
|
187
|
+
void this.fetchSessions().catch(console.error);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get a runtime transport factory for a language server.
|
|
193
|
+
*/
|
|
194
|
+
getTransportFactory(
|
|
195
|
+
languageServerId: TLanguageServerId
|
|
196
|
+
): ILanguageServerProvider.TTransportFactory | null {
|
|
197
|
+
return this._transports.get(languageServerId) ?? null;
|
|
198
|
+
}
|
|
199
|
+
|
|
131
200
|
/**
|
|
132
201
|
* Get matching language server for input language option.
|
|
133
202
|
*/
|
|
@@ -176,58 +245,62 @@ export class LanguageServerManager implements ILanguageServerManager {
|
|
|
176
245
|
if (!this._enabled) {
|
|
177
246
|
return;
|
|
178
247
|
}
|
|
179
|
-
let
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
248
|
+
let statusCode = this._statusCode;
|
|
249
|
+
const sessions: TSessionMap = new Map();
|
|
250
|
+
const specs: TSpecsMap = new Map();
|
|
251
|
+
const transports = new Map<
|
|
252
|
+
TLanguageServerId,
|
|
253
|
+
ILanguageServerProvider.TTransportFactory
|
|
254
|
+
>();
|
|
255
|
+
|
|
256
|
+
const serverData = await this._fetchServerData();
|
|
257
|
+
if (serverData?.statusCode !== undefined) {
|
|
258
|
+
statusCode = serverData.statusCode;
|
|
259
|
+
this._statusCode = statusCode;
|
|
260
|
+
}
|
|
261
|
+
if (serverData?.retryScheduled && this._providers.size === 0) {
|
|
194
262
|
return;
|
|
195
263
|
}
|
|
264
|
+
if (serverData?.sessions !== undefined) {
|
|
265
|
+
this._syncSnapshot(this._serverSessions, serverData.sessions);
|
|
266
|
+
}
|
|
267
|
+
if (serverData?.specs !== undefined) {
|
|
268
|
+
this._syncSnapshot(this._serverSpecs, serverData.specs);
|
|
269
|
+
}
|
|
270
|
+
if (serverData) {
|
|
271
|
+
if (serverData.version !== undefined) {
|
|
272
|
+
this.version = serverData.version;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
196
275
|
|
|
197
|
-
|
|
276
|
+
this._mergeSnapshot(specs, this._serverSpecs);
|
|
277
|
+
this._mergeSnapshot(sessions, this._serverSessions);
|
|
198
278
|
|
|
199
|
-
|
|
200
|
-
const data = await response.json();
|
|
201
|
-
sessions = data.sessions;
|
|
279
|
+
for (const registration of this._providers.values()) {
|
|
202
280
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
281
|
+
const data = await registration.provider.fetch();
|
|
282
|
+
if (!data) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (data.statusCode !== undefined) {
|
|
286
|
+
statusCode = data.statusCode;
|
|
287
|
+
}
|
|
288
|
+
this._mergeSnapshot(specs, data.specs);
|
|
289
|
+
this._mergeSnapshot(sessions, data.sessions);
|
|
290
|
+
if (data.transport) {
|
|
291
|
+
for (const [key, factory] of Object.entries(data.transport)) {
|
|
292
|
+
transports.set(key as TLanguageServerId, factory);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
205
295
|
} catch (err) {
|
|
206
296
|
console.warn(err);
|
|
207
297
|
}
|
|
208
|
-
} catch (err) {
|
|
209
|
-
console.warn(err);
|
|
210
|
-
this._ready.resolve(undefined);
|
|
211
|
-
return;
|
|
212
298
|
}
|
|
213
299
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
} else {
|
|
219
|
-
this._sessions.set(id, sessions[key]);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const oldKeys = this._sessions.keys();
|
|
224
|
-
|
|
225
|
-
for (const oldKey in oldKeys) {
|
|
226
|
-
if (!sessions[oldKey]) {
|
|
227
|
-
let oldId = oldKey as TLanguageServerId;
|
|
228
|
-
this._sessions.delete(oldId);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
300
|
+
this._statusCode = statusCode;
|
|
301
|
+
this._transports = transports;
|
|
302
|
+
this._syncSnapshot(this._specs, specs);
|
|
303
|
+
this._syncSnapshot(this._sessions, sessions);
|
|
231
304
|
this._sessionsChanged.emit(void 0);
|
|
232
305
|
this._ready.resolve(undefined);
|
|
233
306
|
}
|
|
@@ -280,6 +353,95 @@ export class LanguageServerManager implements ILanguageServerManager {
|
|
|
280
353
|
return bRank - aRank;
|
|
281
354
|
}
|
|
282
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Fetch language server data from the HTTP endpoint.
|
|
358
|
+
*/
|
|
359
|
+
private async _fetchServerData(): Promise<
|
|
360
|
+
| (ILanguageServerProvider.IFetchResult & {
|
|
361
|
+
version?: number;
|
|
362
|
+
retryScheduled?: boolean;
|
|
363
|
+
})
|
|
364
|
+
| null
|
|
365
|
+
> {
|
|
366
|
+
let response: Response;
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
response = await ServerConnection.makeRequest(
|
|
370
|
+
this.statusUrl,
|
|
371
|
+
{ method: 'GET' },
|
|
372
|
+
this._settings
|
|
373
|
+
);
|
|
374
|
+
} catch (err) {
|
|
375
|
+
console.warn(err);
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!response.ok) {
|
|
380
|
+
if (this._retries > 0) {
|
|
381
|
+
this._retries -= 1;
|
|
382
|
+
setTimeout(this.fetchSessions.bind(this), this._retriesInterval);
|
|
383
|
+
return { statusCode: response.status, retryScheduled: true };
|
|
384
|
+
} else {
|
|
385
|
+
console.log('Missing jupyter_lsp server extension, skipping.');
|
|
386
|
+
}
|
|
387
|
+
return { statusCode: response.status };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
const data = await response.json();
|
|
392
|
+
return {
|
|
393
|
+
statusCode: response.status,
|
|
394
|
+
version: data.version,
|
|
395
|
+
sessions: data.sessions,
|
|
396
|
+
specs: data.specs
|
|
397
|
+
};
|
|
398
|
+
} catch (err) {
|
|
399
|
+
console.warn(err);
|
|
400
|
+
return { statusCode: response.status };
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Merge provider snapshot into target map.
|
|
406
|
+
*/
|
|
407
|
+
private _mergeSnapshot<T>(
|
|
408
|
+
target: Map<TLanguageServerId, T>,
|
|
409
|
+
source?: Map<TLanguageServerId, T> | { [key: string]: T }
|
|
410
|
+
): void {
|
|
411
|
+
if (!source) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const entries =
|
|
415
|
+
source instanceof Map ? source.entries() : Object.entries(source);
|
|
416
|
+
|
|
417
|
+
for (const [key, value] of entries) {
|
|
418
|
+
target.set(key as TLanguageServerId, value);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Synchronize a mutable map in place.
|
|
424
|
+
*/
|
|
425
|
+
private _syncSnapshot<T>(
|
|
426
|
+
target: Map<TLanguageServerId, T>,
|
|
427
|
+
source: Map<TLanguageServerId, T> | { [key: string]: T }
|
|
428
|
+
): void {
|
|
429
|
+
const entries =
|
|
430
|
+
source instanceof Map ? source.entries() : Object.entries(source);
|
|
431
|
+
const nextKeys = new Set<TLanguageServerId>();
|
|
432
|
+
|
|
433
|
+
for (const [key, value] of entries) {
|
|
434
|
+
const id = key as TLanguageServerId;
|
|
435
|
+
nextKeys.add(id);
|
|
436
|
+
target.set(id, value);
|
|
437
|
+
}
|
|
438
|
+
for (const key of Array.from(target.keys())) {
|
|
439
|
+
if (!nextKeys.has(key)) {
|
|
440
|
+
target.delete(key);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
283
445
|
/**
|
|
284
446
|
* Get the base URL for language server requests.
|
|
285
447
|
*/
|
|
@@ -297,6 +459,16 @@ export class LanguageServerManager implements ILanguageServerManager {
|
|
|
297
459
|
*/
|
|
298
460
|
private _specs: TSpecsMap = new Map();
|
|
299
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Last known language server sessions from HTTP status endpoint.
|
|
464
|
+
*/
|
|
465
|
+
private _serverSessions: TSessionMap = new Map();
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Last known language server specs from HTTP status endpoint.
|
|
469
|
+
*/
|
|
470
|
+
private _serverSpecs: TSpecsMap = new Map();
|
|
471
|
+
|
|
300
472
|
/**
|
|
301
473
|
* Server connection setting.
|
|
302
474
|
*/
|
|
@@ -344,6 +516,19 @@ export class LanguageServerManager implements ILanguageServerManager {
|
|
|
344
516
|
this
|
|
345
517
|
);
|
|
346
518
|
|
|
519
|
+
/**
|
|
520
|
+
* Runtime language server providers.
|
|
521
|
+
*/
|
|
522
|
+
private _providers = new Map<string, IProviderRegistration>();
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Runtime language server transport factories.
|
|
526
|
+
*/
|
|
527
|
+
private _transports = new Map<
|
|
528
|
+
TLanguageServerId,
|
|
529
|
+
ILanguageServerProvider.TTransportFactory
|
|
530
|
+
>();
|
|
531
|
+
|
|
347
532
|
private _isDisposed = false;
|
|
348
533
|
|
|
349
534
|
/**
|
package/src/plugin.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
/**
|
|
12
12
|
* Enable or disable the language server services.
|
|
13
13
|
*/
|
|
14
|
-
export type Activate = Off | On;
|
|
14
|
+
export type Activate = (Off | On) & string;
|
|
15
15
|
export type Off = 'off';
|
|
16
16
|
export type On = 'on';
|
|
17
17
|
/**
|
|
@@ -21,7 +21,8 @@ export type RankOfTheServer = number;
|
|
|
21
21
|
/**
|
|
22
22
|
* Whether to ask server to send logs with execution trace (for debugging). Accepted values are: "off", "messages", "verbose". Servers are allowed to ignore this request.
|
|
23
23
|
*/
|
|
24
|
-
export type AskServersToSendTraceNotifications = Off1 | Messages | Verbose
|
|
24
|
+
export type AskServersToSendTraceNotifications = (Off1 | Messages | Verbose) &
|
|
25
|
+
string;
|
|
25
26
|
export type Off1 = 'off';
|
|
26
27
|
export type Messages = 'messages';
|
|
27
28
|
export type Verbose = 'verbose';
|