diff --git a/README.md b/README.md index 9c2eae7..adb25cf 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,11 @@ -# pylspclient +# PyLSPClient [LSP](https://microsoft.github.io/language-server-protocol/) client implementation in Python. -[![image](https://img.shields.io/pypi/v/pylspclient.svg)](https://pypi.org/project/pylspclient/) -[![Build Status](https://travis-ci.org/yeger00/pylspclient.svg?branch=master)](https://travis-ci.org/yeger00/pylspclient) - # What is LSP? # Installation ``` -pip install pylspclient ``` # Run the tests @@ -21,4 +17,4 @@ or ``` python setup.py test -``` +``` \ No newline at end of file diff --git a/examples/capabilities.py b/examples/capabilities.py new file mode 100644 index 0000000..8cd31d2 --- /dev/null +++ b/examples/capabilities.py @@ -0,0 +1,201 @@ +# Python imports + +# Lib imports + +# Application imports + + + +class Capabilities: + data = { + "textDocument": { + "codeAction": { + "dynamicRegistration": True + }, + "codeLens": { + "dynamicRegistration": True + }, + "colorProvider": { + "dynamicRegistration": True + }, + "completion": { + "completionItem": { + "commitCharactersSupport": True, + "documentationFormat": [ + "markdown", + "plaintext" + ], + "snippetSupport": True + }, + "completionItemKind": { + "valueSet": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25 + ] + }, + "contextSupport": True, + "dynamicRegistration": True + }, + "definition": { + "dynamicRegistration": True + }, + "documentHighlight": { + "dynamicRegistration": True + }, + "documentLink": { + "dynamicRegistration": True + }, + "documentSymbol": { + "dynamicRegistration": True, + "symbolKind": { + "valueSet": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ] + } + }, + "formatting": { + "dynamicRegistration": True + }, + "hover": { + "contentFormat": [ + "markdown", + "plaintext" + ], + "dynamicRegistration": True + }, + "implementation": { + "dynamicRegistration": True + }, + "onTypeFormatting": { + "dynamicRegistration": True + }, + "publishDiagnostics": { + "relatedInformation": True + }, + "rangeFormatting": { + "dynamicRegistration": True + }, + "references": { + "dynamicRegistration": True + }, + "rename": { + "dynamicRegistration": True + }, + "signatureHelp": { + "dynamicRegistration": True, + "signatureInformation": { + "documentationFormat": [ + "markdown", + "plaintext" + ] + } + }, + "synchronization": { + "didSave": True, + "dynamicRegistration": True, + "willSave": True, + "willSaveWaitUntil": True + }, + "typeDefinition": { + "dynamicRegistration": True + } + }, + "workspace": { + "applyEdit": True, + "configuration": True, + "didChangeConfiguration": { + "dynamicRegistration": True + }, + "didChangeWatchedFiles": { + "dynamicRegistration": True + }, + "executeCommand": { + "dynamicRegistration": True + }, + "symbol": { + "dynamicRegistration": True, + "symbolKind": { + "valueSet": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ] + } + }, + "workspaceEdit": { + "documentChanges": True + }, + "workspaceFolders": True + } +} \ No newline at end of file diff --git a/examples/clangd.py b/examples/clangd.py index f0c258c..69e7894 100644 --- a/examples/clangd.py +++ b/examples/clangd.py @@ -1,7 +1,15 @@ -import pylspclient +# Python imports +import argparse import subprocess import threading -import argparse + +# Lib imports + +# Application imports +import pylspclient +from .capabilities import Capabilities + + class ReadPipe(threading.Thread): def __init__(self, pipe): @@ -11,156 +19,66 @@ class ReadPipe(threading.Thread): def run(self): line = self.pipe.readline().decode('utf-8') while line: - print(line) line = self.pipe.readline().decode('utf-8') + + if __name__ == "__main__": - parser = argparse.ArgumentParser(description='pylspclient example with clangd') - 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) + parser = argparse.ArgumentParser(description = 'PyLSPClient example with clangd') + 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.start() + json_rpc_endpoint = pylspclient.JsonRpcEndpoint(p.stdin, p.stdout) # To work with socket: sock_fd = sock.makefile() - lsp_endpoint = pylspclient.LspEndpoint(json_rpc_endpoint) - - lsp_client = pylspclient.LspClient(lsp_endpoint) - capabilities = {'textDocument': {'codeAction': {'dynamicRegistration': True}, - 'codeLens': {'dynamicRegistration': True}, - 'colorProvider': {'dynamicRegistration': True}, - 'completion': {'completionItem': {'commitCharactersSupport': True, - 'documentationFormat': ['markdown', 'plaintext'], - 'snippetSupport': True}, - 'completionItemKind': {'valueSet': [1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25]}, - 'contextSupport': True, - 'dynamicRegistration': True}, - 'definition': {'dynamicRegistration': True}, - 'documentHighlight': {'dynamicRegistration': True}, - 'documentLink': {'dynamicRegistration': True}, - 'documentSymbol': {'dynamicRegistration': True, - 'symbolKind': {'valueSet': [1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26]}}, - 'formatting': {'dynamicRegistration': True}, - 'hover': {'contentFormat': ['markdown', 'plaintext'], - 'dynamicRegistration': True}, - 'implementation': {'dynamicRegistration': True}, - 'onTypeFormatting': {'dynamicRegistration': True}, - 'publishDiagnostics': {'relatedInformation': True}, - 'rangeFormatting': {'dynamicRegistration': True}, - 'references': {'dynamicRegistration': True}, - 'rename': {'dynamicRegistration': True}, - 'signatureHelp': {'dynamicRegistration': True, - 'signatureInformation': {'documentationFormat': ['markdown', 'plaintext']}}, - 'synchronization': {'didSave': True, - 'dynamicRegistration': True, - 'willSave': True, - 'willSaveWaitUntil': True}, - 'typeDefinition': {'dynamicRegistration': True}}, - 'workspace': {'applyEdit': True, - 'configuration': True, - 'didChangeConfiguration': {'dynamicRegistration': True}, - 'didChangeWatchedFiles': {'dynamicRegistration': True}, - 'executeCommand': {'dynamicRegistration': True}, - 'symbol': {'dynamicRegistration': True, - 'symbolKind': {'valueSet': [1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26]}},'workspaceEdit': {'documentChanges': True}, - 'workspaceFolders': True}} - root_uri = 'file:///home/osboxes/projects/ctest/' + lsp_endpoint = pylspclient.LspEndpoint(json_rpc_endpoint) + lsp_client = pylspclient.LspClient(lsp_endpoint) + root_uri = 'file:///home/osboxes/projects/ctest/' 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.data, "off", workspace_folders)) print(lsp_client.initialized()) - file_path = "/home/osboxes/projects/ctest/test.c" - uri = "file://" + file_path - text = open(file_path, "r").read() + file_path = "/home/osboxes/projects/ctest/test.c" + uri = f"file://{file_path}" + text = open(file_path, "r").read() languageId = pylspclient.lsp_structs.LANGUAGE_IDENTIFIER.C - version = 1 + version = 1 + lsp_client.didOpen(pylspclient.lsp_structs.TextDocumentItem(uri, languageId, version, text)) try: 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") + print("Failed to load document symbols...") + + 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(14, 4) + ) + + lsp_client.definition( + pylspclient.lsp_structs.TextDocumentIdentifier(uri), + pylspclient.lsp_structs.Position(14, 4) + ) + + 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.definition(pylspclient.lsp_structs.TextDocumentIdentifier(uri), pylspclient.lsp_structs.Position(14, 4)) - lsp_client.signatureHelp(pylspclient.lsp_structs.TextDocumentIdentifier(uri), pylspclient.lsp_structs.Position(14, 4)) - lsp_client.definition(pylspclient.lsp_structs.TextDocumentIdentifier(uri), pylspclient.lsp_structs.Position(14, 4)) - 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.exit() + + diff --git a/examples/python-language-server.py b/examples/python-language-server.py index 72026e5..819488a 100644 --- a/examples/python-language-server.py +++ b/examples/python-language-server.py @@ -1,12 +1,20 @@ -import pylspclient +# Python imports import subprocess import threading +# Lib imports +import pylspclient + +# Application imports +from .capabilities import Capabilities + + # In order to run this example, you need to have python-language-server module installed. # See more information on the project page: https://github.com/palantir/python-language-server + class ReadPipe(threading.Thread): def __init__(self, pipe): threading.Thread.__init__(self) @@ -15,135 +23,26 @@ class ReadPipe(threading.Thread): def run(self): line = self.pipe.readline().decode('utf-8') while line: - print(line) line = self.pipe.readline().decode('utf-8') + + if __name__ == "__main__": - pyls_cmd = ["python", "-m", "pyls"] - p = subprocess.Popen(pyls_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + pyls_cmd = ["python", "-m", "pyls"] + + p = subprocess.Popen(pyls_cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE) read_pipe = ReadPipe(p.stderr) read_pipe.start() + json_rpc_endpoint = pylspclient.JsonRpcEndpoint(p.stdin, p.stdout) # To work with socket: sock_fd = sock.makefile() - lsp_endpoint = pylspclient.LspEndpoint(json_rpc_endpoint) - - lsp_client = pylspclient.LspClient(lsp_endpoint) - capabilities = {'textDocument': {'codeAction': {'dynamicRegistration': True}, - 'codeLens': {'dynamicRegistration': True}, - 'colorProvider': {'dynamicRegistration': True}, - 'completion': {'completionItem': {'commitCharactersSupport': True, - 'documentationFormat': ['markdown', 'plaintext'], - 'snippetSupport': True}, - 'completionItemKind': {'valueSet': [1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25]}, - 'contextSupport': True, - 'dynamicRegistration': True}, - 'definition': {'dynamicRegistration': True}, - 'documentHighlight': {'dynamicRegistration': True}, - 'documentLink': {'dynamicRegistration': True}, - 'documentSymbol': {'dynamicRegistration': True, - 'symbolKind': {'valueSet': [1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26]}}, - 'formatting': {'dynamicRegistration': True}, - 'hover': {'contentFormat': ['markdown', 'plaintext'], - 'dynamicRegistration': True}, - 'implementation': {'dynamicRegistration': True}, - 'onTypeFormatting': {'dynamicRegistration': True}, - 'publishDiagnostics': {'relatedInformation': True}, - 'rangeFormatting': {'dynamicRegistration': True}, - 'references': {'dynamicRegistration': True}, - 'rename': {'dynamicRegistration': True}, - 'signatureHelp': {'dynamicRegistration': True, - 'signatureInformation': {'documentationFormat': ['markdown', 'plaintext']}}, - 'synchronization': {'didSave': True, - 'dynamicRegistration': True, - 'willSave': True, - 'willSaveWaitUntil': True}, - 'typeDefinition': {'dynamicRegistration': True}}, - 'workspace': {'applyEdit': True, - 'configuration': True, - 'didChangeConfiguration': {'dynamicRegistration': True}, - 'didChangeWatchedFiles': {'dynamicRegistration': True}, - 'executeCommand': {'dynamicRegistration': True}, - 'symbol': {'dynamicRegistration': True, - 'symbolKind': {'valueSet': [1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26]}},'workspaceEdit': {'documentChanges': True}, - 'workspaceFolders': True}} - root_uri = 'file:///path/to/python/project' + lsp_endpoint = pylspclient.LspEndpoint(json_rpc_endpoint) + lsp_client = pylspclient.LspClient(lsp_endpoint) + root_uri = 'file:///path/to/python/project' 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.data, "off", workspace_folders)) print(lsp_client.initialized()) lsp_client.shutdown() - lsp_client.exit() + lsp_client.exit() \ No newline at end of file diff --git a/setup.py b/setup.py index ed6ca96..580bc90 100644 --- a/setup.py +++ b/setup.py @@ -22,15 +22,15 @@ class PyTest(TestCommand): setup( - name="pylspclient", - version="0.0.2", - author="Avi Yeger", - author_email="yeger00@gmail.com", - description="LSP client implementation in Python", + name="PyLSPClient", + version="0.0.1", + author="ITDominator", + author_email="1itdominator@gmail.com", + description="Python LSP client implementation cloned from Avi Yeger's effoerts", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/yeger00/pylspclient", packages=find_packages(), tests_require=["pytest", "pytest_mock"], cmdclass={"test": PyTest}, -) +) \ No newline at end of file diff --git a/tests/test_json_rpc_endpoint.py b/tests/test_json_rpc_endpoint.py index 4999c4d..43c7692 100644 --- a/tests/test_json_rpc_endpoint.py +++ b/tests/test_json_rpc_endpoint.py @@ -1,7 +1,14 @@ +# Python imports import os -import pylspclient import pytest +# Lib imports +import pylspclient + +# Application imports + + + 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_num": 1, "key_str": "some_string"}'.encode("utf-8") @@ -9,12 +16,14 @@ JSON_RPC_RESULT_LIST = [ def test_send_sanity(): - pipein, pipeout = os.pipe() - pipein = os.fdopen(pipein, "rb") - pipeout = os.fdopen(pipeout, "wb") + pipein, pipeout = os.pipe() + pipein = os.fdopen(pipein, "rb") + pipeout = os.fdopen(pipeout, "wb") + json_rpc_endpoint = pylspclient.JsonRpcEndpoint(pipeout, None) json_rpc_endpoint.send_request({"key_num":1, "key_str":"some_string"}) - result = pipein.read(len(JSON_RPC_RESULT_LIST[0])) + result = pipein.read(len(JSON_RPC_RESULT_LIST[0])) + assert(result in JSON_RPC_RESULT_LIST) @@ -24,57 +33,66 @@ def test_send_class(): self.key_num = value_num self.key_str = value_str - pipein, pipeout = os.pipe() - pipein = os.fdopen(pipein, "rb") - pipeout = os.fdopen(pipeout, "wb") + pipein, pipeout = os.pipe() + pipein = os.fdopen(pipein, "rb") + pipeout = os.fdopen(pipeout, "wb") + json_rpc_endpoint = pylspclient.JsonRpcEndpoint(pipeout, None) json_rpc_endpoint.send_request(RpcClass(1, "some_string")) - result = pipein.read(len(JSON_RPC_RESULT_LIST[0])) + result = pipein.read(len(JSON_RPC_RESULT_LIST[0])) + assert(result in JSON_RPC_RESULT_LIST) def test_recv_sanity(): - pipein, pipeout = os.pipe() - pipein = os.fdopen(pipein, "rb") - pipeout = os.fdopen(pipeout, "wb") + pipein, pipeout = os.pipe() + pipein = os.fdopen(pipein, "rb") + pipeout = os.fdopen(pipeout, "wb") + json_rpc_endpoint = pylspclient.JsonRpcEndpoint(None, pipein) pipeout.write('Content-Length: 40\r\n\r\n{"key_str": "some_string", "key_num": 1}'.encode("utf-8")) pipeout.flush() - result = json_rpc_endpoint.recv_response() + result = json_rpc_endpoint.recv_response() + assert({"key_num":1, "key_str":"some_string"} == result) def test_recv_wrong_header(): - pipein, pipeout = os.pipe() - pipein = os.fdopen(pipein, "rb") - pipeout = os.fdopen(pipeout, "wb") + pipein, pipeout = os.pipe() + pipein = os.fdopen(pipein, "rb") + pipeout = os.fdopen(pipeout, "wb") + 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.flush() + with pytest.raises(pylspclient.lsp_structs.ResponseError): result = json_rpc_endpoint.recv_response() - print("should never get here", result) + print("Shouldn't' ever get here...", result) def test_recv_missing_size(): - pipein, pipeout = os.pipe() - pipein = os.fdopen(pipein, "rb") - pipeout = os.fdopen(pipeout, "wb") + 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) + print("Shouldn't' ever get here...", result) def test_recv_close_pipe(): - pipein, pipeout = os.pipe() - pipein = os.fdopen(pipein, "rb") - pipeout = os.fdopen(pipeout, "wb") + pipein, pipeout = os.pipe() + pipein = os.fdopen(pipein, "rb") + pipeout = os.fdopen(pipeout, "wb") + json_rpc_endpoint = pylspclient.JsonRpcEndpoint(None, pipein) pipeout.close() - result = json_rpc_endpoint.recv_response() + result = json_rpc_endpoint.recv_response() + assert(result is None) -