@jupyterlab/lsp
Version:
240 lines • 7.83 kB
JavaScript
/*
* Copyright (c) Jupyter Development Team.
* Distributed under the terms of the Modified BSD License.
*/
// Disclaimer/acknowledgement: Fragments are based on https://github.com/wylieconlon/lsp-editor-adapter,
// which is copyright of wylieconlon and contributors and ISC licenced.
// ISC licence is, quote, "functionally equivalent to the simplified BSD and MIT licenses,
// but without language deemed unnecessary following the Berne Convention." (Wikipedia).
// Introduced modifications are BSD licenced, copyright JupyterLab development team.
import { ConsoleLogger, listen } from 'vscode-ws-jsonrpc';
import { Signal } from '@lumino/signaling';
import { registerServerCapability, unregisterServerCapability } from './server-capability-registration';
export class LspWsConnection {
constructor(options) {
/**
* Map to track opened virtual documents..
*/
this.openedUris = new Map();
/**
* The connection is connected?
*/
this._isConnected = false;
/**
* The connection is initialized?
*/
this._isInitialized = false;
/**
* Array of LSP callback disposables, it is used to
* clear the callbacks when the connection is disposed.
*/
this._disposables = [];
this._disposed = new Signal(this);
this._isDisposed = false;
this._rootUri = options.rootUri;
}
/**
* Is the language server is connected?
*/
get isConnected() {
return this._isConnected;
}
/**
* Is the language server is initialized?
*/
get isInitialized() {
return this._isInitialized;
}
/**
* Is the language server is connected and initialized?
*/
get isReady() {
return this._isConnected && this._isInitialized;
}
/**
* A signal emitted when the connection is disposed.
*/
get disposed() {
return this._disposed;
}
/**
* Check if the connection is disposed
*/
get isDisposed() {
return this._isDisposed;
}
/**
* Initialize a connection over a web socket that speaks the LSP protocol
*/
connect(socket) {
this.socket = socket;
listen({
webSocket: this.socket,
logger: new ConsoleLogger(),
onConnection: (connection) => {
connection.listen();
this._isConnected = true;
this.connection = connection;
this.sendInitialize();
const registerCapabilityDisposable = this.connection.onRequest('client/registerCapability', (params) => {
params.registrations.forEach((capabilityRegistration) => {
try {
this.serverCapabilities = registerServerCapability(this.serverCapabilities, capabilityRegistration);
}
catch (err) {
console.error(err);
}
});
});
this._disposables.push(registerCapabilityDisposable);
const unregisterCapabilityDisposable = this.connection.onRequest('client/unregisterCapability', (params) => {
params.unregisterations.forEach((capabilityUnregistration) => {
this.serverCapabilities = unregisterServerCapability(this.serverCapabilities, capabilityUnregistration);
});
});
this._disposables.push(unregisterCapabilityDisposable);
const disposable = this.connection.onClose(() => {
this._isConnected = false;
});
this._disposables.push(disposable);
}
});
}
/**
* Close the connection
*/
close() {
if (this.connection) {
this.connection.dispose();
}
this.openedUris.clear();
this.socket.close();
}
/**
* The initialize request telling the server which options the client supports
*/
sendInitialize() {
if (!this._isConnected) {
return;
}
this.openedUris.clear();
const message = this.initializeParams();
this.connection
.sendRequest('initialize', message)
.then(params => {
this.onServerInitialized(params);
}, e => {
console.warn('LSP websocket connection initialization failure', e);
});
}
/**
* Inform the server that the document was opened
*/
sendOpen(documentInfo) {
const textDocumentMessage = {
textDocument: {
uri: documentInfo.uri,
languageId: documentInfo.languageId,
text: documentInfo.text,
version: documentInfo.version
}
};
this.connection
.sendNotification('textDocument/didOpen', textDocumentMessage)
.catch(console.error);
this.openedUris.set(documentInfo.uri, true);
this.sendChange(documentInfo);
}
/**
* Sends the full text of the document to the server
*/
sendChange(documentInfo) {
if (!this.isReady) {
return;
}
if (!this.openedUris.get(documentInfo.uri)) {
this.sendOpen(documentInfo);
return;
}
const textDocumentChange = {
textDocument: {
uri: documentInfo.uri,
version: documentInfo.version
},
contentChanges: [{ text: documentInfo.text }]
};
this.connection
.sendNotification('textDocument/didChange', textDocumentChange)
.catch(console.error);
documentInfo.version++;
}
/**
* Send save notification to the server.
*/
sendSaved(documentInfo) {
if (!this.isReady) {
return;
}
const textDocumentChange = {
textDocument: {
uri: documentInfo.uri,
version: documentInfo.version
},
text: documentInfo.text
};
this.connection
.sendNotification('textDocument/didSave', textDocumentChange)
.catch(console.error);
}
/**
* Send configuration change to the server.
*/
sendConfigurationChange(settings) {
if (!this.isReady) {
return;
}
this.connection
.sendNotification('workspace/didChangeConfiguration', settings)
.catch(console.error);
}
/**
* Dispose the connection.
*/
dispose() {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
this._disposables.forEach(disposable => {
disposable.dispose();
});
this._disposed.emit();
Signal.clearData(this);
}
/**
* Callback called when the server is initialized.
*/
onServerInitialized(params) {
this._isInitialized = true;
this.serverCapabilities = params.capabilities;
this.connection.sendNotification('initialized', {}).catch(console.error);
this.connection
.sendNotification('workspace/didChangeConfiguration', {
settings: {}
})
.catch(console.error);
}
/**
* Initialization parameters to be sent to the language server.
* Subclasses should override this when adding more features.
*/
initializeParams() {
return {
capabilities: {},
processId: null,
rootUri: this._rootUri,
workspaceFolders: null
};
}
}
//# sourceMappingURL=ws-connection.js.map