diff --git a/examples/clangd.py b/examples/clangd.py index 51725f4..d743193 100644 --- a/examples/clangd.py +++ b/examples/clangd.py @@ -9,10 +9,10 @@ class ReadPipe(threading.Thread): self.pipe = pipe def run(self): - line = self.pipe.readline() + line = self.pipe.readline().decode('utf-8') while line: print(line) - line = self.pipe.readline() + line = self.pipe.readline().decode('utf-8') if __name__ == "__main__": clangd_path = "/usr/bin/clangd-6.0" diff --git a/pylspclient/json_rpc_endpoint.py b/pylspclient/json_rpc_endpoint.py index 003308d..85e4bbd 100644 --- a/pylspclient/json_rpc_endpoint.py +++ b/pylspclient/json_rpc_endpoint.py @@ -54,7 +54,7 @@ class JsonRpcEndpoint(object): def recv_response(self): ''' - Recives a message. + Recives a message. :return: a message ''' @@ -62,7 +62,7 @@ class JsonRpcEndpoint(object): line = self.stdout.readline() if not line: return None - line = line.decode() + line = line.decode("utf-8") # TODO: handle content type as well. match = re.match(JSON_RPC_RES_REGEX, line) if match is None or not match.groups(): @@ -71,8 +71,8 @@ class JsonRpcEndpoint(object): line = self.stdout.readline() if not line: return None - line = line.decode() + line = line.decode("utf-8") if line != "\r\n": raise RuntimeError("Bad header: missing newline") - jsonrpc_res = self.stdout.read(size) + jsonrpc_res = self.stdout.read(size).decode("utf-8") return json.loads(jsonrpc_res) diff --git a/pylspclient/lsp_endpoint.py b/pylspclient/lsp_endpoint.py index 7de605a..ffda502 100644 --- a/pylspclient/lsp_endpoint.py +++ b/pylspclient/lsp_endpoint.py @@ -1,21 +1,24 @@ from __future__ import print_function import threading +import collections +from pylspclient import lsp_structs + class LspEndpoint(threading.Thread): - def __init__(self, json_rpc_endpoint, default_callback=print, callbacks={}): + def __init__(self, json_rpc_endpoint, method_callbacks={}, notify_callbacks={}): threading.Thread.__init__(self) self.json_rpc_endpoint = json_rpc_endpoint - self.callbacks = callbacks - self.default_callback = default_callback + self.notify_callbacks = notify_callbacks + self.method_callbacks = method_callbacks self.event_dict = {} self.response_dict = {} self.next_id = 0 self.shutdown_flag = False - def handle_result(self, jsonrpc_res): - self.response_dict[jsonrpc_res["id"]] = jsonrpc_res - cond = self.event_dict[jsonrpc_res["id"]] + def handle_result(self, rpc_id, result, error): + self.response_dict[rpc_id] = (result, error) + cond = self.event_dict[rpc_id] cond.acquire() cond.notify() cond.release() @@ -27,23 +30,48 @@ class LspEndpoint(threading.Thread): def run(self): while not self.shutdown_flag: - jsonrpc_message = self.json_rpc_endpoint.recv_response() - - if jsonrpc_message is None: - print("server quit") - break + try: + jsonrpc_message = self.json_rpc_endpoint.recv_response() + method = jsonrpc_message.get("method") + result = jsonrpc_message.get("result") + error = jsonrpc_message.get("error") + rpc_id = jsonrpc_message.get("id") + params = jsonrpc_message.get("params") - if "result" in jsonrpc_message or "error" in jsonrpc_message: - self.handle_result(jsonrpc_message) - elif "method" in jsonrpc_message: - if jsonrpc_message["method"] in self.callbacks: - self.callbacks[jsonrpc_message["method"]](jsonrpc_message) + if jsonrpc_message is None: + print("server quit") + break + + if method: + if rpc_id: + # a call for method + if method not in self.method_callbacks: + raise lsp_structs.ResponseError("Method not found: {method}".format(method=method), lsp_structs.ErrorCodes.MethodNotFound) + result = self.method_callbacks[method](params) + self.send_response(rpc_id, result, None) + else: + # a call for notify + if method not in self.notify_callbacks: + raise lsp_structs.ResponseError("Method not found: {method}".format(method=method), lsp_structs.ErrorCodes.MethodNotFound) + + self.notify_callbacks[method](params) else: - self.default_callback(jsonrpc_message) - else: - print("unknown jsonrpc message") - - + self.handle_result(rpc_id, result, error) + except lsp_structs.ResponseError as e: + self.send_response(rpc_id, None, e) + + + def send_response(self, id, result, error): + message_dict = {} + message_dict["jsonrpc"] = "2.0" + message_dict["id"] = id + if result: + message_dict["result"] = result + if error: + message_dict["error"] = error + self.json_rpc_endpoint.send_request(message_dict) + + def send_message(self, method_name, params, id = None): message_dict = {} message_dict["jsonrpc"] = "2.0" @@ -63,9 +91,10 @@ class LspEndpoint(threading.Thread): self.send_message(method_name, kwargs, current_id) cond.wait() cond.release() - # TODO: check if error, and throw an exception - response = self.response_dict[current_id] - return response["result"] + result, error = self.response_dict[current_id] + if error: + raise lsp_structs.ResponseError(error.get("code"), error.get("message"), error.get("data")) + return result def send_notification(self, method_name, **kwargs): diff --git a/pylspclient/lsp_structs.py b/pylspclient/lsp_structs.py index 6c8e938..04ebaa3 100644 --- a/pylspclient/lsp_structs.py +++ b/pylspclient/lsp_structs.py @@ -425,3 +425,26 @@ class CompletionList(object): """ self.isIncomplete = isIncomplete self.items = [to_type(i, CompletionItem) for i in items] + +class ErrorCodes(object): + # Defined by JSON RPC + ParseError = -32700 + InvalidRequest = -32600 + MethodNotFound = -32601 + InvalidParams = -32602 + InternalError = -32603 + serverErrorStart = -32099 + serverErrorEnd = -32000 + ServerNotInitialized = -32002 + UnknownErrorCode = -32001 + + # Defined by the protocol. + RequestCancelled = -32800 + ContentModified = -32801 + +class ResponseError(Exception): + def __init__(self, code, message, data = None): + self.code = code + self.message = message + if data: + self.data = data \ No newline at end of file