import { search as searchForConfig } from "@css-blocks/config" ; import { BlockFactory , Configuration , CssBlockError , Syntax , resolveConfiguration } from "@css-blocks/core" ; import { CompletionItem , Definition , DidChangeConfigurationNotification , DocumentLink , DocumentLinkParams , IConnection , InitializeParams , InitializeResult , Location , ReferenceParams , TextDocumentChangeEvent , TextDocumentPositionParams , TextDocuments } from "vscode-languageserver" ; import { URI } from "vscode-uri" ; import { emberCompletionProvider } from "./completionProviders/emberCompletionProvider" ; import { createBlockFactory } from "./createBlockFactory" ; import { emberDefinitionProvider } from "./definitionProviders/emberDefinitionProvider" ; import { blockLinksProvider } from "./documentLinksProviders/blockLinkProvider" ; import { documentContentChange } from "./eventHandlers/documentContentChange" ; import { LSImporter } from "./Importer" ; import { EmberClassicTransformer } from "./pathTransformers/EmberClassicTransformer" ; import { PathTransformer } from "./pathTransformers/PathTransformer" ; import { SERVER_CAPABILITIES } from "./serverCapabilities" ; import { isBlockFile } from "./util/blockUtils" ; import { convertErrorsToDiagnostics } from "./util/diagnosticsUtils" ; import { isTemplateFile , validateTemplates } from "./util/hbsUtils" ; export class Server { _blockFactory : BlockFactory | undefined ; _config : Readonly < Configuration > | undefined ; _pathTransformer : PathTransformer | undefined ; connection : IConnection ; documents : TextDocuments ; hasConfigurationCapability = false ; hasWorkspaceFolderCapability = false ; constructor ( connection , documents ) { this . connection = connection ; this . documents = documents ; this . registerDocumentEvents ( ) ; this . registerConnectionEvents ( ) ; } get config ( ) : Readonly < Configuration > { if ( ! this . _config ) { this . _config = resolveConfiguration ( { } ) ; } return this . _config ; } get blockFactory ( ) : BlockFactory { if ( ! this . _blockFactory ) { this . _blockFactory = createBlockFactory ( this . config ) ; } return this . _blockFactory ; } get pathTransformer ( ) : PathTransformer { if ( ! this . _pathTransformer ) { let syntaxes = new Array < Syntax > ( ) ; let keys = Object . keys ( this . config . preprocessors ) ; for ( let k of keys ) { if ( Syntax [ k ] ) { syntaxes . push ( Syntax [ k ] ) ; } } if ( ! syntaxes . includes ( Syntax . css ) ) { syntaxes . push ( Syntax . css ) ; } this . _pathTransformer = new EmberClassicTransformer ( syntaxes ) ; } return this . _pathTransformer ; } listen ( ) { this . documents . listen ( this . connection ) ; this . connection . listen ( ) ; } async validateTemplates ( ) { this . blockFactory . reset ( ) ; let templateUriToErrors = await validateTemplates ( this . documents , this . blockFactory , this . pathTransformer ) ; this . distributeDiagnostics ( templateUriToErrors ) ; } async onDidChangeContent ( e ) { this . blockFactory . reset ( ) ; if ( isBlockFile ( e . document . uri , this . config ) ) { const cssBlockErrors = await documentContentChange ( e , this . blockFactory ) ; this . sendDiagnostics ( cssBlockErrors , e . document . uri ) ; } else if ( isTemplateFile ( e . document . uri ) ) { } } private registerDocumentEvents ( ) { this . documents . onDidChangeContent ( this . onDidChangeContent . bind ( this ) ) ; this . documents . onDidSave ( this . validateTemplates . bind ( this ) ) ; this . documents . onDidOpen ( this . validateTemplates . bind ( this ) ) ; } private registerConnectionEvents ( ) { this . connection . onInitialize ( this . onConnectionInitialize . bind ( this ) ) ; this . connection . onInitialized ( this . afterConnectionInitialized . bind ( this ) ) ; this . connection . onCompletion ( async ( params ) : Promise < CompletionItem [ ] > => { return await emberCompletionProvider ( this . documents , this . blockFactory , params , this . pathTransformer ) ; } ) ; this . connection . onDefinition ( async ( params ) : Promise < Definition > => { return await emberDefinitionProvider ( this . documents , this . blockFactory , params , this . pathTransformer ) ; } ) ; this . connection . onReferences ( async ( params ) : Promise < Location [ ] > => { let uri = params . textDocument . uri ; let locations = [ ] ; if ( isTemplateFile ( uri ) ) { let document = this . documents . get ( params . textDocument . uri ) ; if ( ! document ) { return locations ; } } return locations ; } ) ; this . connection . onDocumentLinks ( async ( params ) : Promise < DocumentLink [ ] > => { return await blockLinksProvider ( this . documents , params , this . config ) ; } ) ; } private async onConnectionInitialize ( params ) { let capabilities = params . capabilities ; let options ; let result = null ; let rootDir = null ; if ( params . workspaceFolders ) { for ( let wsFolder of params . workspaceFolders ) { let folderPath = URI . parse ( wsFolder . uri ) . fsPath ; result = await searchForConfig ( folderPath ) ; if ( result ) { rootDir = folderPath ; break ; } } } else if ( params . rootPath ) { rootDir = params . rootPath ; result = await searchForConfig ( params . rootPath ) ; } options = result || ( rootDir ? { rootDir } : { } ) ; options . importer = new LSImporter ( this . documents , options . importer ) ; this . _config = resolveConfiguration ( options ) ; this . _blockFactory = createBlockFactory ( this . _config ) ; this . hasConfigurationCapability = ! ! ( capabilities . workspace && ! ! capabilities . workspace . configuration ) ; this . hasWorkspaceFolderCapability = ! ! ( capabilities . workspace && ! ! capabilities . workspace . workspaceFolders ) ; return { capabilities : SERVER_CAPABILITIES , } ; } private afterConnectionInitialized ( ) { if ( this . hasConfigurationCapability ) { this . connection . client . register ( DidChangeConfigurationNotification . type , undefined ) ; } if ( this . hasWorkspaceFolderCapability ) { this . connection . workspace . onDidChangeWorkspaceFolders ( _event => { this . connection . console . log ( "Workspace folder change event received." ) ; } ) ; } } private distributeDiagnostics ( uriToErrorsMap ) { uriToErrorsMap . forEach ( this . sendDiagnostics . bind ( this ) ) ; } private sendDiagnostics ( errors , uri ) { this . connection . sendDiagnostics ( { uri , diagnostics : convertErrorsToDiagnostics ( errors ) , } ) ; } }