71 lines
		
	
	
		
			2.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			71 lines
		
	
	
		
			2.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | import { ChildProcess } from "child_process"; | ||
|  | import { EventEmitter } from "events"; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Provides a client for tsserver. Tsserver does not use standard JSON-RPC | ||
|  |  * protocol thus the need for this custom client. | ||
|  |  */ | ||
|  | export class Client { | ||
|  |   private data: Buffer|undefined; | ||
|  |   private id = 0; | ||
|  |   private responseEmitter = new EventEmitter(); | ||
|  | 
 | ||
|  |   constructor(private readonly server: ChildProcess) {} | ||
|  | 
 | ||
|  |   listen() { | ||
|  |     this.server.stdout.on('data', (data: Buffer) => { | ||
|  |       this.data = this.data ? Buffer.concat([this.data, data]) : data; | ||
|  |       const CONTENT_LENGTH = 'Content-Length: ' | ||
|  |       const index = this.data.indexOf(CONTENT_LENGTH); | ||
|  |       if (index < 0) { | ||
|  |         return; | ||
|  |       } | ||
|  |       let start = index + CONTENT_LENGTH.length; | ||
|  |       let end = this.data.indexOf('\r\n', start); | ||
|  |       if (end < start) { | ||
|  |         return; | ||
|  |       } | ||
|  |       const contentLengthStr = this.data.slice(start, end).toString(); | ||
|  |       const contentLength = Number(contentLengthStr); | ||
|  |       if (isNaN(contentLength) || contentLength < 0) { | ||
|  |         return; | ||
|  |       } | ||
|  |       start = end + 4; | ||
|  |       end = start + contentLength; | ||
|  |       if (end > this.data.length) { | ||
|  |         return; | ||
|  |       } | ||
|  |       const content = this.data.slice(start, end).toString(); | ||
|  |       this.data = this.data.slice(end); | ||
|  |       try { | ||
|  |         const payload = JSON.parse(content); | ||
|  |         if (payload.type === "event") { | ||
|  |           return; | ||
|  |         } | ||
|  |         this.responseEmitter.emit('response', payload); | ||
|  |       } | ||
|  |       catch (error) { | ||
|  |         this.responseEmitter.emit('error', error); | ||
|  |       } | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   async send(type: string, command: string, params: {}) { | ||
|  |     const request = { | ||
|  |       seq: this.id++, | ||
|  |       type, | ||
|  |       command, | ||
|  |       arguments: params | ||
|  |     }; | ||
|  |     this.server.stdin.write(JSON.stringify(request) + '\r\n'); | ||
|  |     return new Promise((resolve, reject) => { | ||
|  |       this.responseEmitter.once('response', resolve); | ||
|  |       this.responseEmitter.once('error', reject); | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   async sendRequest(command: string, params: {}) { | ||
|  |     return this.send('request', command, params); | ||
|  |   } | ||
|  | } |