UNPKG

@jupyterlab/lsp

Version:
376 lines 13 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { Signal } from '@lumino/signaling'; import { Method } from './tokens'; import { untilReady } from './utils'; import { registerServerCapability, unregisterServerCapability } from './ws-connection/server-capability-registration'; import { LspWsConnection } from './ws-connection/ws-connection'; /** * Helper class to handle client request */ class ClientRequestHandler { constructor(connection, method, emitter) { this.connection = connection; this.method = method; this.emitter = emitter; } request(params) { // TODO check if is ready? this.emitter.log(MessageKind.clientRequested, { method: this.method, message: params }); return this.connection .sendRequest(this.method, params) .then((result) => { this.emitter.log(MessageKind.resultForClient, { method: this.method, message: params }); return result; }); } } /** * Helper class to handle server responses */ class ServerRequestHandler { constructor(connection, method, emitter) { this.connection = connection; this.method = method; this.emitter = emitter; // on request accepts "thenable" this.connection.onRequest(method, this._handle.bind(this)); this._handler = null; } setHandler(handler) { this._handler = handler; } clearHandler() { this._handler = null; } _handle(request) { this.emitter.log(MessageKind.serverRequested, { method: this.method, message: request }); if (!this._handler) { return new Promise(() => undefined); } return this._handler(request, this.emitter).then(result => { this.emitter.log(MessageKind.responseForServer, { method: this.method, message: result }); return result; }); } } export const Provider = { TEXT_DOCUMENT_SYNC: 'textDocumentSync', COMPLETION: 'completionProvider', HOVER: 'hoverProvider', SIGNATURE_HELP: 'signatureHelpProvider', DECLARATION: 'declarationProvider', DEFINITION: 'definitionProvider', TYPE_DEFINITION: 'typeDefinitionProvider', IMPLEMENTATION: 'implementationProvider', REFERENCES: 'referencesProvider', DOCUMENT_HIGHLIGHT: 'documentHighlightProvider', DOCUMENT_SYMBOL: 'documentSymbolProvider', CODE_ACTION: 'codeActionProvider', CODE_LENS: 'codeLensProvider', DOCUMENT_LINK: 'documentLinkProvider', COLOR: 'colorProvider', DOCUMENT_FORMATTING: 'documentFormattingProvider', DOCUMENT_RANGE_FORMATTING: 'documentRangeFormattingProvider', DOCUMENT_ON_TYPE_FORMATTING: 'documentOnTypeFormattingProvider', RENAME: 'renameProvider', FOLDING_RANGE: 'foldingRangeProvider', EXECUTE_COMMAND: 'executeCommandProvider', SELECTION_RANGE: 'selectionRangeProvider', WORKSPACE_SYMBOL: 'workspaceSymbolProvider', WORKSPACE: 'workspace' }; /** * Create a map between the request method and its handler */ function createMethodMap(methods, handlerFactory) { const result = {}; for (let method of Object.values(methods)) { result[method] = handlerFactory(method); } return result; } var MessageKind; (function (MessageKind) { MessageKind[MessageKind["clientNotifiedServer"] = 0] = "clientNotifiedServer"; MessageKind[MessageKind["serverNotifiedClient"] = 1] = "serverNotifiedClient"; MessageKind[MessageKind["serverRequested"] = 2] = "serverRequested"; MessageKind[MessageKind["clientRequested"] = 3] = "clientRequested"; MessageKind[MessageKind["resultForClient"] = 4] = "resultForClient"; MessageKind[MessageKind["responseForServer"] = 5] = "responseForServer"; })(MessageKind || (MessageKind = {})); export class LSPConnection extends LspWsConnection { constructor(options) { super(options); /** * Is the connection is closed manually? */ this._closingManually = false; this._closeSignal = new Signal(this); this._errorSignal = new Signal(this); this._serverInitialized = new Signal(this); this._options = options; this.logAllCommunication = false; this.serverIdentifier = options.serverIdentifier; this.serverLanguage = options.languageId; this.documentsToOpen = []; this.clientNotifications = this.constructNotificationHandlers(Method.ClientNotification); this.serverNotifications = this.constructNotificationHandlers(Method.ServerNotification); } /** * Signal emitted when the connection is closed. */ get closeSignal() { return this._closeSignal; } /** * Signal emitted when the connection receives an error * message. */ get errorSignal() { return this._errorSignal; } /** * Signal emitted when the connection is initialized. */ get serverInitialized() { return this._serverInitialized; } /** * Dispose the connection. */ dispose() { if (this.isDisposed) { return; } if (this.serverRequests) { // `serverRequests` may be undefined if dispose is called during initialization sequence Object.values(this.serverRequests).forEach(request => request.clearHandler()); } this.close(); super.dispose(); } /** * Helper to print the logs to logger, for now we are using * directly the browser's console. */ log(kind, message) { if (this.logAllCommunication) { console.log(kind, message); } } /** * Send the open request to the backend when the server is * ready. */ sendOpenWhenReady(documentInfo) { if (this.isReady) { this.sendOpen(documentInfo); } else { this.documentsToOpen.push(documentInfo); } } /** * Send the document changes to the server. */ sendSelectiveChange(changeEvent, documentInfo) { this._sendChange([changeEvent], documentInfo); } /** * Send all changes to the server. */ sendFullTextChange(text, documentInfo) { this._sendChange([{ text }], documentInfo); } /** * Check if a capability is available in the server capabilities. */ provides(capability) { return !!(this.serverCapabilities && this.serverCapabilities[capability]); } /** * Close the connection to the server. */ close() { try { this._closingManually = true; super.close(); } catch (e) { this._closingManually = false; } } /** * Initialize a connection over a web socket that speaks the LSP. */ connect(socket) { super.connect(socket); untilReady(() => { return this.isConnected; }, -1) .then(() => { const disposable = this.connection.onClose(() => { this._isConnected = false; this._closeSignal.emit(this._closingManually); }); this._disposables.push(disposable); }) .catch(() => { console.error('Could not connect onClose signal'); }); } /** * Get send request to the server to get completion results * from a completion item */ async getCompletionResolve(completionItem) { if (!this.isReady) { return; } return this.connection.sendRequest('completionItem/resolve', completionItem); } /** * Generate the notification handlers */ constructNotificationHandlers(methods) { const factory = () => new Signal(this); return createMethodMap(methods, factory); } /** * Generate the client request handler */ constructClientRequestHandler(methods) { return createMethodMap(methods, method => new ClientRequestHandler(this.connection, method, this)); } /** * Generate the server response handler */ constructServerRequestHandler(methods) { return createMethodMap(methods, method => new ServerRequestHandler(this.connection, method, this)); } /** * Initialization parameters to be sent to the language server. * Subclasses can overload this when adding more features. */ initializeParams() { return { ...super.initializeParams(), capabilities: this._options.capabilities, initializationOptions: null, processId: null, workspaceFolders: null }; } /** * Callback called when the server is initialized. */ onServerInitialized(params) { this.afterInitialized(); super.onServerInitialized(params); while (this.documentsToOpen.length) { this.sendOpen(this.documentsToOpen.pop()); } this._serverInitialized.emit(this.serverCapabilities); } /** * Once the server is initialized, this method generates the * client and server handlers */ afterInitialized() { const disposable = this.connection.onError(e => this._errorSignal.emit(e)); this._disposables.push(disposable); for (const method of Object.values(Method.ServerNotification)) { const signal = this.serverNotifications[method]; const disposable = this.connection.onNotification(method, params => { this.log(MessageKind.serverNotifiedClient, { method, message: params }); signal.emit(params); }); this._disposables.push(disposable); } for (const method of Object.values(Method.ClientNotification)) { const signal = this.clientNotifications[method]; signal.connect((emitter, params) => { this.log(MessageKind.clientNotifiedServer, { method, message: params }); this.connection.sendNotification(method, params).catch(console.error); }); } this.clientRequests = this.constructClientRequestHandler(Method.ClientRequest); this.serverRequests = this.constructServerRequestHandler(Method.ServerRequest); this.serverRequests['client/registerCapability'].setHandler(async (params) => { params.registrations.forEach((capabilityRegistration) => { try { const updatedCapabilities = registerServerCapability(this.serverCapabilities, capabilityRegistration); if (updatedCapabilities === null) { console.error(`Failed to register server capability: ${capabilityRegistration}`); return; } this.serverCapabilities = updatedCapabilities; } catch (err) { console.error(err); } }); }); this.serverRequests['client/unregisterCapability'].setHandler(async (params) => { params.unregisterations.forEach((capabilityUnregistration) => { this.serverCapabilities = unregisterServerCapability(this.serverCapabilities, capabilityUnregistration); }); }); this.serverRequests['workspace/configuration'].setHandler(async (params) => { return params.items.map(item => { // LSP: "If the client can’t provide a configuration setting for a given scope // then `null` needs to be present in the returned array." // for now we do not support configuration, but yaml server does not respect // client capability so we have a handler just for that return null; }); }); } /** * Send the document changed data to the server. */ _sendChange(changeEvents, documentInfo) { if (!this.isReady) { return; } if (documentInfo.uri.length === 0) { return; } if (!this.openedUris.get(documentInfo.uri)) { this.sendOpen(documentInfo); } const textDocumentChange = { textDocument: { uri: documentInfo.uri, version: documentInfo.version }, contentChanges: changeEvents }; this.connection .sendNotification('textDocument/didChange', textDocumentChange) .catch(console.error); documentInfo.version++; } } //# sourceMappingURL=connection.js.map