/******************************************************************************
 * 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 { AbstractCancellationTokenSource , CancellationToken , Connection , FileChangeType , HandlerResult , InitializeResult , LSPErrorCodes , RequestHandler , ResponseError , ServerRequestHandler , TextDocumentIdentifier , TextDocuments , TextDocumentSyncKind } from 'vscode-languageserver' ; import { TextDocument } from 'vscode-languageserver-textdocument' ; import { URI } from 'vscode-uri' ; import { LangiumServices , LangiumSharedServices } from '../services' ; import { OperationCancelled , startCancelableOperation } from '../utils/promise-util' ; import { LangiumDocument } from '../workspace/documents' ; import { DefaultSemanticTokenOptions } from './semantic-token-provider' ; export function startLanguageServer ( services ) { const languages = services . ServiceRegistry . all ; const connection = services . lsp . Connection ; if ( ! connection ) { throw new Error ( 'Starting a language server requires the languageServer.Connection service to be set.' ) ; } connection . onInitialize ( async ( params ) => { const capabilities = params . capabilities ; const hasWorkspaceFolderCapability = ! ! capabilities . workspace ?. workspaceFolders ; const result = { capabilities : { textDocumentSync : TextDocumentSyncKind . Incremental , completionProvider : { } , referencesProvider : { } , documentSymbolProvider : { } , definitionProvider : { } , documentHighlightProvider : { } , codeActionProvider : languages . some ( e => e . lsp . CodeActionProvider !== undefined ) ? { } : undefined , foldingRangeProvider : { } , hoverProvider : { } , renameProvider : { prepareProvider : true } , semanticTokensProvider : languages . some ( e => e . lsp . SemanticTokenProvider !== undefined ) ? DefaultSemanticTokenOptions : undefined } } ; if ( hasWorkspaceFolderCapability ) { result . capabilities . workspace = { workspaceFolders : { supported : true } } ; } if ( params . capabilities . workspace ?. configuration ) { try { if ( params . workspaceFolders ) await services . workspace . IndexManager . initializeWorkspace ( params . workspaceFolders ) ; } catch ( e ) { console . error ( e ) ; } } return result ; } ) ; const documents = services . workspace . TextDocuments ; addDocumentsHandler ( connection , documents , services ) ; addCompletionHandler ( connection , services ) ; addFindReferencesHandler ( connection , services ) ; addDocumentSymbolHandler ( connection , services ) ; addGotoDefinitionHandler ( connection , services ) ; addDocumentHighlightsHandler ( connection , services ) ; addFoldingRangeHandler ( connection , services ) ; addCodeActionHandler ( connection , services ) ; addRenameHandler ( connection , services ) ; addHoverHandler ( connection , services ) ; addSemanticHighlighting ( connection , services ) ; documents . listen ( connection ) ; connection . listen ( ) ; } export function addDocumentsHandler ( connection , documents , services ) { const documentBuilder = services . workspace . DocumentBuilder ; let changeTokenSource ; let changePromise ; async function onDidChange ( changed , deleted ? ) { changeTokenSource ?. cancel ( ) ; if ( changePromise ) { await changePromise ; } changeTokenSource = startCancelableOperation ( ) ; changePromise = documentBuilder . update ( changed , deleted ?? [ ] , changeTokenSource . token ) . catch ( err => { if ( err !== OperationCancelled ) { console . error ( 'Error: ' , err ) ; } } ) ; } documents . onDidChangeContent ( async ( change ) => { onDidChange ( [ URI . parse ( change . document . uri ) ] ) ; } ) ; connection . onDidChangeWatchedFiles ( async ( params ) => { const changedUris = params . changes . filter ( e => e . type !== FileChangeType . Deleted ) . map ( e => URI . parse ( e . uri ) ) ; const deletedUris = params . changes . filter ( e => e . type === FileChangeType . Deleted ) . map ( e => URI . parse ( e . uri ) ) ; onDidChange ( changedUris , deletedUris ) ; } ) ; } export function addCompletionHandler ( connection , services ) { connection . onCompletion ( createRequestHandler ( ( services , document , params , cancelToken ) => { return services . lsp . completion . CompletionProvider . getCompletion ( document , params , cancelToken ) ; } , services ) ) ; } export function addFindReferencesHandler ( connection , services ) { connection . onReferences ( createRequestHandler ( ( services , document , params , cancelToken ) => services . lsp . ReferenceFinder . findReferences ( document , params , cancelToken ) , services ) ) ; } export function addCodeActionHandler ( connection , services ) { connection . onCodeAction ( createRequestHandler ( ( services , document , params , cancelToken ) => services . lsp . CodeActionProvider ?. getCodeActions ( document , params , cancelToken ) , services ) ) ; } export function addDocumentSymbolHandler ( connection , services ) { connection . onDocumentSymbol ( createRequestHandler ( ( services , document , params , cancelToken ) => services . lsp . DocumentSymbolProvider . getSymbols ( document , params , cancelToken ) , services ) ) ; } export function addGotoDefinitionHandler ( connection , services ) { connection . onDefinition ( createRequestHandler ( ( services , document , params , cancelToken ) => services . lsp . GoToResolver . goToDefinition ( document , params , cancelToken ) , services ) ) ; } export function addDocumentHighlightsHandler ( connection , services ) { connection . onDocumentHighlight ( createRequestHandler ( ( services , document , params , cancelToken ) => services . lsp . DocumentHighlighter . findHighlights ( document , params , cancelToken ) , services ) ) ; } export function addHoverHandler ( connection , services ) { connection . onHover ( createRequestHandler ( ( services , document , params , cancelToken ) => services . lsp . HoverProvider . getHoverContent ( document , params , cancelToken ) , services ) ) ; } export function addFoldingRangeHandler ( connection , services ) { connection . onFoldingRanges ( createRequestHandler ( ( services , document , params , cancelToken ) => services . lsp . FoldingRangeProvider . getFoldingRanges ( document , params , cancelToken ) , services ) ) ; } export function addRenameHandler ( connection , services ) { connection . onRenameRequest ( createRequestHandler ( ( services , document , params , cancelToken ) => services . lsp . RenameHandler . renameElement ( document , params , cancelToken ) , services ) ) ; connection . onPrepareRename ( createRequestHandler ( ( services , document , params , cancelToken ) => services . lsp . RenameHandler . prepareRename ( document , params , cancelToken ) , services ) ) ; } export function addSemanticHighlighting ( connection , services ) { connection . languages . semanticTokens . on ( createServerRequestHandler ( ( services , document , params , cancelToken ) => { if ( services . lsp . SemanticTokenProvider ) { return services . lsp . SemanticTokenProvider . semanticHighlight ( document , params , cancelToken ) ; } return new ResponseError < void > ( 0 , '' ) ; } , services ) ) ; connection . languages . semanticTokens . onDelta ( createServerRequestHandler ( ( services , document , params , cancelToken ) => { if ( services . lsp . SemanticTokenProvider ) { return services . lsp . SemanticTokenProvider . semanticHighlightDelta ( document , params , cancelToken ) ; } return new ResponseError < void > ( 0 , '' ) ; } , services ) ) ; connection . languages . semanticTokens . onRange ( createServerRequestHandler ( ( services , document , params , cancelToken ) => { if ( services . lsp . SemanticTokenProvider ) { return services . lsp . SemanticTokenProvider . semanticHighlightRange ( document , params , cancelToken ) ; } return new ResponseError < void > ( 0 , '' ) ; } , services ) ) ; } export function createServerRequestHandler < P extends { textDocument : TextDocumentIdentifier ; } , R , PR , E = void > ( serviceCall , sharedServices ) { return async ( params , cancelToken ) => { const uri = URI . parse ( params . textDocument . uri ) ; const language = sharedServices . ServiceRegistry . getServices ( uri ) ; if ( ! language ) { console . error ( `Could not find service instance for uri: '${ uri . toString ( ) }'` ) ; throw new Error ( ) ; } const document = sharedServices . workspace . LangiumDocuments . getOrCreateDocument ( uri ) ; if ( ! document ) { throw new Error ( ) ; } try { return await serviceCall ( language , document , params , cancelToken ) ; } catch ( err ) { return responseError < E > ( err ) ; } } ; } export function createRequestHandler < P extends { textDocument : TextDocumentIdentifier ; } , R , E = void > ( serviceCall , sharedServices ) { return async ( params , cancelToken ) => { const uri = URI . parse ( params . textDocument . uri ) ; const language = sharedServices . ServiceRegistry . getServices ( uri ) ; if ( ! language ) { console . error ( `Could not find service instance for uri: '${ uri . toString ( ) }'` ) ; return null ; } const document = sharedServices . workspace . LangiumDocuments . getOrCreateDocument ( uri ) ; if ( ! document ) { return null ; } try { return await serviceCall ( language , document , params , cancelToken ) ; } catch ( err ) { return responseError < E > ( err ) ; } } ; } function responseError < E = void > ( err ) { if ( err === OperationCancelled ) { return new ResponseError ( LSPErrorCodes . RequestCancelled , 'The request has been cancelled.' ) ; } if ( err instanceof ResponseError ) { return err ; } throw err ; }