import { ChildProcess , execSync , spawn , SpawnOptions } from 'child_process' ; import { EventEmitter } from 'events' ; import { existsSync } from 'fs' ; import { EOL , platform } from 'os' ; import { normalize } from 'path' ; import { createInterface } from 'readline' ; import protocol from 'typescript/lib/protocol' ; import { trim } from './utils' ; export class Client extends EventEmitter { public serverHandle : ChildProcess = null ; public _rl : any ; public _seqNumber = 0 ; public _seqToPromises = { } ; public _cwd = process . cwd ( ) ; public _env = process . env ; public serverPath = 'tsserver' ; public serverOptions : string [ ] = [ ] ; public logFunc : Function = null ; public completionCommand = 'completionInfo' ; public tsConfigVersion : { major : number ; minor : number ; patch : number ; } = null ; private getErrRes = [ ] ; getServerPath ( ) { return this . serverPath ; } setServerPath ( val ) { const normalizedPath = normalize ( val ) ; if ( existsSync ( normalizedPath ) ) { this . serverPath = normalizedPath ; } } startServer ( ) { return new Promise ( ( res ) => { let args = [ ... this . serverOptions , '--disableAutomaticTypingAcquisition' ] ; let options = { stdio : 'pipe' , cwd : this . _cwd , env : this . _env , detached : true , shell : false } ; let cmd = this . serverPath ; if ( platform ( ) === 'win32' ) { options . detached = false ; args = [ '/c' , this . serverPath , ... args ] ; cmd = 'cmd' ; } this . serverHandle = spawn ( cmd , args , options ) ; this . _rl = createInterface ( { input : this . serverHandle . stdout , output : this . serverHandle . stdin , terminal : false } ) ; this . serverHandle . stderr . on ( 'data' , ( _data , _err ) => { } ) ; this . serverHandle . on ( 'error' , _data => { } ) ; this . serverHandle . on ( 'exit' , _data => { } ) ; this . serverHandle . on ( 'close' , _data => { } ) ; this . _rl . on ( 'line' , ( msg ) => { if ( msg . indexOf ( '{' ) === 0 ) { this . parseResponse ( msg ) ; } } ) ; return res ( ) ; } ) ; } stopServer ( ) { this . serverHandle . kill ( 'SIGINT' ) ; this . serverHandle = null ; } setTSConfigVersion ( ) { const command = this . serverPath . replace ( 'tsserver' , 'tsc' ) ; const rawOutput = execSync ( `${ command } --version` ) . toString ( ) ; const [ major , minor , patch ] = trim ( rawOutput ) . split ( ' ' ) . pop ( ) . split ( '-' ) [ 0 ] . split ( '.' ) ; this . tsConfigVersion = { major : parseInt ( major ) , minor : parseInt ( minor ) , patch : parseInt ( patch ) } ; this . completionCommand = this . isCurrentVersionHighter ( 300 ) ? 'completionInfo' : 'completions' ; } isCurrentVersionHighter ( val ) { const local = this . tsConfigVersion . major * 100 + this . tsConfigVersion . minor * 10 + this . tsConfigVersion . patch ; return local >= val ; } openFile ( args ) { this . _makeNoResponseRequest ( 'open' , args ) ; } closeFile ( args ) { this . _makeNoResponseRequest ( 'close' , args ) ; } reloadProject ( ) { this . _makeNoResponseRequest ( 'reloadProjects' , null ) ; } updateFile ( args ) { return this . _makeTssRequest ( 'reload' , args ) ; } quickInfo ( args ) { return this . _makeTssRequest ( 'quickinfo' , args ) ; } getDef ( args ) { return this . _makeTssRequest ( 'definition' , args ) ; } getCompletions ( args ) { return this . _makeTssRequest ( this . completionCommand , args ) ; } getCompletionDetails ( args ) { return this . _makeTssRequest ( 'completionEntryDetails' , args ) ; } getProjectInfo ( args ) { return this . _makeTssRequest ( 'projectInfo' , args ) ; } getSymbolRefs ( args ) { return this . _makeTssRequest ( 'references' , args ) ; } getSignature ( args ) { return this . _makeTssRequest ( 'signatureHelp' , args ) ; } renameSymbol ( args ) { return this . _makeTssRequest ( 'rename' , args ) ; } getTypeDef ( args ) { return this . _makeTssRequest ( 'typeDefinition' , args ) ; } getDocumentSymbols ( args ) { return this . _makeTssRequest ( 'navtree' , args ) ; } getWorkspaceSymbols ( args ) { return this . _makeTssRequest ( 'navto' , args ) ; } getSemanticDiagnosticsSync ( args ) { return this . _makeTssRequest ( 'semanticDiagnosticsSync' , args ) ; } getSyntacticDiagnosticsSync ( args ) { return this . _makeTssRequest ( 'syntacticDiagnosticsSync' , args ) ; } getSuggestionDiagnosticsSync ( args ) { return this . _makeTssRequest ( 'suggestionDiagnosticsSync' , args ) ; } getCodeFixes ( args ) { return this . _makeTssRequest ( 'getCodeFixes' , args ) ; } getApplicableRefactors ( args ) { return this . _makeTssRequest ( 'getApplicableRefactors' , args ) ; } getSupportedCodeFixes ( ) { return this . _makeTssRequest ( 'getSupportedCodeFixes' , null ) ; } getCombinedCodeFix ( args ) { return this . _makeTssRequest ( 'getCombinedCodeFix' , args ) ; } getOrganizedImports ( args ) { return this . _makeTssRequest ( 'organizeImports' , args ) ; } getProjectError ( args ) { this . _makeTssRequest ( 'geterrForProject' , args ) ; } getEditsForFileRename ( args ) { return this . _makeTssRequest ( 'getEditsForFileRename' , args ) ; } _makeTssRequest < T > ( commandName , args ? ) { const seq = this . _seqNumber ++ ; const payload = { seq , type : 'request' , arguments : args , command : commandName } ; const ret = this . createDeferredPromise ( ) ; this . _seqToPromises [ seq ] = ret ; this . serverHandle . stdin . write ( JSON . stringify ( payload ) + EOL ) ; return ret . promise ; } _makeNoResponseRequest ( commandName ? , args ? ) { const seq = this . _seqNumber ++ ; const payload = { seq , type : 'request' , arguments : args } ; if ( commandName ) { payload [ 'command' ] = commandName ; } this . serverHandle . stdin . write ( JSON . stringify ( payload ) + EOL ) ; } parseResponse ( returnedData ) { const response = JSON . parse ( returnedData ) ; const seq = response . request_seq ; const success = response . success ; if ( typeof seq === 'number' ) { if ( success ) { this . _seqToPromises [ seq ] . resolve ( response . body ) ; } else { this . _seqToPromises [ seq ] . reject ( response . message ) ; } } else { if ( response . type && response . type === 'event' ) { if ( response . event && response . event === 'telemetry' ) { } if ( response . event && response . event === 'projectsUpdatedInBackground' ) { } if ( response . event && response . event === 'projectLoadingFinish' ) { this . emit ( 'projectLoadingFinish' ) ; } if ( response . event && ( response . event === 'semanticDiag' || response . event === 'syntaxDiag' || response . event === 'suggestionDiag' ) ) { this . getErrRes . push ( response . body ) ; } if ( response . event && response . event === 'requestCompleted' ) { this . getErrCompleted ( ) ; } } } } getErrCompleted ( ) { this . emit ( 'getErrCompleted' , this . getErrRes ) ; this . getErrRes = [ ] ; } createDeferredPromise ( ) { let resolve ; let reject ; const promise = new Promise ( ( res , rej ) => { resolve = res ; reject = rej ; } ) ; return { resolve , reject , promise } ; } } export const TSServer = new Client ( ) ;