# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2009- Spyder Project Contributors # # Distributed under the terms of the MIT License # (see spyder/__init__.py for details) # ----------------------------------------------------------------------------- """ Spyder MS Language Server Protocol v3.0 transport proxy base implementation. This module provides the base class from which each transport-specific client should inherit from. LanguageServerClient implements functions to handle incoming requests from the actual Spyder LSP client ZMQ queue and to encapsulate them into valid JSONRPC messages before sending them to the LSP server, using the specific transport mode. """ # Standard library imports import json import logging # Third party imports import zmq TIMEOUT = 5000 LOCALHOST = '127.0.0.1' logger = logging.getLogger(__name__) class LanguageServerClient(object): """Base implementation of a v3.0 compilant language server client.""" CONTENT_LENGTH = 'Content-Length: {0}\r\n\r\n' def __init__(self, zmq_in_port=7000, zmq_out_port=7001): self.zmq_in_port = zmq_in_port self.zmq_out_port = zmq_out_port self.context = None self.zmq_in_socket = None self.zmq_out_socket = None def finalize_initialization(self): connected, connection_error, pid = self.is_server_alive() if not connected: logger.error("The client was unable to establish a connection " "with the Language Server. The error was: " "{}".format(connection_error)) raise Exception("An error occurred while trying to create a " "client to connect to the Language Server! The " "error was\n\n{}".format(connection_error)) logger.info('Starting ZMQ connection...') self.context = zmq.Context() self.zmq_in_socket = self.context.socket(zmq.PAIR) self.zmq_in_socket.connect("tcp://{0}:{1}".format( LOCALHOST, self.zmq_in_port)) self.zmq_out_socket = self.context.socket(zmq.PAIR) self.zmq_out_socket.connect("tcp://{0}:{1}".format( LOCALHOST, self.zmq_out_port)) logger.info('Sending server_ready...') self.zmq_out_socket.send_pyobj({'id': 0, 'method': 'server_ready', 'params': {'pid': pid}}) def listen(self): events = self.zmq_in_socket.poll(TIMEOUT) # requests = [] while events > 0: client_request = self.zmq_in_socket.recv_pyobj() logger.debug("Client Event: {0}".format(client_request)) server_request = self.__compose_request(client_request) self.__send_request(server_request) # self.zmq_socket.send_pyobj({'a': 'b'}) events -= 1 def __compose_request(self, request): request['jsonrpc'] = '2.0' return request def __send_request(self, request): json_req = json.dumps(request) content = bytes(json_req.encode('utf-8')) content_length = len(content) if 'method' in request: if 'id' in request: logger.debug( 'Sending request of type: {0}'.format(request['method'])) else: logger.debug( 'Sending notification of type: {0}'.format( request['method'])) else: logger.debug('Sending reply to server') logger.debug(json_req) content_length = self.CONTENT_LENGTH.format( content_length).encode('utf-8') self.transport_send(bytes(content_length), content) def transport_send(self, content_length, body): """Subclasses should override this method""" raise NotImplementedError("Not implemented") def is_server_alive(self): """Subclasses should override this method""" raise NotImplementedError("Not implemented") def start(self): """Subclasses should override this method.""" raise NotImplementedError("Not implemented") def stop(self): """Subclasses should override this method.""" raise NotImplementedError("Not implemented")