'use strict' ; import * as vscode from 'vscode' ; import * as util from '../common' ; import * as cpptools from './client' ; import * as telemetry from '../telemetry' ; import { getCustomConfigProviders } from './customProviders' ; import { TimeTelemetryCollector } from './timeTelemetryCollector' ; const defaultClientKey = "@@default@@" ; export interface ClientKey { name : string ; key : string ; } export class ClientCollection { private disposables : vscode . Disposable [ ] = [ ] ; private languageClients = new Map < string , cpptools . Client > ( ) ; private defaultClient : cpptools . Client ; private activeClient : cpptools . Client ; private activeDocument ? : vscode . TextDocument ; public timeTelemetryCollector : TimeTelemetryCollector = new TimeTelemetryCollector ( ) ; public get ActiveClient ( ) : cpptools . Client { return this . activeClient ; } public get Names ( ) : ClientKey [ ] { const result = [ ] ; this . languageClients . forEach ( ( client , key ) => { result . push ( { name : client . Name , key : key } ) ; } ) ; return result ; } public get Count ( ) : number { return this . languageClients . size ; } constructor ( ) { let key = defaultClientKey ; if ( vscode . workspace . workspaceFolders && vscode . workspace . workspaceFolders . length > 0 ) { let isFirstWorkspaceFolder = true ; vscode . workspace . workspaceFolders . forEach ( folder => { const newClient = cpptools . createClient ( this , folder ) ; this . languageClients . set ( util . asFolder ( folder . uri ) , newClient ) ; if ( isFirstWorkspaceFolder ) { isFirstWorkspaceFolder = false ; } else { newClient . deactivate ( ) ; } } ) ; key = util . asFolder ( vscode . workspace . workspaceFolders [ 0 ] . uri ) ; const client = this . languageClients . get ( key ) ; if ( ! client ) { throw new Error ( "Failed to construct default client" ) ; } this . activeClient = client ; } else { this . activeClient = cpptools . createClient ( this ) ; } this . defaultClient = this . activeClient ; this . languageClients . set ( key , this . activeClient ) ; this . disposables . push ( vscode . workspace . onDidChangeWorkspaceFolders ( e => this . onDidChangeWorkspaceFolders ( e ) ) ) ; this . disposables . push ( vscode . workspace . onDidCloseTextDocument ( d => this . onDidCloseTextDocument ( d ) ) ) ; } public async activeDocumentChanged ( document ) { this . activeDocument = document ; const activeClient = this . getClientFor ( document . uri ) ; await activeClient . activeDocumentChanged ( document ) ; if ( activeClient !== this . activeClient ) { activeClient . activate ( ) ; this . activeClient . deactivate ( ) ; this . activeClient = activeClient ; } } /**
     * get a handle to a language client. returns undefined if the client was not found.
     */ public get ( key ) { const client = this . languageClients . get ( key ) ; console . assert ( client , "key not found" ) ; return client ; } public forEach ( callback ) { const languageClients = [ ] ; this . languageClients . forEach ( client => languageClients . push ( client ) ) ; languageClients . forEach ( callback ) ; } public checkOwnership ( client , document ) { return ( this . getClientFor ( document . uri ) === client ) ; } /**
     * creates a new client to replace one that crashed.
     */ public replace ( client , transferFileOwnership ) { let key ; for ( const pair of this . languageClients ) { if ( pair [ 1 ] === client ) { key = pair [ 0 ] ; break ; } } if ( key ) { this . languageClients . delete ( key ) ; if ( transferFileOwnership ) { client . TrackedDocuments . forEach ( document => this . transferOwnership ( document , client ) ) ; client . TrackedDocuments . clear ( ) ; } else { this . languageClients . set ( key , cpptools . createNullClient ( ) ) ; } if ( this . activeClient === client && this . activeDocument ) { this . activeClient = this . getClientFor ( this . activeDocument . uri ) ; this . activeClient . activeDocumentChanged ( this . activeDocument ) ; } client . dispose ( ) ; return this . languageClients . get ( key ) ; } else { console . assert ( key , "unable to locate language client" ) ; return undefined ; } } private onDidChangeWorkspaceFolders ( e ? ) { const folderCount = vscode . workspace . workspaceFolders ? vscode . workspace . workspaceFolders . length : 0 ; if ( folderCount > 1 ) { telemetry . logLanguageServerEvent ( "workspaceFoldersChange" , { "count" : folderCount . toString ( ) } ) ; } if ( e !== undefined ) { e . removed . forEach ( folder => { const path = util . asFolder ( folder . uri ) ; const client = this . languageClients . get ( path ) ; if ( client ) { this . languageClients . delete ( path ) ; client . TrackedDocuments . forEach ( document => this . transferOwnership ( document , client ) ) ; client . TrackedDocuments . clear ( ) ; if ( this . activeClient === client && this . activeDocument ) { this . activeClient = this . getClientFor ( this . activeDocument . uri ) ; this . activeClient . activeDocumentChanged ( this . activeDocument ) ; } client . dispose ( ) ; } } ) ; e . added . forEach ( folder => { const path = util . asFolder ( folder . uri ) ; const client = this . languageClients . get ( path ) ; if ( ! client ) { const newClient = cpptools . createClient ( this , folder ) ; this . languageClients . set ( path , newClient ) ; newClient . deactivate ( ) ; const defaultClient = < cpptools . DefaultClient > newClient ; defaultClient . sendAllSettings ( ) ; } } ) ; } } private transferOwnership ( document , oldOwner ) { const newOwner = this . getClientFor ( document . uri ) ; if ( newOwner !== oldOwner ) { newOwner . takeOwnership ( document ) ; } } public getClientFor ( uri ) { const folder = uri ? vscode . workspace . getWorkspaceFolder ( uri ) : undefined ; if ( ! folder ) { return this . defaultClient ; } else { const key = util . asFolder ( folder . uri ) ; const client = this . languageClients . get ( key ) ; if ( client ) { return client ; } const newClient = cpptools . createClient ( this , folder ) ; this . languageClients . set ( key , newClient ) ; getCustomConfigProviders ( ) . forEach ( provider => newClient . onRegisterCustomConfigurationProvider ( provider ) ) ; const defaultClient = < cpptools . DefaultClient > newClient ; defaultClient . sendAllSettings ( ) ; return newClient ; } } private onDidCloseTextDocument ( document ) { } public dispose ( ) { this . disposables . forEach ( ( d ) => d . dispose ( ) ) ; this . languageClients . forEach ( client => client . dispose ( ) ) ; this . languageClients . clear ( ) ; cpptools . disposeWorkspaceData ( ) ; return cpptools . DefaultClient . stopLanguageClient ( ) ; } }