8000 Custom method dispatchers (#224) · python-lsp/python-lsp-server@fe668a5 · GitHub
[go: up one dir, main page]

Skip to content

Commit fe668a5

Browse files
authored
Custom method dispatchers (#224)
1 parent 0457426 commit fe668a5

File tree

5 files changed

+75
-37
lines changed

5 files changed

+75
-37
lines changed

pyls/dispatcher.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright 2017 Palantir Technologies, Inc.
2+
import re
3+
4+
_RE_FIRST_CAP = re.compile('(.)([A-Z][a-z]+)')
5+
_RE_ALL_CAP = re.compile('([a-z0-9])([A-Z])')
6+
7+
8+
class JSONRPCMethodDispatcher(object):
9+
"""JSON RPC method dispatcher that calls methods on itself with params."""
10+
11+
def __getitem__(self, item):
12+
"""The jsonrpc dispatcher uses getitem to retrieve the RPC method implementation."""
13+
method_name = "m_" + _method_to_string(item)
14+
if not hasattr(self, method_name):
15+
raise KeyError("Cannot find method %s" % method_name)
16+
func = getattr(self, method_name)
17+
18+
def wrapped(*args, **kwargs):
19+
return func(*args, **kwargs)
20+
21+
return wrapped
22+
23+
24+
def _method_to_string(method):
25+
return _camel_to_underscore(method.replace("/", "__").replace("$", ""))
26+
27+
28+
def _camel_to_underscore(string):
29+
s1 = _RE_FIRST_CAP.sub(r'\1_\2', string)
30+
return _RE_ALL_CAP.sub(r'\1_\2', s1).lower()

pyls/hookspecs.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ def pyls_definitions(config, workspace, document, position):
3232
pass
3333

3434

35+
@hookspec
36+
def pyls_dispatchers(config, workspace):
37+
pass
38+
39+
3540
@hookspec
3641
def pyls_document_did_open(config, workspace, document):
3742
pass

pyls/language_server.py

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Copyright 2017 Palantir Technologies, Inc.
22
import logging
3-
import re
43
import socketserver
5-
from . import uris
4+
from . import dispatcher, uris
65
from .server import JSONRPCServer
76

87
log = logging.getLogger(__name__)
@@ -50,26 +49,7 @@ def start_io_lang_server(rfile, wfile, handler_class):
5049
server.handle()
5150

5251

53-
class MethodJSONRPCServer(JSONRPCServer):
54-
"""JSONRPCServer that calls methods on itself with params."""
55-
56-
def __getitem__(self, item):
57-
"""The jsonrpc dispatcher uses getitem to retrieve the RPC method implementation."""
58-
method_name = "m_" + _method_to_string(item)
59-
if not hasattr(self, method_name):
60-
raise KeyError("Cannot find method %s" % method_name)
61-
func = getattr(self, method_name)
62-
63-
def wrapped(*args, **kwargs):
64-
try:
65-
return func(*args, **kwargs)
66-
except:
67-
log.exception("CAUGHT")
68-
raise
69-
return wrapped
70-
71-
72-
class LanguageServer(MethodJSONRPCServer):
52+
class LanguageServer(dispatcher.JSONRPCMethodDispatcher, JSONRPCServer):
7353
""" Implementation of the Microsoft VSCode Language Server Protocol
7454
https://github.com/Microsoft/language-server-protocol/blob/master/versions/protocol-1-x.md
7555
"""
@@ -113,18 +93,3 @@ def m_shutdown(self, **_kwargs):
11393

11494
def m_exit(self, **_kwargs):
11595
self.exit()
116-
117-
118-
_RE_FIRST_CAP = re.compile('(.)([A-Z][a-z]+)')
119-
_RE_ALL_CAP = re.compile('([a-z0-9])([A-Z])')
120-
121-
122-
def _method_to_string(method):
123-
return _camel_to_underscore(
124-
method.replace("/", "__").replace("$", "")
125-
)
126-
127-
128-
def _camel_to_underscore(string):
129-
s1 = _RE_FIRST_CAP.sub(r'\1_\2', string)
130-
return _RE_ALL_CAP.sub(r'\1_\2', s1).lower()

pyls/python_ls.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,24 @@ class PythonLanguageServer(LanguageServer):
1818
workspace = None
1919
config = None
2020

21+
# Set of method dispatchers to query
22+
_dispatchers = []
23+
2124
_pool = multiprocessing.Pool(PLUGGY_RACE_POOL_SIZE)
2225

26+
def __getitem__(self, item):
27+
"""Override the method dispatcher to farm out any unknown messages to our plugins."""
28+
try:
29+
return super(PythonLanguageServer, self).__getitem__(item)
30+
except KeyError:
31+
log.debug("Checking dispatchers for %s: %s", item, self._dispatchers)
32+
for dispatcher in self._dispatchers:
33+
try:
34+
return dispatcher.__getitem__(item)
35+
except KeyError:
36+
pass
37+
raise KeyError("Unknown item %s" % item)
38+
2339
def _hook_caller(self, hook_name):
2440
return self.config.plugin_manager.subset_hook_caller(hook_name, self.config.disabled_plugins)
2541

@@ -56,6 +72,7 @@ def capabilities(self):
5672
def initialize(self, root_uri, init_opts, _process_id):
5773
self.workspace = Workspace(root_uri, lang_server=self)
5874
self.config = config.Config(root_uri, init_opts)
75+
self._dispatchers = self._hook('pyls_dispatchers')
5976
self._hook('pyls_initialize')
6077

6178
def code_actions(self, doc_uri, range, context):

test/test_dispatcher.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2017 Palantir Technologies, Inc.
2+
import pytest
3+
from pyls import dispatcher
4+
5+
6+
class TestDispatcher(dispatcher.JSONRPCMethodDispatcher):
7+
8+
def m_test__method(self, **params):
9+
return params
10+
11+
12+
def test_method_dispatcher():
13+
td = TestDispatcher()
14+
params = {'hello': 'world'}
15+
assert td['test/method'](**params) == params
16+
17+
18+
def test_method_dispatcher_missing_method():
19+
td = TestDispatcher()
20+
with pytest.raises(KeyError):
21+
td['test/noMethod']('hello')

0 commit comments

Comments
 (0)
0