| 
									
										
										
										
											2018-12-17 23:44:26 -08:00
										 |  |  | 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() { | 
					
						
							| 
									
										
										
										
											2019-10-01 16:44:50 -07:00
										 |  |  |     this.server.stdout!.on('data', (data: Buffer) => { | 
					
						
							| 
									
										
										
										
											2018-12-17 23:44:26 -08:00
										 |  |  |       this.data = this.data ? Buffer.concat([this.data, data]) : data; | 
					
						
							| 
									
										
										
										
											2019-04-19 08:53:19 -07:00
										 |  |  |       // tsserver could batch multiple responses together so we have to go
 | 
					
						
							|  |  |  |       // through the entire buffer to keep looking for messages.
 | 
					
						
							| 
									
										
										
										
											2018-12-17 23:44:26 -08:00
										 |  |  |       const CONTENT_LENGTH = 'Content-Length: ' | 
					
						
							| 
									
										
										
										
											2019-04-19 08:53:19 -07:00
										 |  |  |       do { | 
					
						
							|  |  |  |         const index = this.data.indexOf(CONTENT_LENGTH); | 
					
						
							|  |  |  |         if (index < 0) { | 
					
						
							| 
									
										
										
										
											2018-12-17 23:44:26 -08:00
										 |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-04-19 08:53:19 -07:00
										 |  |  |         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 === "response") { | 
					
						
							|  |  |  |             const seq = `${payload.request_seq}`; | 
					
						
							|  |  |  |             this.responseEmitter.emit(seq, payload); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         catch (error) { | 
					
						
							|  |  |  |           this.responseEmitter.emit('error', error); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } while (this.data.length > 0) | 
					
						
							| 
									
										
										
										
											2018-12-17 23:44:26 -08:00
										 |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async send(type: string, command: string, params: {}) { | 
					
						
							| 
									
										
										
										
											2019-04-19 08:53:19 -07:00
										 |  |  |     const seq = this.id++; | 
					
						
							| 
									
										
										
										
											2018-12-17 23:44:26 -08:00
										 |  |  |     const request = { | 
					
						
							| 
									
										
										
										
											2019-04-19 08:53:19 -07:00
										 |  |  |       seq, | 
					
						
							| 
									
										
										
										
											2018-12-17 23:44:26 -08:00
										 |  |  |       type, | 
					
						
							|  |  |  |       command, | 
					
						
							|  |  |  |       arguments: params | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2019-10-01 16:44:50 -07:00
										 |  |  |     this.server.stdin!.write(JSON.stringify(request) + '\r\n'); | 
					
						
							| 
									
										
										
										
											2018-12-17 23:44:26 -08:00
										 |  |  |     return new Promise((resolve, reject) => { | 
					
						
							| 
									
										
										
										
											2019-04-19 08:53:19 -07:00
										 |  |  |       this.responseEmitter.once(`${seq}`, resolve); | 
					
						
							| 
									
										
										
										
											2018-12-17 23:44:26 -08:00
										 |  |  |       this.responseEmitter.once('error', reject); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async sendRequest(command: string, params: {}) { | 
					
						
							|  |  |  |     return this.send('request', command, params); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |