Skip to content

Commit f8e5e3c

Browse files
committed
feat: use pygls for json-rpc language client and add support for lsprotocol 2025.0.0
1 parent 2a471f5 commit f8e5e3c

18 files changed

Lines changed: 1335 additions & 1527 deletions

File tree

spyder/plugins/completion/providers/languageserver/client.py

Lines changed: 652 additions & 473 deletions
Large diffs are not rendered by default.

spyder/plugins/completion/providers/languageserver/decorators.py

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,63 +4,89 @@
44
# Licensed under the terms of the MIT License
55
# (see spyder/__init__.py for details)
66

7-
"""Spyder Language Server Protocol Client auxiliar decorators."""
7+
"""
8+
Spyder Language Server Protocol Client auxiliary decorators.
9+
10+
send_request / send_notification / send_response are now *marker* decorators:
11+
they tag a method with the LSP method name it corresponds to and its kind
12+
('request', 'notification', 'response'). The actual sending is done by
13+
LSPClient.perform_request(), which inspects these attributes via the
14+
sender_registry built by @class_register.
15+
16+
Each tagged method is expected to:
17+
- Accept Spyder's internal params dict.
18+
- Return a lsprotocol typed object (the LSP params to send), or None to
19+
cancel the operation.
20+
21+
@handles(method_name) marks a method as the handler for an LSP response or
22+
server-initiated notification carrying that method name.
23+
24+
@class_register scans a class at definition time and builds:
25+
- handler_registry : {method_name: handler_method_name}
26+
- sender_registry : {method_name: sender_method_name}
27+
- notification_registry : {method_name}; subset of sender methods that are
28+
notifications (no response expected).
29+
"""
830

931
import functools
10-
from spyder.plugins.completion.providers.languageserver.transport import (
11-
MessageKind)
1232

1333

1434
def send_request(req=None, method=None):
15-
"""Send message as a proper JSON-RPC request."""
35+
"""Mark *req* as a method that builds params for an LSP request."""
1636
if req is None:
1737
return functools.partial(send_request, method=method)
18-
19-
return send_message(req, method, kind=MessageKind.REQUEST)
38+
req._sends = method
39+
req._kind = 'request'
40+
return req
2041

2142

2243
def send_notification(req=None, method=None):
23-
"""Send message as a proper JSON-RPC notification."""
44+
"""Mark *req* as a method that builds params for an LSP notification."""
2445
if req is None:
2546
return functools.partial(send_notification, method=method)
26-
27-
return send_message(req, method, kind=MessageKind.NOTIFICATION)
47+
req._sends = method
48+
req._kind = 'notification'
49+
return req
2850

2951

3052
def send_response(req=None, method=None):
31-
"""Send message as a proper JSON-RPC response."""
53+
"""
54+
Mark *req* as a handler for a server-request that requires a response.
55+
56+
With pygls the response is sent automatically by the feature manager when
57+
the registered handler returns a value, so this decorator is retained
58+
only for semantic clarity and registry bookkeeping.
59+
"""
3260
if req is None:
3361
return functools.partial(send_response, method=method)
34-
35-
return send_message(req, method, kind=MessageKind.RESPONSE)
36-
37-
38-
def send_message(req=None, method=None, kind=MessageKind.REQUEST):
39-
"""Call function req and then send its results via ZMQ."""
40-
@functools.wraps(req)
41-
def wrapper(self, *args, **kwargs):
42-
params = req(self, *args, **kwargs)
43-
_id = self.send(method, params, kind)
44-
return _id
45-
wrapper._sends = method
46-
return wrapper
62+
req._sends = method
63+
req._kind = 'response'
64+
return req
4765

4866

4967
def class_register(cls):
50-
"""Class decorator that allows to map LSP method names to class methods."""
68+
"""
69+
Class decorator that builds handler and sender registries from decorated
70+
methods, enabling dynamic dispatch in LSPClient.
71+
"""
5172
cls.handler_registry = {}
5273
cls.sender_registry = {}
74+
cls.notification_registry = set()
75+
5376
for method_name in dir(cls):
5477
method = getattr(cls, method_name)
5578
if hasattr(method, '_handle'):
56-
cls.handler_registry.update({method._handle: method_name})
79+
cls.handler_registry[method._handle] = method_name
5780
if hasattr(method, '_sends'):
58-
cls.sender_registry.update({method._sends: method_name})
81+
cls.sender_registry[method._sends] = method_name
82+
if getattr(method, '_kind', 'request') in ('notification', 'response'):
83+
cls.notification_registry.add(method._sends)
84+
5985
return cls
6086

6187

6288
def handles(method_name):
63-
"""Assign an LSP method name to a python handler."""
89+
"""Tag a method as the handler for LSP *method_name* responses/notifications."""
6490
def wrapper(func):
6591
func._handle = method_name
6692
return func

spyder/plugins/completion/providers/languageserver/provider.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,8 +433,7 @@ def wrap_message_box(parent):
433433
@Slot(str)
434434
def report_lsp_down(self, language):
435435
"""
436-
Report that either the transport layer or the LSP server are
437-
down.
436+
Report that the LSP server is down.
438437
"""
439438
self.update_status(language, ClientStatus.DOWN)
440439

spyder/plugins/completion/providers/languageserver/providers/client.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,23 @@
88

99
import logging
1010

11+
from lsprotocol import types as lsp
12+
1113
from spyder.plugins.completion.api import CompletionRequestTypes
12-
from spyder.plugins.completion.providers.languageserver.decorators import (
13-
handles, send_response)
14+
from spyder.plugins.completion.providers.languageserver.decorators import handles
1415

1516
logger = logging.getLogger(__name__)
1617

1718

1819
class ClientProvider:
1920
@handles(CompletionRequestTypes.CLIENT_REGISTER_CAPABILITY)
20-
@send_response
21-
def handle_register_capability(self, params):
21+
def handle_register_capability(
22+
self, params: lsp.RegistrationParams, *args
23+
) -> None:
2224
"""TODO: Handle the glob patterns of the files to watch."""
23-
logger.debug('Register Capability: {0}'.format(params))
24-
return {}
25+
for reg in params.registrations:
26+
logger.debug(
27+
'Register capability: id=%s method=%s',
28+
reg.id,
29+
reg.method,
30+
)

0 commit comments

Comments
 (0)