jaclang 0.0.6__py3-none-any.whl → 0.0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jaclang might be problematic. Click here for more details.
- jaclang/__init__.py +2 -1
- jaclang/cli/__jac_gen__/__init__.py +0 -0
- jaclang/cli/__jac_gen__/cli.py +175 -0
- jaclang/cli/__jac_gen__/cmds.py +132 -0
- jaclang/cli/cmds.jac +3 -0
- jaclang/cli/impl/__jac_gen__/__init__.py +0 -0
- jaclang/cli/impl/__jac_gen__/cli_impl.py +16 -0
- jaclang/cli/impl/__jac_gen__/cmds_impl.py +26 -0
- jaclang/cli/impl/cmds_impl.jac +17 -3
- jaclang/core/__jac_gen__/__init__.py +0 -0
- jaclang/core/__jac_gen__/primitives.py +567 -0
- jaclang/core/impl/__jac_gen__/__init__.py +0 -0
- jaclang/core/impl/__jac_gen__/arch_impl.py +24 -0
- jaclang/core/impl/__jac_gen__/element_impl.py +26 -0
- jaclang/core/impl/__jac_gen__/exec_ctx_impl.py +12 -0
- jaclang/core/impl/__jac_gen__/memory_impl.py +14 -0
- jaclang/core/impl/element_impl.jac +2 -2
- jaclang/core/primitives.jac +1 -0
- jaclang/jac/absyntree.py +65 -42
- jaclang/jac/constant.py +4 -0
- jaclang/jac/importer.py +18 -60
- jaclang/jac/langserve.py +26 -0
- jaclang/jac/lexer.py +9 -1
- jaclang/jac/parser.py +135 -123
- jaclang/jac/passes/blue/ast_build_pass.py +410 -353
- jaclang/jac/passes/blue/blue_pygen_pass.py +15 -0
- jaclang/jac/passes/blue/decl_def_match_pass.py +33 -21
- jaclang/jac/passes/blue/import_pass.py +1 -1
- jaclang/jac/passes/blue/pyout_pass.py +47 -12
- jaclang/jac/passes/blue/sym_tab_build_pass.py +38 -127
- jaclang/jac/passes/blue/tests/test_ast_build_pass.py +2 -2
- jaclang/jac/passes/blue/tests/test_blue_pygen_pass.py +9 -30
- jaclang/jac/passes/blue/tests/test_decl_def_match_pass.py +13 -13
- jaclang/jac/passes/blue/tests/test_sym_tab_build_pass.py +6 -4
- jaclang/jac/passes/ir_pass.py +1 -1
- jaclang/jac/passes/purple/__jac_gen__/__init__.py +0 -0
- jaclang/jac/passes/purple/__jac_gen__/analyze_pass.py +37 -0
- jaclang/jac/passes/purple/__jac_gen__/purple_pygen_pass.py +305 -0
- jaclang/jac/passes/purple/impl/__jac_gen__/__init__.py +0 -0
- jaclang/jac/passes/purple/impl/__jac_gen__/purple_pygen_pass_impl.py +23 -0
- jaclang/jac/symtable.py +12 -4
- jaclang/jac/tests/fixtures/__jac_gen__/__init__.py +0 -0
- jaclang/jac/tests/fixtures/__jac_gen__/hello_world.py +16 -0
- jaclang/jac/tests/fixtures/fam.jac +7 -8
- jaclang/jac/transform.py +4 -3
- jaclang/jac/transpiler.py +13 -9
- jaclang/utils/fstring_parser.py +2 -2
- jaclang/utils/helpers.py +41 -0
- jaclang/utils/test.py +30 -0
- jaclang/vendor/__init__.py +1 -0
- jaclang/vendor/pygls/__init__.py +25 -0
- jaclang/vendor/pygls/capabilities.py +502 -0
- jaclang/vendor/pygls/client.py +176 -0
- jaclang/vendor/pygls/constants.py +26 -0
- jaclang/vendor/pygls/exceptions.py +220 -0
- jaclang/vendor/pygls/feature_manager.py +241 -0
- jaclang/vendor/pygls/lsp/__init__.py +139 -0
- jaclang/vendor/pygls/lsp/client.py +2224 -0
- jaclang/vendor/pygls/lsprotocol/__init__.py +2 -0
- jaclang/vendor/pygls/lsprotocol/_hooks.py +1233 -0
- jaclang/vendor/pygls/lsprotocol/converters.py +17 -0
- jaclang/vendor/pygls/lsprotocol/types.py +12820 -0
- jaclang/vendor/pygls/lsprotocol/validators.py +47 -0
- jaclang/vendor/pygls/progress.py +79 -0
- jaclang/vendor/pygls/protocol.py +1184 -0
- jaclang/vendor/pygls/server.py +620 -0
- jaclang/vendor/pygls/uris.py +184 -0
- jaclang/vendor/pygls/workspace/__init__.py +81 -0
- jaclang/vendor/pygls/workspace/position.py +204 -0
- jaclang/vendor/pygls/workspace/text_document.py +234 -0
- jaclang/vendor/pygls/workspace/workspace.py +311 -0
- {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/METADATA +1 -1
- jaclang-0.0.8.dist-info/RECORD +118 -0
- jaclang/core/jaclang.jac +0 -62
- jaclang-0.0.6.dist-info/RECORD +0 -76
- /jaclang/{utils → vendor}/sly/__init__.py +0 -0
- /jaclang/{utils → vendor}/sly/docparse.py +0 -0
- /jaclang/{utils → vendor}/sly/lex.py +0 -0
- /jaclang/{utils → vendor}/sly/yacc.py +0 -0
- {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/WHEEL +0 -0
- {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/entry_points.txt +0 -0
- {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1184 @@
|
|
|
1
|
+
############################################################################
|
|
2
|
+
# Copyright(c) Open Law Library. All rights reserved. #
|
|
3
|
+
# See ThirdPartyNotices.txt in the project root for additional notices. #
|
|
4
|
+
# #
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License") #
|
|
6
|
+
# you may not use this file except in compliance with the License. #
|
|
7
|
+
# You may obtain a copy of the License at #
|
|
8
|
+
# #
|
|
9
|
+
# http: // www.apache.org/licenses/LICENSE-2.0 #
|
|
10
|
+
# #
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software #
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
|
14
|
+
# See the License for the specific language governing permissions and #
|
|
15
|
+
# limitations under the License. #
|
|
16
|
+
############################################################################
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
import asyncio
|
|
19
|
+
import enum
|
|
20
|
+
import functools
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
import uuid
|
|
26
|
+
import traceback
|
|
27
|
+
from collections import namedtuple
|
|
28
|
+
from concurrent.futures import Future
|
|
29
|
+
from functools import lru_cache, partial
|
|
30
|
+
from itertools import zip_longest
|
|
31
|
+
from typing import (
|
|
32
|
+
Any,
|
|
33
|
+
Callable,
|
|
34
|
+
Dict,
|
|
35
|
+
List,
|
|
36
|
+
Optional,
|
|
37
|
+
Type,
|
|
38
|
+
TypeVar,
|
|
39
|
+
Union,
|
|
40
|
+
TYPE_CHECKING,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
from jaclang.vendor.pygls.server import LanguageServer, WebSocketTransportAdapter
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
import attrs
|
|
48
|
+
from cattrs.errors import ClassValidationError
|
|
49
|
+
from jaclang.vendor.pygls.lsprotocol import converters
|
|
50
|
+
|
|
51
|
+
from jaclang.vendor.pygls.capabilities import ServerCapabilitiesBuilder
|
|
52
|
+
from jaclang.vendor.pygls.constants import ATTR_FEATURE_TYPE
|
|
53
|
+
from jaclang.vendor.pygls.exceptions import (
|
|
54
|
+
JsonRpcException,
|
|
55
|
+
JsonRpcInternalError,
|
|
56
|
+
JsonRpcInvalidParams,
|
|
57
|
+
JsonRpcMethodNotFound,
|
|
58
|
+
JsonRpcRequestCancelled,
|
|
59
|
+
FeatureNotificationError,
|
|
60
|
+
FeatureRequestError,
|
|
61
|
+
)
|
|
62
|
+
from jaclang.vendor.pygls.feature_manager import (
|
|
63
|
+
FeatureManager,
|
|
64
|
+
assign_help_attrs,
|
|
65
|
+
is_thread_function,
|
|
66
|
+
)
|
|
67
|
+
from jaclang.vendor.pygls.lsp import ConfigCallbackType, ShowDocumentCallbackType
|
|
68
|
+
from jaclang.vendor.pygls.lsprotocol.types import (
|
|
69
|
+
CANCEL_REQUEST,
|
|
70
|
+
CLIENT_REGISTER_CAPABILITY,
|
|
71
|
+
CLIENT_UNREGISTER_CAPABILITY,
|
|
72
|
+
EXIT,
|
|
73
|
+
INITIALIZE,
|
|
74
|
+
INITIALIZED,
|
|
75
|
+
METHOD_TO_TYPES,
|
|
76
|
+
NOTEBOOK_DOCUMENT_DID_CHANGE,
|
|
77
|
+
NOTEBOOK_DOCUMENT_DID_CLOSE,
|
|
78
|
+
NOTEBOOK_DOCUMENT_DID_OPEN,
|
|
79
|
+
LOG_TRACE,
|
|
80
|
+
SET_TRACE,
|
|
81
|
+
SHUTDOWN,
|
|
82
|
+
TEXT_DOCUMENT_DID_CHANGE,
|
|
83
|
+
TEXT_DOCUMENT_DID_CLOSE,
|
|
84
|
+
TEXT_DOCUMENT_DID_OPEN,
|
|
85
|
+
TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS,
|
|
86
|
+
WINDOW_LOG_MESSAGE,
|
|
87
|
+
WINDOW_SHOW_DOCUMENT,
|
|
88
|
+
WINDOW_SHOW_MESSAGE,
|
|
89
|
+
WINDOW_WORK_DONE_PROGRESS_CANCEL,
|
|
90
|
+
WORKSPACE_APPLY_EDIT,
|
|
91
|
+
WORKSPACE_CONFIGURATION,
|
|
92
|
+
WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS,
|
|
93
|
+
WORKSPACE_EXECUTE_COMMAND,
|
|
94
|
+
WORKSPACE_SEMANTIC_TOKENS_REFRESH,
|
|
95
|
+
)
|
|
96
|
+
from jaclang.vendor.pygls.lsprotocol.types import (
|
|
97
|
+
ApplyWorkspaceEditParams,
|
|
98
|
+
Diagnostic,
|
|
99
|
+
DidChangeNotebookDocumentParams,
|
|
100
|
+
DidChangeTextDocumentParams,
|
|
101
|
+
DidChangeWorkspaceFoldersParams,
|
|
102
|
+
DidCloseNotebookDocumentParams,
|
|
103
|
+
DidCloseTextDocumentParams,
|
|
104
|
+
DidOpenNotebookDocumentParams,
|
|
105
|
+
DidOpenTextDocumentParams,
|
|
106
|
+
ExecuteCommandParams,
|
|
107
|
+
InitializeParams,
|
|
108
|
+
InitializeResult,
|
|
109
|
+
LogMessageParams,
|
|
110
|
+
LogTraceParams,
|
|
111
|
+
MessageType,
|
|
112
|
+
PublishDiagnosticsParams,
|
|
113
|
+
RegistrationParams,
|
|
114
|
+
ResponseErrorMessage,
|
|
115
|
+
SetTraceParams,
|
|
116
|
+
ShowDocumentParams,
|
|
117
|
+
ShowMessageParams,
|
|
118
|
+
TraceValues,
|
|
119
|
+
UnregistrationParams,
|
|
120
|
+
WorkspaceApplyEditResponse,
|
|
121
|
+
WorkspaceEdit,
|
|
122
|
+
InitializeResultServerInfoType,
|
|
123
|
+
WorkspaceConfigurationParams,
|
|
124
|
+
WorkDoneProgressCancelParams,
|
|
125
|
+
)
|
|
126
|
+
from jaclang.vendor.pygls.uris import from_fs_path
|
|
127
|
+
from jaclang.vendor.pygls.workspace import Workspace
|
|
128
|
+
|
|
129
|
+
logger = logging.getLogger(__name__)
|
|
130
|
+
|
|
131
|
+
F = TypeVar("F", bound=Callable)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def call_user_feature(base_func, method_name):
|
|
135
|
+
"""Wraps generic LSP features and calls user registered feature
|
|
136
|
+
immediately after it.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
@functools.wraps(base_func)
|
|
140
|
+
def decorator(self, *args, **kwargs):
|
|
141
|
+
ret_val = base_func(self, *args, **kwargs)
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
user_func = self.fm.features[method_name]
|
|
145
|
+
self._execute_notification(user_func, *args, **kwargs)
|
|
146
|
+
except KeyError:
|
|
147
|
+
pass
|
|
148
|
+
except Exception:
|
|
149
|
+
logger.exception(
|
|
150
|
+
'Failed to handle user defined notification "%s": %s', method_name, args
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return ret_val
|
|
154
|
+
|
|
155
|
+
return decorator
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@attrs.define
|
|
159
|
+
class JsonRPCNotification:
|
|
160
|
+
"""A class that represents a generic json rpc notification message.
|
|
161
|
+
Used as a fallback for unknown types.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
method: str
|
|
165
|
+
jsonrpc: str
|
|
166
|
+
params: Any
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@attrs.define
|
|
170
|
+
class JsonRPCRequestMessage:
|
|
171
|
+
"""A class that represents a generic json rpc request message.
|
|
172
|
+
Used as a fallback for unknown types.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
id: Union[int, str]
|
|
176
|
+
method: str
|
|
177
|
+
jsonrpc: str
|
|
178
|
+
params: Any
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@attrs.define
|
|
182
|
+
class JsonRPCResponseMessage:
|
|
183
|
+
"""A class that represents a generic json rpc response message.
|
|
184
|
+
Used as a fallback for unknown types.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
id: Union[int, str]
|
|
188
|
+
jsonrpc: str
|
|
189
|
+
result: Any
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _dict_to_object(d: Any):
|
|
193
|
+
"""Create nested objects (namedtuple) from dict."""
|
|
194
|
+
|
|
195
|
+
if d is None:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
if not isinstance(d, dict):
|
|
199
|
+
return d
|
|
200
|
+
|
|
201
|
+
type_name = d.pop("type_name", "Object")
|
|
202
|
+
return json.loads(
|
|
203
|
+
json.dumps(d),
|
|
204
|
+
object_hook=lambda p: namedtuple(type_name, p.keys(), rename=True)(*p.values()),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _params_field_structure_hook(obj, cls):
|
|
209
|
+
if "params" in obj:
|
|
210
|
+
obj["params"] = _dict_to_object(obj["params"])
|
|
211
|
+
|
|
212
|
+
return cls(**obj)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _result_field_structure_hook(obj, cls):
|
|
216
|
+
if "result" in obj:
|
|
217
|
+
obj["result"] = _dict_to_object(obj["result"])
|
|
218
|
+
|
|
219
|
+
return cls(**obj)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def default_converter():
|
|
223
|
+
"""Default converter factory function."""
|
|
224
|
+
|
|
225
|
+
converter = converters.get_converter()
|
|
226
|
+
converter.register_structure_hook(
|
|
227
|
+
JsonRPCRequestMessage, _params_field_structure_hook
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
converter.register_structure_hook(
|
|
231
|
+
JsonRPCResponseMessage, _result_field_structure_hook
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
converter.register_structure_hook(JsonRPCNotification, _params_field_structure_hook)
|
|
235
|
+
|
|
236
|
+
return converter
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class JsonRPCProtocol(asyncio.Protocol):
|
|
240
|
+
"""Json RPC protocol implementation using on top of `asyncio.Protocol`.
|
|
241
|
+
|
|
242
|
+
Specification of the protocol can be found here:
|
|
243
|
+
https://www.jsonrpc.org/specification
|
|
244
|
+
|
|
245
|
+
This class provides bidirectional communication which is needed for LSP.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
CHARSET = "utf-8"
|
|
249
|
+
CONTENT_TYPE = "application/vscode-jsonrpc"
|
|
250
|
+
|
|
251
|
+
MESSAGE_PATTERN = re.compile(
|
|
252
|
+
rb"^(?:[^\r\n]+\r\n)*"
|
|
253
|
+
+ rb"Content-Length: (?P<length>\d+)\r\n"
|
|
254
|
+
+ rb"(?:[^\r\n]+\r\n)*\r\n"
|
|
255
|
+
+ rb"(?P<body>{.*)",
|
|
256
|
+
re.DOTALL,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
VERSION = "2.0"
|
|
260
|
+
|
|
261
|
+
def __init__(self, server: LanguageServer, converter):
|
|
262
|
+
self._server = server
|
|
263
|
+
self._converter = converter
|
|
264
|
+
|
|
265
|
+
self._shutdown = False
|
|
266
|
+
|
|
267
|
+
# Book keeping for in-flight requests
|
|
268
|
+
self._request_futures: Dict[str, Future[Any]] = {}
|
|
269
|
+
self._result_types: Dict[str, Any] = {}
|
|
270
|
+
|
|
271
|
+
self.fm = FeatureManager(server)
|
|
272
|
+
self.transport: Optional[
|
|
273
|
+
Union[asyncio.WriteTransport, WebSocketTransportAdapter]
|
|
274
|
+
] = None
|
|
275
|
+
self._message_buf: List[bytes] = []
|
|
276
|
+
|
|
277
|
+
self._send_only_body = False
|
|
278
|
+
|
|
279
|
+
def __call__(self):
|
|
280
|
+
return self
|
|
281
|
+
|
|
282
|
+
def _execute_notification(self, handler, *params):
|
|
283
|
+
"""Executes notification message handler."""
|
|
284
|
+
if asyncio.iscoroutinefunction(handler):
|
|
285
|
+
future = asyncio.ensure_future(handler(*params))
|
|
286
|
+
future.add_done_callback(self._execute_notification_callback)
|
|
287
|
+
else:
|
|
288
|
+
if is_thread_function(handler):
|
|
289
|
+
self._server.thread_pool.apply_async(handler, (*params,))
|
|
290
|
+
else:
|
|
291
|
+
handler(*params)
|
|
292
|
+
|
|
293
|
+
def _execute_notification_callback(self, future):
|
|
294
|
+
"""Success callback used for coroutine notification message."""
|
|
295
|
+
if future.exception():
|
|
296
|
+
try:
|
|
297
|
+
raise future.exception()
|
|
298
|
+
except Exception:
|
|
299
|
+
error = JsonRpcInternalError.of(sys.exc_info()).to_dict()
|
|
300
|
+
logger.exception('Exception occurred in notification: "%s"', error)
|
|
301
|
+
|
|
302
|
+
# Revisit. Client does not support response with msg_id = None
|
|
303
|
+
# https://stackoverflow.com/questions/31091376/json-rpc-2-0-allow-notifications-to-have-an-error-response
|
|
304
|
+
# self._send_response(None, error=error)
|
|
305
|
+
|
|
306
|
+
def _execute_request(self, msg_id, handler, params):
|
|
307
|
+
"""Executes request message handler."""
|
|
308
|
+
|
|
309
|
+
if asyncio.iscoroutinefunction(handler):
|
|
310
|
+
future = asyncio.ensure_future(handler(params))
|
|
311
|
+
self._request_futures[msg_id] = future
|
|
312
|
+
future.add_done_callback(partial(self._execute_request_callback, msg_id))
|
|
313
|
+
else:
|
|
314
|
+
# Can't be canceled
|
|
315
|
+
if is_thread_function(handler):
|
|
316
|
+
self._server.thread_pool.apply_async(
|
|
317
|
+
handler,
|
|
318
|
+
(params,),
|
|
319
|
+
callback=partial(
|
|
320
|
+
self._send_response,
|
|
321
|
+
msg_id,
|
|
322
|
+
),
|
|
323
|
+
error_callback=partial(self._execute_request_err_callback, msg_id),
|
|
324
|
+
)
|
|
325
|
+
else:
|
|
326
|
+
self._send_response(msg_id, handler(params))
|
|
327
|
+
|
|
328
|
+
def _execute_request_callback(self, msg_id, future):
|
|
329
|
+
"""Success callback used for coroutine request message."""
|
|
330
|
+
try:
|
|
331
|
+
if not future.cancelled():
|
|
332
|
+
self._send_response(msg_id, result=future.result())
|
|
333
|
+
else:
|
|
334
|
+
self._send_response(
|
|
335
|
+
msg_id,
|
|
336
|
+
error=JsonRpcRequestCancelled(
|
|
337
|
+
f'Request with id "{msg_id}" is canceled'
|
|
338
|
+
),
|
|
339
|
+
)
|
|
340
|
+
self._request_futures.pop(msg_id, None)
|
|
341
|
+
except Exception:
|
|
342
|
+
error = JsonRpcInternalError.of(sys.exc_info()).to_dict()
|
|
343
|
+
logger.exception('Exception occurred for message "%s": %s', msg_id, error)
|
|
344
|
+
self._send_response(msg_id, error=error)
|
|
345
|
+
|
|
346
|
+
def _execute_request_err_callback(self, msg_id, exc):
|
|
347
|
+
"""Error callback used for coroutine request message."""
|
|
348
|
+
exc_info = (type(exc), exc, None)
|
|
349
|
+
error = JsonRpcInternalError.of(exc_info).to_dict()
|
|
350
|
+
logger.exception('Exception occurred for message "%s": %s', msg_id, error)
|
|
351
|
+
self._send_response(msg_id, error=error)
|
|
352
|
+
|
|
353
|
+
def _get_handler(self, feature_name):
|
|
354
|
+
"""Returns builtin or used defined feature by name if exists."""
|
|
355
|
+
try:
|
|
356
|
+
return self.fm.builtin_features[feature_name]
|
|
357
|
+
except KeyError:
|
|
358
|
+
try:
|
|
359
|
+
return self.fm.features[feature_name]
|
|
360
|
+
except KeyError:
|
|
361
|
+
raise JsonRpcMethodNotFound.of(feature_name)
|
|
362
|
+
|
|
363
|
+
def _handle_cancel_notification(self, msg_id):
|
|
364
|
+
"""Handles a cancel notification from the client."""
|
|
365
|
+
future = self._request_futures.pop(msg_id, None)
|
|
366
|
+
|
|
367
|
+
if not future:
|
|
368
|
+
logger.warning('Cancel notification for unknown message id "%s"', msg_id)
|
|
369
|
+
return
|
|
370
|
+
|
|
371
|
+
# Will only work if the request hasn't started executing
|
|
372
|
+
if future.cancel():
|
|
373
|
+
logger.info('Cancelled request with id "%s"', msg_id)
|
|
374
|
+
|
|
375
|
+
def _handle_notification(self, method_name, params):
|
|
376
|
+
"""Handles a notification from the client."""
|
|
377
|
+
if method_name == CANCEL_REQUEST:
|
|
378
|
+
self._handle_cancel_notification(params.id)
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
try:
|
|
382
|
+
handler = self._get_handler(method_name)
|
|
383
|
+
self._execute_notification(handler, params)
|
|
384
|
+
except (KeyError, JsonRpcMethodNotFound):
|
|
385
|
+
logger.warning('Ignoring notification for unknown method "%s"', method_name)
|
|
386
|
+
except Exception as error:
|
|
387
|
+
logger.exception(
|
|
388
|
+
'Failed to handle notification "%s": %s',
|
|
389
|
+
method_name,
|
|
390
|
+
params,
|
|
391
|
+
exc_info=True,
|
|
392
|
+
)
|
|
393
|
+
self._server._report_server_error(error, FeatureNotificationError)
|
|
394
|
+
|
|
395
|
+
def _handle_request(self, msg_id, method_name, params):
|
|
396
|
+
"""Handles a request from the client."""
|
|
397
|
+
try:
|
|
398
|
+
handler = self._get_handler(method_name)
|
|
399
|
+
|
|
400
|
+
# workspace/executeCommand is a special case
|
|
401
|
+
if method_name == WORKSPACE_EXECUTE_COMMAND:
|
|
402
|
+
handler(params, msg_id)
|
|
403
|
+
else:
|
|
404
|
+
self._execute_request(msg_id, handler, params)
|
|
405
|
+
|
|
406
|
+
except JsonRpcException as error:
|
|
407
|
+
logger.exception(
|
|
408
|
+
"Failed to handle request %s %s %s",
|
|
409
|
+
msg_id,
|
|
410
|
+
method_name,
|
|
411
|
+
params,
|
|
412
|
+
exc_info=True,
|
|
413
|
+
)
|
|
414
|
+
self._send_response(msg_id, None, error.to_dict())
|
|
415
|
+
self._server._report_server_error(error, FeatureRequestError)
|
|
416
|
+
except Exception as error:
|
|
417
|
+
logger.exception(
|
|
418
|
+
"Failed to handle request %s %s %s",
|
|
419
|
+
msg_id,
|
|
420
|
+
method_name,
|
|
421
|
+
params,
|
|
422
|
+
exc_info=True,
|
|
423
|
+
)
|
|
424
|
+
err = JsonRpcInternalError.of(sys.exc_info()).to_dict()
|
|
425
|
+
self._send_response(msg_id, None, err)
|
|
426
|
+
self._server._report_server_error(error, FeatureRequestError)
|
|
427
|
+
|
|
428
|
+
def _handle_response(self, msg_id, result=None, error=None):
|
|
429
|
+
"""Handles a response from the client."""
|
|
430
|
+
future = self._request_futures.pop(msg_id, None)
|
|
431
|
+
|
|
432
|
+
if not future:
|
|
433
|
+
logger.warning('Received response to unknown message id "%s"', msg_id)
|
|
434
|
+
return
|
|
435
|
+
|
|
436
|
+
if error is not None:
|
|
437
|
+
logger.debug('Received error response to message "%s": %s', msg_id, error)
|
|
438
|
+
future.set_exception(JsonRpcException.from_error(error))
|
|
439
|
+
else:
|
|
440
|
+
logger.debug('Received result for message "%s": %s', msg_id, result)
|
|
441
|
+
future.set_result(result)
|
|
442
|
+
|
|
443
|
+
def _serialize_message(self, data):
|
|
444
|
+
"""Function used to serialize data sent to the client."""
|
|
445
|
+
|
|
446
|
+
if hasattr(data, "__attrs_attrs__"):
|
|
447
|
+
return self._converter.unstructure(data)
|
|
448
|
+
|
|
449
|
+
if isinstance(data, enum.Enum):
|
|
450
|
+
return data.value
|
|
451
|
+
|
|
452
|
+
return data.__dict__
|
|
453
|
+
|
|
454
|
+
def _deserialize_message(self, data):
|
|
455
|
+
"""Function used to deserialize data recevied from the client."""
|
|
456
|
+
|
|
457
|
+
if "jsonrpc" not in data:
|
|
458
|
+
return data
|
|
459
|
+
|
|
460
|
+
try:
|
|
461
|
+
if "id" in data:
|
|
462
|
+
if "error" in data:
|
|
463
|
+
return self._converter.structure(data, ResponseErrorMessage)
|
|
464
|
+
elif "method" in data:
|
|
465
|
+
request_type = (
|
|
466
|
+
self.get_message_type(data["method"]) or JsonRPCRequestMessage
|
|
467
|
+
)
|
|
468
|
+
return self._converter.structure(data, request_type)
|
|
469
|
+
else:
|
|
470
|
+
response_type = (
|
|
471
|
+
self._result_types.pop(data["id"]) or JsonRPCResponseMessage
|
|
472
|
+
)
|
|
473
|
+
return self._converter.structure(data, response_type)
|
|
474
|
+
|
|
475
|
+
else:
|
|
476
|
+
method = data.get("method", "")
|
|
477
|
+
notification_type = self.get_message_type(method) or JsonRPCNotification
|
|
478
|
+
return self._converter.structure(data, notification_type)
|
|
479
|
+
|
|
480
|
+
except ClassValidationError as exc:
|
|
481
|
+
logger.error("Unable to deserialize message\n%s", traceback.format_exc())
|
|
482
|
+
raise JsonRpcInvalidParams() from exc
|
|
483
|
+
|
|
484
|
+
except Exception as exc:
|
|
485
|
+
logger.error("Unable to deserialize message\n%s", traceback.format_exc())
|
|
486
|
+
raise JsonRpcInternalError() from exc
|
|
487
|
+
|
|
488
|
+
def _procedure_handler(self, message):
|
|
489
|
+
"""Delegates message to handlers depending on message type."""
|
|
490
|
+
|
|
491
|
+
if message.jsonrpc != JsonRPCProtocol.VERSION:
|
|
492
|
+
logger.warning('Unknown message "%s"', message)
|
|
493
|
+
return
|
|
494
|
+
|
|
495
|
+
if self._shutdown and getattr(message, "method", "") != EXIT:
|
|
496
|
+
logger.warning("Server shutting down. No more requests!")
|
|
497
|
+
return
|
|
498
|
+
|
|
499
|
+
if hasattr(message, "method"):
|
|
500
|
+
if hasattr(message, "id"):
|
|
501
|
+
logger.debug("Request message received.")
|
|
502
|
+
self._handle_request(message.id, message.method, message.params)
|
|
503
|
+
else:
|
|
504
|
+
logger.debug("Notification message received.")
|
|
505
|
+
self._handle_notification(message.method, message.params)
|
|
506
|
+
else:
|
|
507
|
+
if hasattr(message, "error"):
|
|
508
|
+
logger.debug("Error message received.")
|
|
509
|
+
self._handle_response(message.id, None, message.error)
|
|
510
|
+
else:
|
|
511
|
+
logger.debug("Response message received.")
|
|
512
|
+
self._handle_response(message.id, message.result)
|
|
513
|
+
|
|
514
|
+
def _send_data(self, data):
|
|
515
|
+
"""Sends data to the client."""
|
|
516
|
+
if not data:
|
|
517
|
+
return
|
|
518
|
+
|
|
519
|
+
if self.transport is None:
|
|
520
|
+
logger.error("Unable to send data, no available transport!")
|
|
521
|
+
return
|
|
522
|
+
|
|
523
|
+
try:
|
|
524
|
+
body = json.dumps(data, default=self._serialize_message)
|
|
525
|
+
logger.info("Sending data: %s", body)
|
|
526
|
+
|
|
527
|
+
if self._send_only_body:
|
|
528
|
+
# Mypy/Pyright seem to think `write()` wants `"bytes | bytearray | memoryview"`
|
|
529
|
+
# But runtime errors with anything but `str`.
|
|
530
|
+
self.transport.write(body) # type: ignore
|
|
531
|
+
return
|
|
532
|
+
|
|
533
|
+
header = (
|
|
534
|
+
f"Content-Length: {len(body)}\r\n"
|
|
535
|
+
f"Content-Type: {self.CONTENT_TYPE}; charset={self.CHARSET}\r\n\r\n"
|
|
536
|
+
).encode(self.CHARSET)
|
|
537
|
+
|
|
538
|
+
self.transport.write(header + body.encode(self.CHARSET))
|
|
539
|
+
except Exception as error:
|
|
540
|
+
logger.exception("Error sending data", exc_info=True)
|
|
541
|
+
self._server._report_server_error(error, JsonRpcInternalError)
|
|
542
|
+
|
|
543
|
+
def _send_response(self, msg_id, result=None, error=None):
|
|
544
|
+
"""Sends a JSON RPC response to the client.
|
|
545
|
+
|
|
546
|
+
Args:
|
|
547
|
+
msg_id(str): Id from request
|
|
548
|
+
result(any): Result returned by handler
|
|
549
|
+
error(any): Error returned by handler
|
|
550
|
+
"""
|
|
551
|
+
|
|
552
|
+
if error is not None:
|
|
553
|
+
response = ResponseErrorMessage(id=msg_id, error=error)
|
|
554
|
+
|
|
555
|
+
else:
|
|
556
|
+
response_type = self._result_types.pop(msg_id, JsonRPCResponseMessage)
|
|
557
|
+
response = response_type(
|
|
558
|
+
id=msg_id, result=result, jsonrpc=JsonRPCProtocol.VERSION
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
self._send_data(response)
|
|
562
|
+
|
|
563
|
+
def connection_lost(self, exc):
|
|
564
|
+
"""Method from base class, called when connection is lost, in which case we
|
|
565
|
+
want to shutdown the server's process as well.
|
|
566
|
+
"""
|
|
567
|
+
logger.error("Connection to the client is lost! Shutting down the server.")
|
|
568
|
+
sys.exit(1)
|
|
569
|
+
|
|
570
|
+
def connection_made( # type: ignore # see: https://github.com/python/typeshed/issues/3021
|
|
571
|
+
self,
|
|
572
|
+
transport: asyncio.Transport,
|
|
573
|
+
):
|
|
574
|
+
"""Method from base class, called when connection is established"""
|
|
575
|
+
self.transport = transport
|
|
576
|
+
|
|
577
|
+
def data_received(self, data: bytes):
|
|
578
|
+
try:
|
|
579
|
+
self._data_received(data)
|
|
580
|
+
except Exception as error:
|
|
581
|
+
logger.exception("Error receiving data", exc_info=True)
|
|
582
|
+
self._server._report_server_error(error, JsonRpcInternalError)
|
|
583
|
+
|
|
584
|
+
def _data_received(self, data: bytes):
|
|
585
|
+
"""Method from base class, called when server receives the data"""
|
|
586
|
+
logger.debug("Received %r", data)
|
|
587
|
+
|
|
588
|
+
while len(data):
|
|
589
|
+
# Append the incoming chunk to the message buffer
|
|
590
|
+
self._message_buf.append(data)
|
|
591
|
+
|
|
592
|
+
# Look for the body of the message
|
|
593
|
+
message = b"".join(self._message_buf)
|
|
594
|
+
found = JsonRPCProtocol.MESSAGE_PATTERN.fullmatch(message)
|
|
595
|
+
|
|
596
|
+
body = found.group("body") if found else b""
|
|
597
|
+
length = int(found.group("length")) if found else 1
|
|
598
|
+
|
|
599
|
+
if len(body) < length:
|
|
600
|
+
# Message is incomplete; bail until more data arrives
|
|
601
|
+
return
|
|
602
|
+
|
|
603
|
+
# Message is complete;
|
|
604
|
+
# extract the body and any remaining data,
|
|
605
|
+
# and reset the buffer for the next message
|
|
606
|
+
body, data = body[:length], body[length:]
|
|
607
|
+
self._message_buf = []
|
|
608
|
+
|
|
609
|
+
# Parse the body
|
|
610
|
+
self._procedure_handler(
|
|
611
|
+
json.loads(
|
|
612
|
+
body.decode(self.CHARSET), object_hook=self._deserialize_message
|
|
613
|
+
)
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
def get_message_type(self, method: str) -> Optional[Type]:
|
|
617
|
+
"""Return the type definition of the message associated with the given method."""
|
|
618
|
+
return None
|
|
619
|
+
|
|
620
|
+
def get_result_type(self, method: str) -> Optional[Type]:
|
|
621
|
+
"""Return the type definition of the result associated with the given method."""
|
|
622
|
+
return None
|
|
623
|
+
|
|
624
|
+
def notify(self, method: str, params=None):
|
|
625
|
+
"""Sends a JSON RPC notification to the client."""
|
|
626
|
+
|
|
627
|
+
logger.debug("Sending notification: '%s' %s", method, params)
|
|
628
|
+
|
|
629
|
+
notification_type = self.get_message_type(method) or JsonRPCNotification
|
|
630
|
+
notification = notification_type(
|
|
631
|
+
method=method, params=params, jsonrpc=JsonRPCProtocol.VERSION
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
self._send_data(notification)
|
|
635
|
+
|
|
636
|
+
def send_request(self, method, params=None, callback=None, msg_id=None):
|
|
637
|
+
"""Sends a JSON RPC request to the client.
|
|
638
|
+
|
|
639
|
+
Args:
|
|
640
|
+
method(str): The method name of the message to send
|
|
641
|
+
params(any): The payload of the message
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
Future that will be resolved once a response has been received
|
|
645
|
+
"""
|
|
646
|
+
|
|
647
|
+
if msg_id is None:
|
|
648
|
+
msg_id = str(uuid.uuid4())
|
|
649
|
+
|
|
650
|
+
request_type = self.get_message_type(method) or JsonRPCRequestMessage
|
|
651
|
+
logger.debug('Sending request with id "%s": %s %s', msg_id, method, params)
|
|
652
|
+
|
|
653
|
+
request = request_type(
|
|
654
|
+
id=msg_id,
|
|
655
|
+
method=method,
|
|
656
|
+
params=params,
|
|
657
|
+
jsonrpc=JsonRPCProtocol.VERSION,
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
future = Future() # type: ignore[var-annotated]
|
|
661
|
+
# If callback function is given, call it when result is received
|
|
662
|
+
if callback:
|
|
663
|
+
|
|
664
|
+
def wrapper(future: Future):
|
|
665
|
+
result = future.result()
|
|
666
|
+
logger.info("Client response for %s received: %s", params, result)
|
|
667
|
+
callback(result)
|
|
668
|
+
|
|
669
|
+
future.add_done_callback(wrapper)
|
|
670
|
+
|
|
671
|
+
self._request_futures[msg_id] = future
|
|
672
|
+
self._result_types[msg_id] = self.get_result_type(method)
|
|
673
|
+
|
|
674
|
+
self._send_data(request)
|
|
675
|
+
|
|
676
|
+
return future
|
|
677
|
+
|
|
678
|
+
def send_request_async(self, method, params=None, msg_id=None):
|
|
679
|
+
"""Calls `send_request` and wraps `concurrent.futures.Future` with
|
|
680
|
+
`asyncio.Future` so it can be used with `await` keyword.
|
|
681
|
+
|
|
682
|
+
Args:
|
|
683
|
+
method(str): The method name of the message to send
|
|
684
|
+
params(any): The payload of the message
|
|
685
|
+
msg_id(str|int): Optional, message id
|
|
686
|
+
|
|
687
|
+
Returns:
|
|
688
|
+
`asyncio.Future` that can be awaited
|
|
689
|
+
"""
|
|
690
|
+
return asyncio.wrap_future(
|
|
691
|
+
self.send_request(method, params=params, msg_id=msg_id)
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
def thread(self):
|
|
695
|
+
"""Decorator that mark function to execute it in a thread."""
|
|
696
|
+
return self.fm.thread()
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def lsp_method(method_name: str) -> Callable[[F], F]:
|
|
700
|
+
def decorator(f: F) -> F:
|
|
701
|
+
f.method_name = method_name # type: ignore[attr-defined]
|
|
702
|
+
return f
|
|
703
|
+
|
|
704
|
+
return decorator
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
class LSPMeta(type):
|
|
708
|
+
"""Wraps LSP built-in features (`lsp_` naming convention).
|
|
709
|
+
|
|
710
|
+
Built-in features cannot be overridden but user defined features with
|
|
711
|
+
the same LSP name will be called after them.
|
|
712
|
+
"""
|
|
713
|
+
|
|
714
|
+
def __new__(mcs, cls_name, cls_bases, cls):
|
|
715
|
+
for attr_name, attr_val in cls.items():
|
|
716
|
+
if callable(attr_val) and hasattr(attr_val, "method_name"):
|
|
717
|
+
method_name = attr_val.method_name
|
|
718
|
+
wrapped = call_user_feature(attr_val, method_name)
|
|
719
|
+
assign_help_attrs(wrapped, method_name, ATTR_FEATURE_TYPE)
|
|
720
|
+
cls[attr_name] = wrapped
|
|
721
|
+
|
|
722
|
+
logger.debug('Added decorator for lsp method: "%s"', attr_name)
|
|
723
|
+
|
|
724
|
+
return super().__new__(mcs, cls_name, cls_bases, cls)
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
class LanguageServerProtocol(JsonRPCProtocol, metaclass=LSPMeta):
|
|
728
|
+
"""A class that represents language server protocol.
|
|
729
|
+
|
|
730
|
+
It contains implementations for generic LSP features.
|
|
731
|
+
|
|
732
|
+
Attributes:
|
|
733
|
+
workspace(Workspace): In memory workspace
|
|
734
|
+
"""
|
|
735
|
+
|
|
736
|
+
def __init__(self, server, converter):
|
|
737
|
+
super().__init__(server, converter)
|
|
738
|
+
|
|
739
|
+
self._workspace: Optional[Workspace] = None
|
|
740
|
+
self.trace = None
|
|
741
|
+
|
|
742
|
+
from jaclang.vendor.pygls.progress import Progress
|
|
743
|
+
|
|
744
|
+
self.progress = Progress(self)
|
|
745
|
+
|
|
746
|
+
self.server_info = InitializeResultServerInfoType(
|
|
747
|
+
name=server.name,
|
|
748
|
+
version=server.version,
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
self._register_builtin_features()
|
|
752
|
+
|
|
753
|
+
def _register_builtin_features(self):
|
|
754
|
+
"""Registers generic LSP features from this class."""
|
|
755
|
+
for name in dir(self):
|
|
756
|
+
if name in {"workspace"}:
|
|
757
|
+
continue
|
|
758
|
+
|
|
759
|
+
attr = getattr(self, name)
|
|
760
|
+
if callable(attr) and hasattr(attr, "method_name"):
|
|
761
|
+
self.fm.add_builtin_feature(attr.method_name, attr)
|
|
762
|
+
|
|
763
|
+
@property
|
|
764
|
+
def workspace(self) -> Workspace:
|
|
765
|
+
if self._workspace is None:
|
|
766
|
+
raise RuntimeError(
|
|
767
|
+
"The workspace is not available - has the server been initialized?"
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
return self._workspace
|
|
771
|
+
|
|
772
|
+
@lru_cache()
|
|
773
|
+
def get_message_type(self, method: str) -> Optional[Type]:
|
|
774
|
+
"""Return LSP type definitions, as provided by `lsprotocol`"""
|
|
775
|
+
return METHOD_TO_TYPES.get(method, (None,))[0]
|
|
776
|
+
|
|
777
|
+
@lru_cache()
|
|
778
|
+
def get_result_type(self, method: str) -> Optional[Type]:
|
|
779
|
+
return METHOD_TO_TYPES.get(method, (None, None))[1]
|
|
780
|
+
|
|
781
|
+
def apply_edit(
|
|
782
|
+
self, edit: WorkspaceEdit, label: Optional[str] = None
|
|
783
|
+
) -> WorkspaceApplyEditResponse:
|
|
784
|
+
"""Sends apply edit request to the client."""
|
|
785
|
+
return self.send_request(
|
|
786
|
+
WORKSPACE_APPLY_EDIT, ApplyWorkspaceEditParams(edit=edit, label=label)
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
def apply_edit_async(
|
|
790
|
+
self, edit: WorkspaceEdit, label: Optional[str] = None
|
|
791
|
+
) -> WorkspaceApplyEditResponse:
|
|
792
|
+
"""Sends apply edit request to the client. Should be called with `await`"""
|
|
793
|
+
return self.send_request_async(
|
|
794
|
+
WORKSPACE_APPLY_EDIT, ApplyWorkspaceEditParams(edit=edit, label=label)
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
@lsp_method(EXIT)
|
|
798
|
+
def lsp_exit(self, *args) -> None:
|
|
799
|
+
"""Stops the server process."""
|
|
800
|
+
if self.transport is not None:
|
|
801
|
+
self.transport.close()
|
|
802
|
+
|
|
803
|
+
sys.exit(0 if self._shutdown else 1)
|
|
804
|
+
|
|
805
|
+
@lsp_method(INITIALIZE)
|
|
806
|
+
def lsp_initialize(self, params: InitializeParams) -> InitializeResult:
|
|
807
|
+
"""Method that initializes language server.
|
|
808
|
+
It will compute and return server capabilities based on
|
|
809
|
+
registered features.
|
|
810
|
+
"""
|
|
811
|
+
logger.info("Language server initialized %s", params)
|
|
812
|
+
|
|
813
|
+
self._server.process_id = params.process_id
|
|
814
|
+
|
|
815
|
+
text_document_sync_kind = self._server._text_document_sync_kind
|
|
816
|
+
notebook_document_sync = self._server._notebook_document_sync
|
|
817
|
+
|
|
818
|
+
# Initialize server capabilities
|
|
819
|
+
self.client_capabilities = params.capabilities
|
|
820
|
+
self.server_capabilities = ServerCapabilitiesBuilder(
|
|
821
|
+
self.client_capabilities,
|
|
822
|
+
set({**self.fm.features, **self.fm.builtin_features}.keys()),
|
|
823
|
+
self.fm.feature_options,
|
|
824
|
+
list(self.fm.commands.keys()),
|
|
825
|
+
text_document_sync_kind,
|
|
826
|
+
notebook_document_sync,
|
|
827
|
+
).build()
|
|
828
|
+
logger.debug(
|
|
829
|
+
"Server capabilities: %s",
|
|
830
|
+
json.dumps(self.server_capabilities, default=self._serialize_message),
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
root_path = params.root_path
|
|
834
|
+
root_uri = params.root_uri
|
|
835
|
+
if root_path is not None and root_uri is None:
|
|
836
|
+
root_uri = from_fs_path(root_path)
|
|
837
|
+
|
|
838
|
+
# Initialize the workspace
|
|
839
|
+
workspace_folders = params.workspace_folders or []
|
|
840
|
+
self._workspace = Workspace(
|
|
841
|
+
root_uri,
|
|
842
|
+
text_document_sync_kind,
|
|
843
|
+
workspace_folders,
|
|
844
|
+
self.server_capabilities.position_encoding,
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
self.trace = TraceValues.Off
|
|
848
|
+
|
|
849
|
+
return InitializeResult(
|
|
850
|
+
capabilities=self.server_capabilities,
|
|
851
|
+
server_info=self.server_info,
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
@lsp_method(INITIALIZED)
|
|
855
|
+
def lsp_initialized(self, *args) -> None:
|
|
856
|
+
"""Notification received when client and server are connected."""
|
|
857
|
+
pass
|
|
858
|
+
|
|
859
|
+
@lsp_method(SHUTDOWN)
|
|
860
|
+
def lsp_shutdown(self, *args) -> None:
|
|
861
|
+
"""Request from client which asks server to shutdown."""
|
|
862
|
+
for future in self._request_futures.values():
|
|
863
|
+
future.cancel()
|
|
864
|
+
|
|
865
|
+
self._shutdown = True
|
|
866
|
+
return None
|
|
867
|
+
|
|
868
|
+
@lsp_method(TEXT_DOCUMENT_DID_CHANGE)
|
|
869
|
+
def lsp_text_document__did_change(
|
|
870
|
+
self, params: DidChangeTextDocumentParams
|
|
871
|
+
) -> None:
|
|
872
|
+
"""Updates document's content.
|
|
873
|
+
(Incremental(from server capabilities); not configurable for now)
|
|
874
|
+
"""
|
|
875
|
+
for change in params.content_changes:
|
|
876
|
+
self.workspace.update_text_document(params.text_document, change)
|
|
877
|
+
|
|
878
|
+
@lsp_method(TEXT_DOCUMENT_DID_CLOSE)
|
|
879
|
+
def lsp_text_document__did_close(self, params: DidCloseTextDocumentParams) -> None:
|
|
880
|
+
"""Removes document from workspace."""
|
|
881
|
+
self.workspace.remove_text_document(params.text_document.uri)
|
|
882
|
+
|
|
883
|
+
@lsp_method(TEXT_DOCUMENT_DID_OPEN)
|
|
884
|
+
def lsp_text_document__did_open(self, params: DidOpenTextDocumentParams) -> None:
|
|
885
|
+
"""Puts document to the workspace."""
|
|
886
|
+
self.workspace.put_text_document(params.text_document)
|
|
887
|
+
|
|
888
|
+
@lsp_method(NOTEBOOK_DOCUMENT_DID_OPEN)
|
|
889
|
+
def lsp_notebook_document__did_open(
|
|
890
|
+
self, params: DidOpenNotebookDocumentParams
|
|
891
|
+
) -> None:
|
|
892
|
+
"""Put a notebook document into the workspace"""
|
|
893
|
+
self.workspace.put_notebook_document(params)
|
|
894
|
+
|
|
895
|
+
@lsp_method(NOTEBOOK_DOCUMENT_DID_CHANGE)
|
|
896
|
+
def lsp_notebook_document__did_change(
|
|
897
|
+
self, params: DidChangeNotebookDocumentParams
|
|
898
|
+
) -> None:
|
|
899
|
+
"""Update a notebook's contents"""
|
|
900
|
+
self.workspace.update_notebook_document(params)
|
|
901
|
+
|
|
902
|
+
@lsp_method(NOTEBOOK_DOCUMENT_DID_CLOSE)
|
|
903
|
+
def lsp_notebook_document__did_close(
|
|
904
|
+
self, params: DidCloseNotebookDocumentParams
|
|
905
|
+
) -> None:
|
|
906
|
+
"""Remove a notebook document from the workspace."""
|
|
907
|
+
self.workspace.remove_notebook_document(params)
|
|
908
|
+
|
|
909
|
+
@lsp_method(SET_TRACE)
|
|
910
|
+
def lsp_set_trace(self, params: SetTraceParams) -> None:
|
|
911
|
+
"""Changes server trace value."""
|
|
912
|
+
self.trace = params.value
|
|
913
|
+
|
|
914
|
+
@lsp_method(WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS)
|
|
915
|
+
def lsp_workspace__did_change_workspace_folders(
|
|
916
|
+
self, params: DidChangeWorkspaceFoldersParams
|
|
917
|
+
) -> None:
|
|
918
|
+
"""Adds/Removes folders from the workspace."""
|
|
919
|
+
logger.info("Workspace folders changed: %s", params)
|
|
920
|
+
|
|
921
|
+
added_folders = params.event.added or []
|
|
922
|
+
removed_folders = params.event.removed or []
|
|
923
|
+
|
|
924
|
+
for f_add, f_remove in zip_longest(added_folders, removed_folders):
|
|
925
|
+
if f_add:
|
|
926
|
+
self.workspace.add_folder(f_add)
|
|
927
|
+
if f_remove:
|
|
928
|
+
self.workspace.remove_folder(f_remove.uri)
|
|
929
|
+
|
|
930
|
+
@lsp_method(WORKSPACE_EXECUTE_COMMAND)
|
|
931
|
+
def lsp_workspace__execute_command(
|
|
932
|
+
self, params: ExecuteCommandParams, msg_id: str
|
|
933
|
+
) -> None:
|
|
934
|
+
"""Executes commands with passed arguments and returns a value."""
|
|
935
|
+
cmd_handler = self.fm.commands[params.command]
|
|
936
|
+
self._execute_request(msg_id, cmd_handler, params.arguments)
|
|
937
|
+
|
|
938
|
+
@lsp_method(WINDOW_WORK_DONE_PROGRESS_CANCEL)
|
|
939
|
+
def lsp_work_done_progress_cancel(
|
|
940
|
+
self, params: WorkDoneProgressCancelParams
|
|
941
|
+
) -> None:
|
|
942
|
+
"""Received a progress cancellation from client."""
|
|
943
|
+
future = self.progress.tokens.get(params.token)
|
|
944
|
+
if future is None:
|
|
945
|
+
logger.warning(
|
|
946
|
+
"Ignoring work done progress cancel for unknown token %s", params.token
|
|
947
|
+
)
|
|
948
|
+
else:
|
|
949
|
+
future.cancel()
|
|
950
|
+
|
|
951
|
+
def get_configuration(
|
|
952
|
+
self,
|
|
953
|
+
params: WorkspaceConfigurationParams,
|
|
954
|
+
callback: Optional[ConfigCallbackType] = None,
|
|
955
|
+
) -> Future:
|
|
956
|
+
"""Sends configuration request to the client.
|
|
957
|
+
|
|
958
|
+
Args:
|
|
959
|
+
params(WorkspaceConfigurationParams): WorkspaceConfigurationParams from lsp specs
|
|
960
|
+
callback(callable): Callabe which will be called after
|
|
961
|
+
response from the client is received
|
|
962
|
+
Returns:
|
|
963
|
+
concurrent.futures.Future object that will be resolved once a
|
|
964
|
+
response has been received
|
|
965
|
+
"""
|
|
966
|
+
return self.send_request(WORKSPACE_CONFIGURATION, params, callback)
|
|
967
|
+
|
|
968
|
+
def get_configuration_async(
|
|
969
|
+
self, params: WorkspaceConfigurationParams
|
|
970
|
+
) -> asyncio.Future:
|
|
971
|
+
"""Calls `get_configuration` method but designed to use with coroutines
|
|
972
|
+
|
|
973
|
+
Args:
|
|
974
|
+
params(WorkspaceConfigurationParams): WorkspaceConfigurationParams from lsp specs
|
|
975
|
+
Returns:
|
|
976
|
+
asyncio.Future that can be awaited
|
|
977
|
+
"""
|
|
978
|
+
return asyncio.wrap_future(self.get_configuration(params))
|
|
979
|
+
|
|
980
|
+
def log_trace(self, message: str, verbose: Optional[str] = None) -> None:
|
|
981
|
+
"""Sends trace notification to the client."""
|
|
982
|
+
if self.trace == TraceValues.Off:
|
|
983
|
+
return
|
|
984
|
+
|
|
985
|
+
params = LogTraceParams(message=message)
|
|
986
|
+
if verbose and self.trace == TraceValues.Verbose:
|
|
987
|
+
params.verbose = verbose
|
|
988
|
+
|
|
989
|
+
self.notify(LOG_TRACE, params)
|
|
990
|
+
|
|
991
|
+
def _publish_diagnostics_deprecator(
|
|
992
|
+
self,
|
|
993
|
+
params_or_uri: Union[str, PublishDiagnosticsParams],
|
|
994
|
+
diagnostics: Optional[List[Diagnostic]],
|
|
995
|
+
version: Optional[int],
|
|
996
|
+
**kwargs,
|
|
997
|
+
) -> PublishDiagnosticsParams:
|
|
998
|
+
if isinstance(params_or_uri, str):
|
|
999
|
+
message = "DEPRECATION: "
|
|
1000
|
+
"`publish_diagnostics("
|
|
1001
|
+
"self, doc_uri: str, diagnostics: List[Diagnostic], version: Optional[int] = None)`"
|
|
1002
|
+
"will be replaced with `publish_diagnostics(self, params: PublishDiagnosticsParams)`"
|
|
1003
|
+
logging.warning(message)
|
|
1004
|
+
|
|
1005
|
+
params = self._construct_publish_diagnostic_type(
|
|
1006
|
+
params_or_uri, diagnostics, version, **kwargs
|
|
1007
|
+
)
|
|
1008
|
+
else:
|
|
1009
|
+
params = params_or_uri
|
|
1010
|
+
return params
|
|
1011
|
+
|
|
1012
|
+
def _construct_publish_diagnostic_type(
|
|
1013
|
+
self,
|
|
1014
|
+
uri: str,
|
|
1015
|
+
diagnostics: Optional[List[Diagnostic]],
|
|
1016
|
+
version: Optional[int],
|
|
1017
|
+
**kwargs,
|
|
1018
|
+
) -> PublishDiagnosticsParams:
|
|
1019
|
+
if diagnostics is None:
|
|
1020
|
+
diagnostics = []
|
|
1021
|
+
|
|
1022
|
+
args = {
|
|
1023
|
+
**{"uri": uri, "diagnostics": diagnostics, "version": version},
|
|
1024
|
+
**kwargs,
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
params = PublishDiagnosticsParams(**args) # type:ignore
|
|
1028
|
+
return params
|
|
1029
|
+
|
|
1030
|
+
def publish_diagnostics(
|
|
1031
|
+
self,
|
|
1032
|
+
params_or_uri: Union[str, PublishDiagnosticsParams],
|
|
1033
|
+
diagnostics: Optional[List[Diagnostic]] = None,
|
|
1034
|
+
version: Optional[int] = None,
|
|
1035
|
+
**kwargs,
|
|
1036
|
+
):
|
|
1037
|
+
"""Sends diagnostic notification to the client.
|
|
1038
|
+
|
|
1039
|
+
.. deprecated:: 1.0.1
|
|
1040
|
+
|
|
1041
|
+
Passing ``(uri, diagnostics, version)`` as arguments is deprecated.
|
|
1042
|
+
Pass an instance of :class:`~lsprotocol.types.PublishDiagnosticParams`
|
|
1043
|
+
instead.
|
|
1044
|
+
|
|
1045
|
+
Parameters
|
|
1046
|
+
----------
|
|
1047
|
+
params_or_uri
|
|
1048
|
+
The :class:`~lsprotocol.types.PublishDiagnosticParams` to send to the client.
|
|
1049
|
+
|
|
1050
|
+
diagnostics
|
|
1051
|
+
*Deprecated*. The diagnostics to publish
|
|
1052
|
+
|
|
1053
|
+
version
|
|
1054
|
+
*Deprecated*: The version number
|
|
1055
|
+
"""
|
|
1056
|
+
params = self._publish_diagnostics_deprecator(
|
|
1057
|
+
params_or_uri, diagnostics, version, **kwargs
|
|
1058
|
+
)
|
|
1059
|
+
self.notify(TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS, params)
|
|
1060
|
+
|
|
1061
|
+
def register_capability(
|
|
1062
|
+
self, params: RegistrationParams, callback: Optional[Callable[[], None]] = None
|
|
1063
|
+
) -> Future:
|
|
1064
|
+
"""Register a new capability on the client.
|
|
1065
|
+
|
|
1066
|
+
Args:
|
|
1067
|
+
params(RegistrationParams): RegistrationParams from lsp specs
|
|
1068
|
+
callback(callable): Callabe which will be called after
|
|
1069
|
+
response from the client is received
|
|
1070
|
+
Returns:
|
|
1071
|
+
concurrent.futures.Future object that will be resolved once a
|
|
1072
|
+
response has been received
|
|
1073
|
+
"""
|
|
1074
|
+
return self.send_request(CLIENT_REGISTER_CAPABILITY, params, callback)
|
|
1075
|
+
|
|
1076
|
+
def register_capability_async(self, params: RegistrationParams) -> asyncio.Future:
|
|
1077
|
+
"""Register a new capability on the client.
|
|
1078
|
+
|
|
1079
|
+
Args:
|
|
1080
|
+
params(RegistrationParams): RegistrationParams from lsp specs
|
|
1081
|
+
|
|
1082
|
+
Returns:
|
|
1083
|
+
asyncio.Future object that will be resolved once a
|
|
1084
|
+
response has been received
|
|
1085
|
+
"""
|
|
1086
|
+
return asyncio.wrap_future(self.register_capability(params, None))
|
|
1087
|
+
|
|
1088
|
+
def semantic_tokens_refresh(
|
|
1089
|
+
self, callback: Optional[Callable[[], None]] = None
|
|
1090
|
+
) -> Future:
|
|
1091
|
+
"""Requesting a refresh of all semantic tokens.
|
|
1092
|
+
|
|
1093
|
+
Args:
|
|
1094
|
+
callback(callable): Callabe which will be called after
|
|
1095
|
+
response from the client is received
|
|
1096
|
+
|
|
1097
|
+
Returns:
|
|
1098
|
+
concurrent.futures.Future object that will be resolved once a
|
|
1099
|
+
response has been received
|
|
1100
|
+
"""
|
|
1101
|
+
return self.send_request(WORKSPACE_SEMANTIC_TOKENS_REFRESH, callback=callback)
|
|
1102
|
+
|
|
1103
|
+
def semantic_tokens_refresh_async(self) -> asyncio.Future:
|
|
1104
|
+
"""Requesting a refresh of all semantic tokens.
|
|
1105
|
+
|
|
1106
|
+
Returns:
|
|
1107
|
+
asyncio.Future object that will be resolved once a
|
|
1108
|
+
response has been received
|
|
1109
|
+
"""
|
|
1110
|
+
return asyncio.wrap_future(self.semantic_tokens_refresh(None))
|
|
1111
|
+
|
|
1112
|
+
def show_document(
|
|
1113
|
+
self,
|
|
1114
|
+
params: ShowDocumentParams,
|
|
1115
|
+
callback: Optional[ShowDocumentCallbackType] = None,
|
|
1116
|
+
) -> Future:
|
|
1117
|
+
"""Display a particular document in the user interface.
|
|
1118
|
+
|
|
1119
|
+
Args:
|
|
1120
|
+
params(ShowDocumentParams): ShowDocumentParams from lsp specs
|
|
1121
|
+
callback(callable): Callabe which will be called after
|
|
1122
|
+
response from the client is received
|
|
1123
|
+
|
|
1124
|
+
Returns:
|
|
1125
|
+
concurrent.futures.Future object that will be resolved once a
|
|
1126
|
+
response has been received
|
|
1127
|
+
"""
|
|
1128
|
+
return self.send_request(WINDOW_SHOW_DOCUMENT, params, callback)
|
|
1129
|
+
|
|
1130
|
+
def show_document_async(self, params: ShowDocumentParams) -> asyncio.Future:
|
|
1131
|
+
"""Display a particular document in the user interface.
|
|
1132
|
+
|
|
1133
|
+
Args:
|
|
1134
|
+
params(ShowDocumentParams): ShowDocumentParams from lsp specs
|
|
1135
|
+
|
|
1136
|
+
Returns:
|
|
1137
|
+
asyncio.Future object that will be resolved once a
|
|
1138
|
+
response has been received
|
|
1139
|
+
"""
|
|
1140
|
+
return asyncio.wrap_future(self.show_document(params, None))
|
|
1141
|
+
|
|
1142
|
+
def show_message(self, message, msg_type=MessageType.Info):
|
|
1143
|
+
"""Sends message to the client to display message."""
|
|
1144
|
+
self.notify(
|
|
1145
|
+
WINDOW_SHOW_MESSAGE, ShowMessageParams(type=msg_type, message=message)
|
|
1146
|
+
)
|
|
1147
|
+
|
|
1148
|
+
def show_message_log(self, message, msg_type=MessageType.Log):
|
|
1149
|
+
"""Sends message to the client's output channel."""
|
|
1150
|
+
self.notify(
|
|
1151
|
+
WINDOW_LOG_MESSAGE, LogMessageParams(type=msg_type, message=message)
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
def unregister_capability(
|
|
1155
|
+
self,
|
|
1156
|
+
params: UnregistrationParams,
|
|
1157
|
+
callback: Optional[Callable[[], None]] = None,
|
|
1158
|
+
) -> Future:
|
|
1159
|
+
"""Unregister a new capability on the client.
|
|
1160
|
+
|
|
1161
|
+
Args:
|
|
1162
|
+
params(UnregistrationParams): UnregistrationParams from lsp specs
|
|
1163
|
+
callback(callable): Callabe which will be called after
|
|
1164
|
+
response from the client is received
|
|
1165
|
+
Returns:
|
|
1166
|
+
concurrent.futures.Future object that will be resolved once a
|
|
1167
|
+
response has been received
|
|
1168
|
+
"""
|
|
1169
|
+
return self.send_request(CLIENT_UNREGISTER_CAPABILITY, params, callback)
|
|
1170
|
+
|
|
1171
|
+
def unregister_capability_async(
|
|
1172
|
+
self, params: UnregistrationParams
|
|
1173
|
+
) -> asyncio.Future:
|
|
1174
|
+
"""Unregister a new capability on the client.
|
|
1175
|
+
|
|
1176
|
+
Args:
|
|
1177
|
+
params(UnregistrationParams): UnregistrationParams from lsp specs
|
|
1178
|
+
callback(callable): Callabe which will be called after
|
|
1179
|
+
response from the client is received
|
|
1180
|
+
Returns:
|
|
1181
|
+
asyncio.Future object that will be resolved once a
|
|
1182
|
+
response has been received
|
|
1183
|
+
"""
|
|
1184
|
+
return asyncio.wrap_future(self.unregister_capability(params, None))
|