some fixes
This commit is contained in:
parent
4d383b7ef8
commit
9b0b41b89e
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
7
setup.py
7
setup.py
@ -20,6 +20,10 @@ class PyTest(TestCommand):
|
|||||||
errno = pytest.main(self.pytest_args)
|
errno = pytest.main(self.pytest_args)
|
||||||
sys.exit(errno)
|
sys.exit(errno)
|
||||||
|
|
||||||
|
|
||||||
|
install_requires=["enum34;python_version<'3.4'"]
|
||||||
|
tests_require=["pytest", "pytest_mock"] + install_requires
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="pylspclient",
|
name="pylspclient",
|
||||||
version="0.0.2",
|
version="0.0.2",
|
||||||
@ -30,6 +34,7 @@ setup(
|
|||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
url="https://github.com/yeger00/pylspclient",
|
url="https://github.com/yeger00/pylspclient",
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
tests_require=["pytest", "pytest_mock"],
|
install_requires=install_requires,
|
||||||
|
tests_require=tests_require,
|
||||||
cmdclass={"test": PyTest},
|
cmdclass={"test": PyTest},
|
||||||
)
|
)
|
||||||
|
@ -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():
|
||||||
|
Loading…
Reference in New Issue
Block a user