Skip to content

Commit 8960c65

Browse files
authored
Add examples (palantir#8)
1 parent 4638538 commit 8960c65

File tree

3 files changed

+139
-2
lines changed

3 files changed

+139
-2
lines changed

README.rst

+12-2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,18 @@ Installation
2020

2121
``pip install -U python-jsonrpc-server``
2222

23-
Usage
24-
-----
23+
Examples
24+
--------
2525

26+
The examples directory contains two examples of running language servers over websockets. ``examples/langserver.py`` shows how to run a language server in-memory. ``examples/langserver_ext.py`` shows how to run a subprocess language server, in this case the `Python Language Server`_.
27+
28+
Start by installing `tornado` and `python-language-server`
29+
30+
``pip install python-language-server[all] tornado``
31+
32+
Then running `python examples/langserver.py` or `python examples/langserver_ext.py` will host a websocket on ``ws://localhost:3000/python``.
33+
34+
To setup a client, you can use the examples from `Monaco Language Client`_.
2635

2736
Development
2837
-----------
@@ -39,3 +48,4 @@ This project is made available under the MIT License.
3948
.. _JSON RPC 2.0: http://www.jsonrpc.org/specification
4049
.. _Python Language Server: https://github.com/palantir/python-language-server
4150
.. _concurrent.futures backport: https://github.com/agronholm/pythonfutures
51+
.. _Monaco Language Client: https://github.com/TypeFox/monaco-languageclient

examples/langserver.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import json
2+
import logging
3+
4+
from tornado import web, ioloop, websocket
5+
6+
from jsonrpc import dispatchers, endpoint
7+
8+
log = logging.getLogger(__name__)
9+
10+
11+
class LanguageServer(dispatchers.MethodDispatcher):
12+
"""Implement a JSON RPC method dispatcher for the language server protocol."""
13+
14+
def __init__(self):
15+
# Endpoint is lazily set after construction
16+
self.endpoint = None
17+
18+
def m_initialize(self, rootUri=None, **kwargs):
19+
log.info("Got initialize params: %s", kwargs)
20+
return {"capabilities": {
21+
"textDocumentSync": {
22+
"openClose": True,
23+
}
24+
}}
25+
26+
def m_text_document__did_open(self, textDocument=None, **_kwargs):
27+
log.info("Opened text document %s", textDocument)
28+
self.endpoint.notify('textDocument/publishDiagnostics', {
29+
'uri': textDocument['uri'],
30+
'diagnostics': [{
31+
'range': {
32+
'start': {'line': 0, 'character': 0},
33+
'end': {'line': 1, 'character': 0},
34+
},
35+
'message': 'Some very bad Python code',
36+
'severity': 1 # DiagnosticSeverity.Error
37+
}]
38+
})
39+
40+
41+
class LanguageServerWebSocketHandler(websocket.WebSocketHandler):
42+
"""Setup tornado websocket handler to host language server."""
43+
44+
def __init__(self, *args, **kwargs):
45+
# Create an instance of the language server used to dispatch JSON RPC methods
46+
langserver = LanguageServer()
47+
48+
# Setup an endpoint that dispatches to the ls, and writes server->client messages
49+
# back to the client websocket
50+
self.endpoint = endpoint.Endpoint(langserver, lambda msg: self.write_message(json.dumps(msg)))
51+
52+
# Give the language server a handle to the endpoint so it can send JSON RPC
53+
# notifications and requests.
54+
langserver.endpoint = self.endpoint
55+
56+
super(LanguageServerWebSocketHandler, self).__init__(*args, **kwargs)
57+
58+
def on_message(self, message):
59+
"""Forward client->server messages to the endpoint."""
60+
self.endpoint.consume(json.loads(message))
61+
62+
def check_origin(self, origin):
63+
return True
64+
65+
66+
if __name__ == "__main__":
67+
app = web.Application([
68+
(r"/python", LanguageServerWebSocketHandler),
69+
])
70+
app.listen(3000, address='127.0.0.1')
71+
ioloop.IOLoop.current().start()

examples/langserver_ext.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import json
2+
import logging
3+
import subprocess
4+
import threading
5+
6+
from tornado import ioloop, process, web, websocket
7+
8+
from jsonrpc import streams
9+
10+
log = logging.getLogger(__name__)
11+
12+
13+
class LanguageServerWebSocketHandler(websocket.WebSocketHandler):
14+
"""Setup tornado websocket handler to host an external language server."""
15+
16+
writer = None
17+
18+
def open(self, *args, **kwargs):
19+
log.info("Spawning pyls subprocess")
20+
21+
# Create an instance of the language server
22+
proc = process.Subprocess(
23+
['pyls', '-v'],
24+
stdin=subprocess.PIPE,
25+
stdout=subprocess.PIPE
26+
)
27+
28+
# Create a writer that formats json messages with the correct LSP headers
29+
self.writer = streams.JsonRpcStreamWriter(proc.stdin)
30+
31+
# Create a reader for consuming stdout of the language server. We need to
32+
# consume this in another thread
33+
def consume():
34+
# Start a tornado IOLoop for reading/writing to the process in this thread
35+
ioloop.IOLoop()
36+
reader = streams.JsonRpcStreamReader(proc.stdout)
37+
reader.listen(lambda msg: self.write_message(json.dumps(msg)))
38+
39+
thread = threading.Thread(target=consume)
40+
thread.daemon = True
41+
thread.start()
42+
43+
def on_message(self, message):
44+
"""Forward client->server messages to the endpoint."""
45+
self.writer.write(json.loads(message))
46+
47+
def check_origin(self, origin):
48+
return True
49+
50+
51+
if __name__ == "__main__":
52+
app = web.Application([
53+
(r"/python", LanguageServerWebSocketHandler),
54+
])
55+
app.listen(3000, address='127.0.0.1')
56+
ioloop.IOLoop.current().start()

0 commit comments

Comments
 (0)