UNPKG

langium

Version:

A language engineering tool for the Language Server Protocol

529 lines 28.7 kB
/****************************************************************************** * Copyright 2021 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ import { DidChangeConfigurationNotification, Emitter, LSPErrorCodes, ResponseError, TextDocumentSyncKind } from 'vscode-languageserver-protocol'; import { eagerLoad } from '../dependency-injection.js'; import { isOperationCancelled } from '../utils/promise-utils.js'; import { URI } from '../utils/uri-utils.js'; import { DocumentState } from '../workspace/documents.js'; import { mergeCompletionProviderOptions } from './completion/completion-provider.js'; import { mergeSemanticTokenProviderOptions } from './semantic-token-provider.js'; import { mergeSignatureHelpOptions } from './signature-help-provider.js'; export class DefaultLanguageServer { constructor(services) { this.onInitializeEmitter = new Emitter(); this.onInitializedEmitter = new Emitter(); this.services = services; } get onInitialize() { return this.onInitializeEmitter.event; } get onInitialized() { return this.onInitializedEmitter.event; } async initialize(params) { this.eagerLoadServices(); this.fireInitializeOnDefaultServices(params); this.onInitializeEmitter.fire(params); this.onInitializeEmitter.dispose(); return this.buildInitializeResult(params); } /** * Eagerly loads all services before emitting the `onInitialize` event. * Ensures that all services are able to catch the event. */ eagerLoadServices() { eagerLoad(this.services); this.services.ServiceRegistry.all.forEach(language => eagerLoad(language)); } hasService(callback) { const allServices = this.services.ServiceRegistry.all; return allServices.some(services => callback(services) !== undefined); } buildInitializeResult(_params) { const documentUpdateHandler = this.services.lsp.DocumentUpdateHandler; const fileOperationOptions = this.services.lsp.FileOperationHandler?.fileOperationOptions; const allServices = this.services.ServiceRegistry.all; const hasFormattingService = this.hasService(e => e.lsp?.Formatter); const formattingOnTypeOptions = allServices.map(e => e.lsp?.Formatter?.formatOnTypeOptions).find(e => Boolean(e)); const hasCodeActionProvider = this.hasService(e => e.lsp?.CodeActionProvider); const hasSemanticTokensProvider = this.hasService(e => e.lsp?.SemanticTokenProvider); const semanticTokensOptions = mergeSemanticTokenProviderOptions(allServices.map(e => e.lsp?.SemanticTokenProvider?.semanticTokensOptions)); const commandNames = this.services.lsp?.ExecuteCommandHandler?.commands; const hasDocumentLinkProvider = this.hasService(e => e.lsp?.DocumentLinkProvider); const signatureHelpOptions = mergeSignatureHelpOptions(allServices.map(e => e.lsp?.SignatureHelp?.signatureHelpOptions)); const hasGoToTypeProvider = this.hasService(e => e.lsp?.TypeProvider); const hasGoToImplementationProvider = this.hasService(e => e.lsp?.ImplementationProvider); const hasCompletionProvider = this.hasService(e => e.lsp?.CompletionProvider); const completionOptions = mergeCompletionProviderOptions(allServices.map(e => e.lsp?.CompletionProvider?.completionOptions)); const hasReferencesProvider = this.hasService(e => e.lsp?.ReferencesProvider); const hasDocumentSymbolProvider = this.hasService(e => e.lsp?.DocumentSymbolProvider); const hasDefinitionProvider = this.hasService(e => e.lsp?.DefinitionProvider); const hasDocumentHighlightProvider = this.hasService(e => e.lsp?.DocumentHighlightProvider); const hasFoldingRangeProvider = this.hasService(e => e.lsp?.FoldingRangeProvider); const hasHoverProvider = this.hasService(e => e.lsp?.HoverProvider); const hasRenameProvider = this.hasService(e => e.lsp?.RenameProvider); const hasCallHierarchyProvider = this.hasService(e => e.lsp?.CallHierarchyProvider); const hasTypeHierarchyProvider = this.hasService((e) => e.lsp?.TypeHierarchyProvider); const hasCodeLensProvider = this.hasService(e => e.lsp?.CodeLensProvider); const hasDeclarationProvider = this.hasService(e => e.lsp?.DeclarationProvider); const hasInlayHintProvider = this.hasService(e => e.lsp?.InlayHintProvider); const workspaceSymbolProvider = this.services.lsp?.WorkspaceSymbolProvider; const result = { capabilities: { workspace: { workspaceFolders: { supported: true }, fileOperations: fileOperationOptions }, executeCommandProvider: commandNames && { commands: commandNames }, textDocumentSync: { change: TextDocumentSyncKind.Incremental, openClose: true, save: Boolean(documentUpdateHandler.didSaveDocument), willSave: Boolean(documentUpdateHandler.willSaveDocument), willSaveWaitUntil: Boolean(documentUpdateHandler.willSaveDocumentWaitUntil) }, completionProvider: hasCompletionProvider ? completionOptions : undefined, referencesProvider: hasReferencesProvider, documentSymbolProvider: hasDocumentSymbolProvider, definitionProvider: hasDefinitionProvider, typeDefinitionProvider: hasGoToTypeProvider, documentHighlightProvider: hasDocumentHighlightProvider, codeActionProvider: hasCodeActionProvider, documentFormattingProvider: hasFormattingService, documentRangeFormattingProvider: hasFormattingService, documentOnTypeFormattingProvider: formattingOnTypeOptions, foldingRangeProvider: hasFoldingRangeProvider, hoverProvider: hasHoverProvider, renameProvider: hasRenameProvider ? { prepareProvider: true } : undefined, semanticTokensProvider: hasSemanticTokensProvider ? semanticTokensOptions : undefined, signatureHelpProvider: signatureHelpOptions, implementationProvider: hasGoToImplementationProvider, callHierarchyProvider: hasCallHierarchyProvider ? {} : undefined, typeHierarchyProvider: hasTypeHierarchyProvider ? {} : undefined, documentLinkProvider: hasDocumentLinkProvider ? { resolveProvider: false } : undefined, codeLensProvider: hasCodeLensProvider ? { resolveProvider: false } : undefined, declarationProvider: hasDeclarationProvider, inlayHintProvider: hasInlayHintProvider ? { resolveProvider: false } : undefined, workspaceSymbolProvider: workspaceSymbolProvider ? { resolveProvider: Boolean(workspaceSymbolProvider.resolveSymbol) } : undefined } }; return result; } initialized(params) { this.fireInitializedOnDefaultServices(params); this.onInitializedEmitter.fire(params); this.onInitializedEmitter.dispose(); } fireInitializeOnDefaultServices(params) { this.services.workspace.ConfigurationProvider.initialize(params); this.services.workspace.WorkspaceManager.initialize(params); } fireInitializedOnDefaultServices(params) { const connection = this.services.lsp.Connection; const configurationParams = connection ? { ...params, register: params => connection.client.register(DidChangeConfigurationNotification.type, params), fetchConfiguration: params => connection.workspace.getConfiguration(params) } : params; // do not await the promises of the following calls, as they must not block the initialization process! // otherwise, there is the danger of out-of-order processing of subsequent incoming messages from the language client // however, awaiting should be possible in general, e.g. in unit test scenarios this.services.workspace.ConfigurationProvider.initialized(configurationParams) .catch(err => console.error('Error in ConfigurationProvider initialization:', err)); this.services.workspace.WorkspaceManager.initialized(params) .catch(err => console.error('Error in WorkspaceManager initialization:', err)); } } export function startLanguageServer(services) { const connection = services.lsp.Connection; if (!connection) { throw new Error('Starting a language server requires the languageServer.Connection service to be set.'); } addDocumentUpdateHandler(connection, services); addFileOperationHandler(connection, services); addDiagnosticsHandler(connection, services); addCompletionHandler(connection, services); addFindReferencesHandler(connection, services); addDocumentSymbolHandler(connection, services); addGotoDefinitionHandler(connection, services); addGoToTypeDefinitionHandler(connection, services); addGoToImplementationHandler(connection, services); addDocumentHighlightsHandler(connection, services); addFoldingRangeHandler(connection, services); addFormattingHandler(connection, services); addCodeActionHandler(connection, services); addRenameHandler(connection, services); addHoverHandler(connection, services); addInlayHintHandler(connection, services); addSemanticTokenHandler(connection, services); addExecuteCommandHandler(connection, services); addSignatureHelpHandler(connection, services); addCallHierarchyHandler(connection, services); addTypeHierarchyHandler(connection, services); addCodeLensHandler(connection, services); addDocumentLinkHandler(connection, services); addConfigurationChangeHandler(connection, services); addGoToDeclarationHandler(connection, services); addWorkspaceSymbolHandler(connection, services); connection.onInitialize(params => { return services.lsp.LanguageServer.initialize(params); }); connection.onInitialized(params => { services.lsp.LanguageServer.initialized(params); }); // Make the text document manager listen on the connection for open, change and close text document events. const documents = services.workspace.TextDocuments; documents.listen(connection); // Start listening for incoming messages from the client. connection.listen(); } /** * Adds a handler for document updates when content changes, or watch catches a change. * In the case there is no handler service registered, this function does nothing. */ export function addDocumentUpdateHandler(connection, services) { const handler = services.lsp.DocumentUpdateHandler; const documents = services.workspace.TextDocuments; if (handler.didOpenDocument) { documents.onDidOpen(change => handler.didOpenDocument(change)); } if (handler.didChangeContent) { documents.onDidChangeContent(change => handler.didChangeContent(change)); } if (handler.didCloseDocument) { documents.onDidClose(change => handler.didCloseDocument(change)); } if (handler.didSaveDocument) { documents.onDidSave(change => handler.didSaveDocument(change)); } if (handler.willSaveDocument) { documents.onWillSave(event => handler.willSaveDocument(event)); } if (handler.willSaveDocumentWaitUntil) { documents.onWillSaveWaitUntil(event => handler.willSaveDocumentWaitUntil(event)); } if (handler.didChangeWatchedFiles) { connection.onDidChangeWatchedFiles(params => handler.didChangeWatchedFiles(params)); } } export function addFileOperationHandler(connection, services) { const handler = services.lsp.FileOperationHandler; if (!handler) { return; } if (handler.didCreateFiles) { connection.workspace.onDidCreateFiles(params => handler.didCreateFiles(params)); } if (handler.didRenameFiles) { connection.workspace.onDidRenameFiles(params => handler.didRenameFiles(params)); } if (handler.didDeleteFiles) { connection.workspace.onDidDeleteFiles(params => handler.didDeleteFiles(params)); } if (handler.willCreateFiles) { connection.workspace.onWillCreateFiles(params => handler.willCreateFiles(params)); } if (handler.willRenameFiles) { connection.workspace.onWillRenameFiles(params => handler.willRenameFiles(params)); } if (handler.willDeleteFiles) { connection.workspace.onWillDeleteFiles(params => handler.willDeleteFiles(params)); } } export function addDiagnosticsHandler(connection, services) { const documentBuilder = services.workspace.DocumentBuilder; documentBuilder.onUpdate(async (_, deleted) => { for (const uri of deleted) { connection.sendDiagnostics({ uri: uri.toString(), diagnostics: [] }); } }); documentBuilder.onDocumentPhase(DocumentState.Validated, async (document) => { if (document.diagnostics) { connection.sendDiagnostics({ uri: document.uri.toString(), diagnostics: document.diagnostics }); } }); } export function addCompletionHandler(connection, services) { connection.onCompletion(createRequestHandler((services, document, params, cancelToken) => { return services.lsp?.CompletionProvider?.getCompletion(document, params, cancelToken); }, services, DocumentState.IndexedReferences)); } export function addFindReferencesHandler(connection, services) { connection.onReferences(createRequestHandler((services, document, params, cancelToken) => services.lsp?.ReferencesProvider?.findReferences(document, params, cancelToken), services, DocumentState.IndexedReferences)); } export function addCodeActionHandler(connection, services) { connection.onCodeAction(createRequestHandler((services, document, params, cancelToken) => services.lsp?.CodeActionProvider?.getCodeActions(document, params, cancelToken), services, DocumentState.Validated)); } export function addDocumentSymbolHandler(connection, services) { connection.onDocumentSymbol(createRequestHandler((services, document, params, cancelToken) => services.lsp?.DocumentSymbolProvider?.getSymbols(document, params, cancelToken), services, DocumentState.Parsed)); } export function addGotoDefinitionHandler(connection, services) { connection.onDefinition(createRequestHandler((services, document, params, cancelToken) => services.lsp?.DefinitionProvider?.getDefinition(document, params, cancelToken), services, DocumentState.IndexedReferences)); } export function addGoToTypeDefinitionHandler(connection, services) { connection.onTypeDefinition(createRequestHandler((services, document, params, cancelToken) => services.lsp?.TypeProvider?.getTypeDefinition(document, params, cancelToken), services, DocumentState.IndexedReferences)); } export function addGoToImplementationHandler(connection, services) { connection.onImplementation(createRequestHandler((services, document, params, cancelToken) => services.lsp?.ImplementationProvider?.getImplementation(document, params, cancelToken), services, DocumentState.IndexedReferences)); } export function addGoToDeclarationHandler(connection, services) { connection.onDeclaration(createRequestHandler((services, document, params, cancelToken) => services.lsp?.DeclarationProvider?.getDeclaration(document, params, cancelToken), services, DocumentState.IndexedReferences)); } export function addDocumentHighlightsHandler(connection, services) { connection.onDocumentHighlight(createRequestHandler((services, document, params, cancelToken) => services.lsp?.DocumentHighlightProvider?.getDocumentHighlight(document, params, cancelToken), services, DocumentState.IndexedReferences)); } export function addHoverHandler(connection, services) { connection.onHover(createRequestHandler((services, document, params, cancelToken) => services.lsp?.HoverProvider?.getHoverContent(document, params, cancelToken), services, DocumentState.IndexedReferences)); } export function addFoldingRangeHandler(connection, services) { connection.onFoldingRanges(createRequestHandler((services, document, params, cancelToken) => services.lsp?.FoldingRangeProvider?.getFoldingRanges(document, params, cancelToken), services, DocumentState.Parsed)); } export function addFormattingHandler(connection, services) { connection.onDocumentFormatting(createRequestHandler((services, document, params, cancelToken) => services.lsp?.Formatter?.formatDocument(document, params, cancelToken), services, DocumentState.Parsed)); connection.onDocumentRangeFormatting(createRequestHandler((services, document, params, cancelToken) => services.lsp?.Formatter?.formatDocumentRange(document, params, cancelToken), services, DocumentState.Parsed)); connection.onDocumentOnTypeFormatting(createRequestHandler((services, document, params, cancelToken) => services.lsp?.Formatter?.formatDocumentOnType(document, params, cancelToken), services, DocumentState.Parsed)); } export function addRenameHandler(connection, services) { connection.onRenameRequest(createRequestHandler((services, document, params, cancelToken) => services.lsp?.RenameProvider?.rename(document, params, cancelToken), services, DocumentState.IndexedReferences)); connection.onPrepareRename(createRequestHandler((services, document, params, cancelToken) => services.lsp?.RenameProvider?.prepareRename(document, params, cancelToken), services, DocumentState.IndexedReferences)); } export function addInlayHintHandler(connection, services) { connection.languages.inlayHint.on(createServerRequestHandler((services, document, params, cancelToken) => services.lsp?.InlayHintProvider?.getInlayHints(document, params, cancelToken), services, DocumentState.IndexedReferences)); } export function addSemanticTokenHandler(connection, services) { // If no semantic token provider is registered that's fine. Just return an empty result const emptyResult = { data: [] }; connection.languages.semanticTokens.on(createServerRequestHandler((services, document, params, cancelToken) => { if (services.lsp?.SemanticTokenProvider) { return services.lsp.SemanticTokenProvider.semanticHighlight(document, params, cancelToken); } return emptyResult; }, services, DocumentState.IndexedReferences)); connection.languages.semanticTokens.onDelta(createServerRequestHandler((services, document, params, cancelToken) => { if (services.lsp?.SemanticTokenProvider) { return services.lsp.SemanticTokenProvider.semanticHighlightDelta(document, params, cancelToken); } return emptyResult; }, services, DocumentState.IndexedReferences)); connection.languages.semanticTokens.onRange(createServerRequestHandler((services, document, params, cancelToken) => { if (services.lsp?.SemanticTokenProvider) { return services.lsp.SemanticTokenProvider.semanticHighlightRange(document, params, cancelToken); } return emptyResult; }, services, DocumentState.IndexedReferences)); } export function addConfigurationChangeHandler(connection, services) { connection.onDidChangeConfiguration(change => { services.workspace.ConfigurationProvider.updateConfiguration(change); }); } export function addExecuteCommandHandler(connection, services) { const commandHandler = services.lsp.ExecuteCommandHandler; if (commandHandler) { connection.onExecuteCommand(async (params, token) => { try { return await commandHandler.executeCommand(params.command, params.arguments ?? [], token); } catch (err) { return responseError(err); } }); } } export function addDocumentLinkHandler(connection, services) { connection.onDocumentLinks(createServerRequestHandler((services, document, params, cancelToken) => services.lsp?.DocumentLinkProvider?.getDocumentLinks(document, params, cancelToken), services, DocumentState.Parsed)); } export function addSignatureHelpHandler(connection, services) { connection.onSignatureHelp(createServerRequestHandler((services, document, params, cancelToken) => services.lsp?.SignatureHelp?.provideSignatureHelp(document, params, cancelToken), services, DocumentState.IndexedReferences)); } export function addCodeLensHandler(connection, services) { connection.onCodeLens(createServerRequestHandler((services, document, params, cancelToken) => services.lsp?.CodeLensProvider?.provideCodeLens(document, params, cancelToken), services, DocumentState.IndexedReferences)); } export function addWorkspaceSymbolHandler(connection, services) { const workspaceSymbolProvider = services.lsp.WorkspaceSymbolProvider; if (workspaceSymbolProvider) { const documentBuilder = services.workspace.DocumentBuilder; connection.onWorkspaceSymbol(async (params, token) => { try { await documentBuilder.waitUntil(DocumentState.IndexedContent, token); return await workspaceSymbolProvider.getSymbols(params, token); } catch (err) { return responseError(err); } }); const resolveWorkspaceSymbol = workspaceSymbolProvider.resolveSymbol?.bind(workspaceSymbolProvider); if (resolveWorkspaceSymbol) { connection.onWorkspaceSymbolResolve(async (workspaceSymbol, token) => { try { await documentBuilder.waitUntil(DocumentState.IndexedContent, token); return await resolveWorkspaceSymbol(workspaceSymbol, token); } catch (err) { return responseError(err); } }); } } } export function addCallHierarchyHandler(connection, services) { connection.languages.callHierarchy.onPrepare(createServerRequestHandler(async (services, document, params, cancelToken) => { if (services.lsp?.CallHierarchyProvider) { const result = await services.lsp.CallHierarchyProvider.prepareCallHierarchy(document, params, cancelToken); return result ?? null; } return null; }, services, DocumentState.IndexedReferences)); connection.languages.callHierarchy.onIncomingCalls(createHierarchyRequestHandler(async (services, params, cancelToken) => { if (services.lsp?.CallHierarchyProvider) { const result = await services.lsp.CallHierarchyProvider.incomingCalls(params, cancelToken); return result ?? null; } return null; }, services)); connection.languages.callHierarchy.onOutgoingCalls(createHierarchyRequestHandler(async (services, params, cancelToken) => { if (services.lsp?.CallHierarchyProvider) { const result = await services.lsp.CallHierarchyProvider.outgoingCalls(params, cancelToken); return result ?? null; } return null; }, services)); } export function addTypeHierarchyHandler(connection, sharedServices) { // Don't register type hierarchy handlers if no type hierarchy provider is registered if (!sharedServices.ServiceRegistry.all.some((services) => services.lsp?.TypeHierarchyProvider)) { return; } connection.languages.typeHierarchy.onPrepare(createServerRequestHandler(async (services, document, params, cancelToken) => { const result = await services.lsp?.TypeHierarchyProvider?.prepareTypeHierarchy(document, params, cancelToken); return result ?? null; }, sharedServices, DocumentState.IndexedReferences)); connection.languages.typeHierarchy.onSupertypes(createHierarchyRequestHandler(async (services, params, cancelToken) => { const result = await services.lsp?.TypeHierarchyProvider?.supertypes(params, cancelToken); return result ?? null; }, sharedServices)); connection.languages.typeHierarchy.onSubtypes(createHierarchyRequestHandler(async (services, params, cancelToken) => { const result = await services.lsp?.TypeHierarchyProvider?.subtypes(params, cancelToken); return result ?? null; }, sharedServices)); } export function createHierarchyRequestHandler(serviceCall, sharedServices) { const serviceRegistry = sharedServices.ServiceRegistry; return async (params, cancelToken) => { const uri = URI.parse(params.item.uri); const cancellationError = await waitUntilPhase(sharedServices, cancelToken, uri, DocumentState.IndexedReferences); if (cancellationError) { return cancellationError; } if (!serviceRegistry.hasServices(uri)) { const errorText = `Could not find service instance for uri: '${uri}'`; console.debug(errorText); return responseError(new Error(errorText)); } const language = serviceRegistry.getServices(uri); try { return await serviceCall(language, params, cancelToken); } catch (err) { return responseError(err); } }; } export function createServerRequestHandler(serviceCall, sharedServices, targetState) { const documents = sharedServices.workspace.LangiumDocuments; const serviceRegistry = sharedServices.ServiceRegistry; return async (params, cancelToken) => { const uri = URI.parse(params.textDocument.uri); const cancellationError = await waitUntilPhase(sharedServices, cancelToken, uri, targetState); if (cancellationError) { return cancellationError; } if (!serviceRegistry.hasServices(uri)) { const errorText = `Could not find service instance for uri: '${uri}'`; console.debug(errorText); return responseError(new Error(errorText)); } const language = serviceRegistry.getServices(uri); try { const document = await documents.getOrCreateDocument(uri); return await serviceCall(language, document, params, cancelToken); } catch (err) { return responseError(err); } }; } export function createRequestHandler(serviceCall, sharedServices, targetState) { const documents = sharedServices.workspace.LangiumDocuments; const serviceRegistry = sharedServices.ServiceRegistry; return async (params, cancelToken) => { const uri = URI.parse(params.textDocument.uri); const cancellationError = await waitUntilPhase(sharedServices, cancelToken, uri, targetState); if (cancellationError) { return cancellationError; } if (!serviceRegistry.hasServices(uri)) { console.debug(`Could not find service instance for uri: '${uri.toString()}'`); return null; } const language = serviceRegistry.getServices(uri); try { const document = await documents.getOrCreateDocument(uri); return await serviceCall(language, document, params, cancelToken); } catch (err) { return responseError(err); } }; } async function waitUntilPhase(services, cancelToken, uri, targetState) { if (targetState !== undefined) { const documentBuilder = services.workspace.DocumentBuilder; try { await documentBuilder.waitUntil(targetState, uri, cancelToken); } catch (err) { return responseError(err); } } return undefined; } function responseError(err) { if (isOperationCancelled(err)) { return new ResponseError(LSPErrorCodes.RequestCancelled, 'The request has been cancelled.'); } if (err instanceof ResponseError) { return err; } throw err; } //# sourceMappingURL=language-server.js.map