-
Notifications
You must be signed in to change notification settings - Fork 16
langserver.py: Update to use jsonrpc #32
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,4 @@ coverage: | |
project: | ||
default: | ||
enabled: true | ||
target: 83% | ||
target: 95% |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,62 +3,52 @@ | |
import socketserver | ||
import traceback | ||
|
||
from .jsonrpc import JSONRPC2Connection, ReadWriter, TCPReadWriter | ||
from pyls.jsonrpc.endpoint import Endpoint | ||
from pyls.jsonrpc.dispatchers import MethodDispatcher | ||
from pyls.jsonrpc.streams import JsonRpcStreamReader | ||
from pyls.jsonrpc.streams import JsonRpcStreamWriter | ||
from coala_utils.decorators import enforce_signature | ||
from .log import log | ||
from .coalashim import run_coala_with_specific_file | ||
from .uri import path_from_uri | ||
from .diagnostic import output_to_diagnostics | ||
|
||
|
||
class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): | ||
pass | ||
class _StreamHandlerWrapper(socketserver.StreamRequestHandler, object): | ||
""" | ||
A wrapper class that is used to construct a custom handler class. | ||
""" | ||
|
||
delegate = None | ||
|
||
class LangserverTCPTransport(socketserver.StreamRequestHandler): | ||
def setup(self): | ||
super(_StreamHandlerWrapper, self).setup() | ||
self.delegate = self.DELEGATE_CLASS(self.rfile, self.wfile) | ||
|
||
def handle(self): | ||
s = LangServer(conn=TCPReadWriter(self.rfile, self.wfile)) | ||
try: | ||
s.listen() | ||
except Exception as e: | ||
tb = traceback.format_exc() | ||
log('ERROR: {} {}'.format(e, tb)) | ||
self.delegate.start() | ||
|
||
|
||
class LangServer(JSONRPC2Connection): | ||
class LangServer(MethodDispatcher): | ||
""" | ||
Language server for coala base on JSON RPC. | ||
""" | ||
|
||
def __init__(self, conn=None): | ||
super().__init__(conn=conn) | ||
def __init__(self, rx, tx): | ||
self.root_path = None | ||
self._jsonrpc_stream_reader = JsonRpcStreamReader(rx) | ||
self._jsonrpc_stream_writer = JsonRpcStreamWriter(tx) | ||
self._endpoint = Endpoint(self, self._jsonrpc_stream_writer.write) | ||
self._dispatchers = [] | ||
self._shutdown = False | ||
|
||
def handle(self, _id, request): | ||
""" | ||
Handle the request from language client. | ||
""" | ||
log('REQUEST: ', request) | ||
resp = None | ||
|
||
if request['method'] == 'initialize': | ||
resp = self.serve_initialize(request) | ||
# TODO: Support did_change and did_change_watched_files. | ||
# elif request["method"] == "textDocument/didChange": | ||
# resp = self.serve_change(request) | ||
# elif request["method"] == "workspace/didChangeWatchedFiles": | ||
# resp = self.serve_did_change_watched_files(request) | ||
elif request['method'] == 'textDocument/didSave': | ||
self.serve_did_save(request) | ||
|
||
if resp is not None: | ||
self.write_response(request['id'], resp) | ||
|
||
def serve_initialize(self, request): | ||
def start(self): | ||
self._jsonrpc_stream_reader.listen(self._endpoint.consume) | ||
|
||
def m_initialize(self, **params): | ||
""" | ||
Serve for the initialization request. | ||
""" | ||
params = request['params'] | ||
# Notice that the root_path could be None. | ||
if 'rootUri' in params: | ||
self.root_path = path_from_uri(params['rootUri']) | ||
|
@@ -70,17 +60,19 @@ def serve_initialize(self, request): | |
} | ||
} | ||
|
||
def serve_did_save(self, request): | ||
def m_text_document__did_save(self, **params): | ||
""" | ||
Serve for did_change request. | ||
""" | ||
params = request['params'] | ||
uri = params['textDocument']['uri'] | ||
path = path_from_uri(uri) | ||
diagnostics = output_to_diagnostics( | ||
run_coala_with_specific_file(self.root_path, path)) | ||
self.send_diagnostics(path, diagnostics) | ||
|
||
def m_shutdown(self, **_kwargs): | ||
self._shutdown = True | ||
|
||
# TODO: Support did_change and did_change_watched_files. | ||
# def serve_change(self, request): | ||
# '""Serve for the request of documentation changed.""' | ||
|
@@ -110,7 +102,38 @@ def send_diagnostics(self, path, diagnostics): | |
'uri': 'file://{0}'.format(path), | ||
'diagnostics': _diagnostics, | ||
} | ||
self.send_notification('textDocument/publishDiagnostics', params) | ||
self._endpoint.notify('textDocument/publishDiagnostics', params=params) | ||
|
||
|
||
@enforce_signature | ||
def start_tcp_lang_server(handler_class: LangServer, bind_addr, port): | ||
# Construct a custom wrapper class around the user's handler_class | ||
wrapper_class = type( | ||
handler_class.__name__ + 'Handler', | ||
(_StreamHandlerWrapper,), | ||
{'DELEGATE_CLASS': handler_class}, | ||
) | ||
|
||
try: | ||
server = socketserver.TCPServer((bind_addr, port), wrapper_class) | ||
except Exception as e: | ||
log('Fatal Exception: {}'.format(e)) | ||
sys.exit(1) | ||
|
||
log('Serving {} on ({}, {})'.format( | ||
handler_class.__name__, bind_addr, port)) | ||
try: | ||
server.serve_forever() | ||
finally: | ||
log('Shutting down') | ||
server.server_close() | ||
|
||
|
||
@enforce_signature | ||
def start_io_lang_server(handler_class: LangServer, rstream, wstream): | ||
log('Starting {} IO language server'.format(handler_class.__name__)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the reasons mentioned in the previous change request and maintaining uniformity I think I'll have to retain this log and remove the other redundant log call. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok. |
||
server = handler_class(rstream, wstream) | ||
server.start() | ||
|
||
|
||
def main(): | ||
|
@@ -123,18 +146,10 @@ def main(): | |
args = parser.parse_args() | ||
|
||
if args.mode == 'stdio': | ||
log('Reading on stdin, writing on stdout') | ||
s = LangServer(conn=ReadWriter(sys.stdin, sys.stdout)) | ||
s.listen() | ||
start_io_lang_server(LangServer, sys.stdin.buffer, sys.stdout.buffer) | ||
elif args.mode == 'tcp': | ||
host, addr = '0.0.0.0', args.addr | ||
log('Accepting TCP connections on {}:{}'.format(host, addr)) | ||
ThreadingTCPServer.allow_reuse_address = True | ||
s = ThreadingTCPServer((host, addr), LangserverTCPTransport) | ||
try: | ||
s.serve_forever() | ||
finally: | ||
s.shutdown() | ||
start_tcp_lang_server(LangServer, host, addr) | ||
|
||
|
||
if __name__ == '__main__': | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
coala>=0.10.0.dev20170213201648 | ||
typing>=3.5.3.0 | ||
coala-bears>=0.10.0.dev20170215041744 | ||
python-language-server~=0.18.0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,20 @@ | ||
Feature: jsonrpc module | ||
jsonrpc is a module of language-server. | ||
|
||
Scenario: Test ReadWriter | ||
Given the string | ||
When I write it to ReadWriter | ||
Then it should read from ReadWriter | ||
Scenario: Test JsonRpcStreamWriter and JsonRpcStreamReader | ||
Given the message | ||
When I write it to JsonRpcStreamWriter | ||
Then it should read from JsonRpcStreamReader | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the code is from Python Language Server, do we need to test it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is an internal module of pyls, we should check for some form of primary breaking changes in API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SGTM |
||
|
||
Scenario: Test ReadWriter | ||
Given the string | ||
When I write it to ReadWriter | ||
Then it should readline from ReadWriter | ||
Scenario: Test notification and disptacher | ||
Given a notification type rpc request | ||
When I send rpc request using JsonRpcStreamWriter | ||
Then it should invoke the notification consumer with args | ||
|
||
Scenario: Test TCPReadWriter | ||
Given the string | ||
When I write it to TCPReadWriter | ||
Then it should read from TCPReadWriter | ||
|
||
Scenario: Test TCPReadWriter | ||
Given the string | ||
When I write it to TCPReadWriter | ||
Then it should readline from TCPReadWriter | ||
|
||
Scenario: Test send_notification and read_message | ||
Given the JSONRPC2Connection instance | ||
When I write a notification to the JSONRPC2Connection | ||
Then it should return the notification from JSONRPC2Connection | ||
|
||
Scenario: Test write_response | ||
Given the JSONRPC2Connection instance | ||
When I write a response to the JSONRPC2Connection | ||
Then it should return the response from JSONRPC2Connection | ||
Scenario: Test rpc request and response | ||
Given a request type rpc request | ||
When I send rpc request using JsonRpcStreamWriter | ||
Then it should invoke consumer and return response | ||
|
||
# TODO: block until we have generantee the unique request. | ||
# Scenario: Test send_request | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You do not use the preferred quotation marks.
Origin: QuotesBear, Section:
python
.The issue can be fixed by applying the following patch: