Last active
          May 16, 2024 19:17 
        
      - 
      
 - 
        
Save icholy/8e2164c77d0c2bbac245 to your computer and use it in GitHub Desktop.  
    Python wrapper around TypeScript TSServer
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | #!/usr/bin/env python | |
| # | |
| # Copyright (C) 2015 Google Inc. | |
| # | |
| # This file is part of YouCompleteMe. | |
| # | |
| # YouCompleteMe is free software: you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation, either version 3 of the License, or | |
| # (at your option) any later version. | |
| # | |
| # YouCompleteMe is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. | |
| import json | |
| import logging | |
| import subprocess | |
| from threading import Lock, Event, Thread | |
| from ycmd import utils | |
| BINARY_NOT_FOUND_MESSAGE = ( 'tsserver not found. ' | |
| 'TypeScript 1.5 or higher is required' ) | |
| _logger = logging.getLogger( __name__ ) | |
| class ResponseEvent( Event ): | |
| "Used for blocking the SendRequest method until the response is available" | |
| def __init__( self ): | |
| super( ResponseEvent, self ).__init__() | |
| self._response = None | |
| def SetResponse( self, response ): | |
| self._response = response | |
| self.set() | |
| def GetResponse( self ): | |
| if not self.is_set(): | |
| self.wait() | |
| return self._response | |
| class TSServer: | |
| """ | |
| Wrapper for for TSServer which is bundled with TypeScript 1.5 | |
| See the protocol here: | |
| https://github.com/Microsoft/TypeScript/blob/2cb0dfd99dc2896958b75e44303d8a7a32e5dc33/src/server/protocol.d.ts | |
| """ | |
| def __init__( self, user_options ): | |
| # Used to prevent threads from concurrently reading and writing to | |
| # the tsserver process' stdout and stdin | |
| self._lock = Lock() | |
| binarypath = utils.PathToFirstExistingExecutable( [ 'tsserver' ] ) | |
| if not binarypath: | |
| _logger.error( BINARY_NOT_FOUND_MESSAGE ) | |
| raise RuntimeError( BINARY_NOT_FOUND_MESSAGE ) | |
| # Each request sent to tsserver must have a sequence id. | |
| # Responses contain the id sent in the corresponding request. | |
| self._sequenceid = 0 | |
| # TSServer ignores the fact that newlines are two characters on Windows | |
| # (\r\n) instead of one on other platforms (\n), so we use the | |
| # universal_newlines option to convert those newlines to \n. See the issue | |
| # https://github.com/Microsoft/TypeScript/issues/3403 | |
| # TODO: remove this option when the issue is fixed. | |
| # We also need to redirect the error stream to the output one on Windows. | |
| self._tsserver_handle = utils.SafePopen( binarypath, | |
| stdout = subprocess.PIPE, | |
| stdin = subprocess.PIPE, | |
| stderr = subprocess.STDOUT, | |
| universal_newlines = True ) | |
| # When a request message is sent and requires a response, a ResponseEvent | |
| # is placed in this dictionary and | |
| self._response_events = {} | |
| Thread( target = self._MessageReaderLoop ).start() | |
| def SendRequest( self, command, arguments = None, wait_for_response = True ): | |
| """Send a request message to TSServer.""" | |
| seq = self._NextSequenceId() | |
| request = { | |
| 'seq': seq, | |
| 'type': 'request', | |
| 'command': command | |
| } | |
| if arguments: | |
| request[ 'arguments' ] = arguments | |
| # If the request expects a response, use an Event to block | |
| # until the response is available | |
| if wait_for_response: | |
| event = ResponseEvent() | |
| self._response_events[ seq ] = event | |
| self._WriteMessage( request ) | |
| return event.GetResponse() | |
| self._WriteMessage( request ) | |
| def _NextSequenceId( self ): | |
| seq = self._sequenceid | |
| self._sequenceid += 1 | |
| return seq | |
| def _WriteMessage( self, message ): | |
| with self._lock: | |
| self._tsserver_handle.stdin.write( json.dumps( message ) ) | |
| self._tsserver_handle.stdin.write( "\n" ) | |
| def _MessageReaderLoop( self ): | |
| while True: | |
| try: | |
| message = self._ReadMessage() | |
| if message[ 'type' ] == 'event': | |
| self._HandleEvent( message ) | |
| if message[ 'type' ] == 'response': | |
| self._HandleResponse( message ) | |
| except Exception as e: | |
| _logger.error( e ) | |
| def _ReadMessage( self ): | |
| """Read a response message from TSServer.""" | |
| # The headers are pretty similar to HTTP. | |
| # At the time of writing, 'Content-Length' is the only supplied header. | |
| headers = {} | |
| while True: | |
| headerline = self._tsserver_handle.stdout.readline() | |
| if not headerline: | |
| break | |
| key, value = headerline.split( ':', 1 ) | |
| headers[ key.strip() ] = value.strip() | |
| # The response message is a JSON object which comes back on one line. | |
| # Since this might change in the future, we use the 'Content-Length' | |
| # header. | |
| if 'Content-Length' not in headers: | |
| raise RuntimeError( "Missing 'Content-Length' header" ) | |
| contentlength = int( headers[ 'Content-Length' ] ) | |
| message = json.loads( self._tsserver_handle.stdout.read( contentlength ) ) | |
| return message | |
| def _HandleResponse( self, response ): | |
| seq = response[ 'request_seq' ] | |
| if seq in self._response_events: | |
| self._response_events[ seq ].SetResponse( response ) | |
| del self._response_events[ seq ] | |
| else: | |
| _logger.info( 'Recieved unhandled response (sequence {0})'.format( seq ) ) | |
| def _HandleEvent( self, event ): | |
| """Handle event message from TSServer.""" | |
| # We ignore events for now since we don't have a use for them. | |
| eventname = event[ 'event' ] | |
| _logger.info( 'Recieved {0} event from tsserver'.format( eventname ) ) | |
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment