Merge pull request #4 from yeger00/small-fixes

Small fixes
This commit is contained in:
Yeger 2019-06-09 23:48:47 +03:00 committed by GitHub
commit 3ee6fd666c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 86 additions and 58 deletions

View File

@ -9,8 +9,8 @@ python:
# - "3.6-dev" # 3.6 development branch # - "3.6-dev" # 3.6 development branch
# - "3.7-dev" # 3.7 development branch # - "3.7-dev" # 3.7 development branch
# command to install dependencies # command to install dependencies
#install: install:
# - pip install -r requirements.txt - pip install -r requirements.txt
# command to run tests # command to run tests
script: script:
- pytest - pytest

View File

@ -1,7 +1,7 @@
import pylspclient import pylspclient
import subprocess import subprocess
import threading import threading
import argparse
class ReadPipe(threading.Thread): class ReadPipe(threading.Thread):
def __init__(self, pipe): def __init__(self, pipe):
@ -15,8 +15,11 @@ class ReadPipe(threading.Thread):
line = self.pipe.readline().decode('utf-8') line = self.pipe.readline().decode('utf-8')
if __name__ == "__main__": if __name__ == "__main__":
clangd_path = "/usr/bin/clangd-6.0" parser = argparse.ArgumentParser(description='pylspclient example with clangd')
p = subprocess.Popen(clangd_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) parser.add_argument('clangd_path', type=str, default="/usr/bin/clangd-6.0",
help='the clangd path', nargs="?")
args = parser.parse_args()
p = subprocess.Popen([args.clangd_path], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
read_pipe = ReadPipe(p.stderr) read_pipe = ReadPipe(p.stderr)
read_pipe.start() read_pipe.start()
json_rpc_endpoint = pylspclient.JsonRpcEndpoint(p.stdin, p.stdout) json_rpc_endpoint = pylspclient.JsonRpcEndpoint(p.stdin, p.stdout)
@ -136,7 +139,7 @@ if __name__ == "__main__":
25, 25,
26]}},'workspaceEdit': {'documentChanges': True}, 26]}},'workspaceEdit': {'documentChanges': True},
'workspaceFolders': True}} 'workspaceFolders': True}}
root_uri = 'file:///home/osboxes/projects/ctest' root_uri = 'file:///home/osboxes/projects/ctest/'
workspace_folders = [{'name': 'python-lsp', 'uri': root_uri}] workspace_folders = [{'name': 'python-lsp', 'uri': root_uri}]
print(lsp_client.initialize(p.pid, None, root_uri, None, capabilities, "off", workspace_folders)) print(lsp_client.initialize(p.pid, None, root_uri, None, capabilities, "off", workspace_folders))
print(lsp_client.initialized()) print(lsp_client.initialized())
@ -147,11 +150,16 @@ if __name__ == "__main__":
languageId = pylspclient.lsp_structs.LANGUAGE_IDENTIFIER.C languageId = pylspclient.lsp_structs.LANGUAGE_IDENTIFIER.C
version = 1 version = 1
lsp_client.didOpen(pylspclient.lsp_structs.TextDocumentItem(uri, languageId, version, text)) lsp_client.didOpen(pylspclient.lsp_structs.TextDocumentItem(uri, languageId, version, text))
# documentSymbol is supported from version 8. try:
#lsp_client.documentSymbol(pylspclient.lsp_structs.TextDocumentIdentifier(uri)) symbols = lsp_client.documentSymbol(pylspclient.lsp_structs.TextDocumentIdentifier(uri))
for symbol in symbols:
print(symbol.name)
except pylspclient.lsp_structs.ResponseError:
# documentSymbol is supported from version 8.
print("Failed to document symbols")
lsp_client.definition(pylspclient.lsp_structs.TextDocumentIdentifier(uri), pylspclient.lsp_structs.Position(15, 4)) lsp_client.definition(pylspclient.lsp_structs.TextDocumentIdentifier(uri), pylspclient.lsp_structs.Position(14, 4))
lsp_client.signatureHelp(pylspclient.lsp_structs.TextDocumentIdentifier(uri), pylspclient.lsp_structs.Position(15, 4)) lsp_client.signatureHelp(pylspclient.lsp_structs.TextDocumentIdentifier(uri), pylspclient.lsp_structs.Position(14, 4))
lsp_client.completion(pylspclient.lsp_structs.TextDocumentIdentifier(uri), pylspclient.lsp_structs.Position(15, 4), pylspclient.lsp_structs.CompletionContext(pylspclient.lsp_structs.CompletionTriggerKind.Invoked)) lsp_client.completion(pylspclient.lsp_structs.TextDocumentIdentifier(uri), pylspclient.lsp_structs.Position(14, 4), pylspclient.lsp_structs.CompletionContext(pylspclient.lsp_structs.CompletionTriggerKind.Invoked))
lsp_client.shutdown() lsp_client.shutdown()
lsp_client.exit() lsp_client.exit()

View File

@ -5,15 +5,18 @@ from pylspclient import lsp_structs
import threading import threading
JSON_RPC_REQ_FORMAT = "Content-Length: {json_string_len}\r\n\r\n{json_string}" JSON_RPC_REQ_FORMAT = "Content-Length: {json_string_len}\r\n\r\n{json_string}"
JSON_RPC_RES_REGEX = "Content-Length: ([0-9]*)\r\n" LEN_HEADER = "Content-Length: "
TYPE_HEADER = "Content-Type: "
# TODO: add content-type # TODO: add content-type
class MyEncoder(json.JSONEncoder): class MyEncoder(json.JSONEncoder):
""" """
Encodes an object in JSON Encodes an object in JSON
""" """
def default(self, o): def default(self, o): # pylint: disable=E0202
return o.__dict__ return o.__dict__
@ -59,20 +62,33 @@ class JsonRpcEndpoint(object):
:return: a message :return: a message
''' '''
with self.read_lock: with self.read_lock:
line = self.stdout.readline() message_size = None
if not line: while True:
return None #read header
line = line.decode("utf-8") line = self.stdout.readline()
# TODO: handle content type as well. if not line:
match = re.match(JSON_RPC_RES_REGEX, line) # server quit
if match is None or not match.groups(): return None
raise RuntimeError("Bad header: " + line) line = line.decode("utf-8")
size = int(match.groups()[0]) if not line.endswith("\r\n"):
line = self.stdout.readline() raise lsp_structs.ResponseError(lsp_structs.ErrorCodes.ParseError, "Bad header: missing newline")
if not line: #remove the "\r\n"
return None line = line[:-2]
line = line.decode("utf-8") if line == "":
if line != "\r\n": # done with the headers
raise RuntimeError("Bad header: missing newline") break
jsonrpc_res = self.stdout.read(size).decode("utf-8") elif line.startswith(LEN_HEADER):
line = line[len(LEN_HEADER):]
if not line.isdigit():
raise lsp_structs.ResponseError(lsp_structs.ErrorCodes.ParseError, "Bad header: size is not int")
message_size = int(line)
elif line.startswith(TYPE_HEADER):
# nothing todo with type for now.
pass
else:
raise lsp_structs.ResponseError(lsp_structs.ErrorCodes.ParseError, "Bad header: unkown header")
if not message_size:
raise lsp_structs.ResponseError(lsp_structs.ErrorCodes.ParseError, "Bad header: missing size")
jsonrpc_res = self.stdout.read(message_size).decode("utf-8")
return json.loads(jsonrpc_res) return json.loads(jsonrpc_res)

View File

@ -46,15 +46,16 @@ class LspEndpoint(threading.Thread):
if rpc_id: if rpc_id:
# a call for method # a call for method
if method not in self.method_callbacks: if method not in self.method_callbacks:
raise lsp_structs.ResponseError("Method not found: {method}".format(method=method), lsp_structs.ErrorCodes.MethodNotFound) raise lsp_structs.ResponseError(lsp_structs.ErrorCodes.MethodNotFound, "Method not found: {method}".format(method=method))
result = self.method_callbacks[method](params) result = self.method_callbacks[method](params)
self.send_response(rpc_id, result, None) self.send_response(rpc_id, result, None)
else: else:
# a call for notify # a call for notify
if method not in self.notify_callbacks: if method not in self.notify_callbacks:
raise lsp_structs.ResponseError("Method not found: {method}".format(method=method), lsp_structs.ErrorCodes.MethodNotFound) # Have nothing to do with this.
print("Notify method not found: {method}.".format(method=method))
self.notify_callbacks[method](params) else:
self.notify_callbacks[method](params)
else: else:
self.handle_result(rpc_id, result, error) self.handle_result(rpc_id, result, error)
except lsp_structs.ResponseError as e: except lsp_structs.ResponseError as e:

View File

@ -1,3 +1,6 @@
import enum
def to_type(o, new_type): def to_type(o, new_type):
''' '''
Helper funciton that receives an object or a dict and convert it to a new given type. Helper funciton that receives an object or a dict and convert it to a new given type.
@ -149,7 +152,7 @@ class TextDocumentPositionParams(object):
self.position = position self.position = position
class LANGUAGE_IDENTIFIER: class LANGUAGE_IDENTIFIER(object):
BAT="bat" BAT="bat"
BIBTEX="bibtex" BIBTEX="bibtex"
CLOJURE="clojure" CLOJURE="clojure"
@ -201,7 +204,7 @@ class LANGUAGE_IDENTIFIER:
YAML="yaml" YAML="yaml"
class SymbolKind(object): class SymbolKind(enum.Enum):
File = 1 File = 1
Module = 2 Module = 2
Namespace = 3 Namespace = 3
@ -256,7 +259,7 @@ class SymbolInformation(object):
:param bool deprecated: Indicates if this symbol is deprecated. :param bool deprecated: Indicates if this symbol is deprecated.
""" """
self.name = name self.name = name
self.kind = kind self.kind = SymbolKind(kind)
self.deprecated = deprecated self.deprecated = deprecated
self.location = to_type(location, Location) self.location = to_type(location, Location)
self.containerName = containerName self.containerName = containerName
@ -426,7 +429,7 @@ class CompletionList(object):
self.isIncomplete = isIncomplete self.isIncomplete = isIncomplete
self.items = [to_type(i, CompletionItem) for i in items] self.items = [to_type(i, CompletionItem) for i in items]
class ErrorCodes(object): class ErrorCodes(enum.Enum):
# Defined by JSON RPC # Defined by JSON RPC
ParseError = -32700 ParseError = -32700
InvalidRequest = -32600 InvalidRequest = -32600
@ -447,4 +450,4 @@ class ResponseError(Exception):
self.code = code self.code = code
self.message = message self.message = message
if data: if data:
self.data = data self.data = data

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
enum ; python_version < '3.4'

View File

@ -20,6 +20,7 @@ class PyTest(TestCommand):
errno = pytest.main(self.pytest_args) errno = pytest.main(self.pytest_args)
sys.exit(errno) sys.exit(errno)
setup( setup(
name="pylspclient", name="pylspclient",
version="0.0.2", version="0.0.2",

View File

@ -1,29 +1,13 @@
import os import os
#from pytest_mock import mocker
import pytest
import pylspclient import pylspclient
import pytest
class StdinMock(object):
def write(self, s):
pass
def flush(self):
pass
class StdoutMock(object):
def readline(self):
pass
def read(self):
pass
JSON_RPC_RESULT_LIST = [ JSON_RPC_RESULT_LIST = [
'Content-Length: 40\r\n\r\n{"key_str": "some_string", "key_num": 1}'.encode("utf-8"), 'Content-Length: 40\r\n\r\n{"key_str": "some_string", "key_num": 1}'.encode("utf-8"),
'Content-Length: 40\r\n\r\n{"key_num": 1, "key_str": "some_string"}'.encode("utf-8") 'Content-Length: 40\r\n\r\n{"key_num": 1, "key_str": "some_string"}'.encode("utf-8")
] ]
def test_send_sanity(): def test_send_sanity():
pipein, pipeout = os.pipe() pipein, pipeout = os.pipe()
pipein = os.fdopen(pipein, "rb") pipein = os.fdopen(pipein, "rb")
@ -67,8 +51,21 @@ def test_recv_wrong_header():
json_rpc_endpoint = pylspclient.JsonRpcEndpoint(None, pipein) json_rpc_endpoint = pylspclient.JsonRpcEndpoint(None, pipein)
pipeout.write('Contentength: 40\r\n\r\n{"key_str": "some_string", "key_num": 1}'.encode("utf-8")) pipeout.write('Contentength: 40\r\n\r\n{"key_str": "some_string", "key_num": 1}'.encode("utf-8"))
pipeout.flush() pipeout.flush()
with pytest.raises(RuntimeError): with pytest.raises(pylspclient.lsp_structs.ResponseError):
result = json_rpc_endpoint.recv_response() result = json_rpc_endpoint.recv_response()
print("should never get here", result)
def test_recv_missing_size():
pipein, pipeout = os.pipe()
pipein = os.fdopen(pipein, "rb")
pipeout = os.fdopen(pipeout, "wb")
json_rpc_endpoint = pylspclient.JsonRpcEndpoint(None, pipein)
pipeout.write('Content-Type: 40\r\n\r\n{"key_str": "some_string", "key_num": 1}'.encode("utf-8"))
pipeout.flush()
with pytest.raises(pylspclient.lsp_structs.ResponseError):
result = json_rpc_endpoint.recv_response()
print("should never get here", result)
def test_recv_close_pipe(): def test_recv_close_pipe():

View File

@ -2,5 +2,6 @@
envlist = py27,py36 envlist = py27,py36
[testenv] [testenv]
deps=-r{toxinidir}/requirements.txt
commands = commands =
python setup.py test python setup.py test