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.

Files changed (82) hide show
  1. jaclang/__init__.py +2 -1
  2. jaclang/cli/__jac_gen__/__init__.py +0 -0
  3. jaclang/cli/__jac_gen__/cli.py +175 -0
  4. jaclang/cli/__jac_gen__/cmds.py +132 -0
  5. jaclang/cli/cmds.jac +3 -0
  6. jaclang/cli/impl/__jac_gen__/__init__.py +0 -0
  7. jaclang/cli/impl/__jac_gen__/cli_impl.py +16 -0
  8. jaclang/cli/impl/__jac_gen__/cmds_impl.py +26 -0
  9. jaclang/cli/impl/cmds_impl.jac +17 -3
  10. jaclang/core/__jac_gen__/__init__.py +0 -0
  11. jaclang/core/__jac_gen__/primitives.py +567 -0
  12. jaclang/core/impl/__jac_gen__/__init__.py +0 -0
  13. jaclang/core/impl/__jac_gen__/arch_impl.py +24 -0
  14. jaclang/core/impl/__jac_gen__/element_impl.py +26 -0
  15. jaclang/core/impl/__jac_gen__/exec_ctx_impl.py +12 -0
  16. jaclang/core/impl/__jac_gen__/memory_impl.py +14 -0
  17. jaclang/core/impl/element_impl.jac +2 -2
  18. jaclang/core/primitives.jac +1 -0
  19. jaclang/jac/absyntree.py +65 -42
  20. jaclang/jac/constant.py +4 -0
  21. jaclang/jac/importer.py +18 -60
  22. jaclang/jac/langserve.py +26 -0
  23. jaclang/jac/lexer.py +9 -1
  24. jaclang/jac/parser.py +135 -123
  25. jaclang/jac/passes/blue/ast_build_pass.py +410 -353
  26. jaclang/jac/passes/blue/blue_pygen_pass.py +15 -0
  27. jaclang/jac/passes/blue/decl_def_match_pass.py +33 -21
  28. jaclang/jac/passes/blue/import_pass.py +1 -1
  29. jaclang/jac/passes/blue/pyout_pass.py +47 -12
  30. jaclang/jac/passes/blue/sym_tab_build_pass.py +38 -127
  31. jaclang/jac/passes/blue/tests/test_ast_build_pass.py +2 -2
  32. jaclang/jac/passes/blue/tests/test_blue_pygen_pass.py +9 -30
  33. jaclang/jac/passes/blue/tests/test_decl_def_match_pass.py +13 -13
  34. jaclang/jac/passes/blue/tests/test_sym_tab_build_pass.py +6 -4
  35. jaclang/jac/passes/ir_pass.py +1 -1
  36. jaclang/jac/passes/purple/__jac_gen__/__init__.py +0 -0
  37. jaclang/jac/passes/purple/__jac_gen__/analyze_pass.py +37 -0
  38. jaclang/jac/passes/purple/__jac_gen__/purple_pygen_pass.py +305 -0
  39. jaclang/jac/passes/purple/impl/__jac_gen__/__init__.py +0 -0
  40. jaclang/jac/passes/purple/impl/__jac_gen__/purple_pygen_pass_impl.py +23 -0
  41. jaclang/jac/symtable.py +12 -4
  42. jaclang/jac/tests/fixtures/__jac_gen__/__init__.py +0 -0
  43. jaclang/jac/tests/fixtures/__jac_gen__/hello_world.py +16 -0
  44. jaclang/jac/tests/fixtures/fam.jac +7 -8
  45. jaclang/jac/transform.py +4 -3
  46. jaclang/jac/transpiler.py +13 -9
  47. jaclang/utils/fstring_parser.py +2 -2
  48. jaclang/utils/helpers.py +41 -0
  49. jaclang/utils/test.py +30 -0
  50. jaclang/vendor/__init__.py +1 -0
  51. jaclang/vendor/pygls/__init__.py +25 -0
  52. jaclang/vendor/pygls/capabilities.py +502 -0
  53. jaclang/vendor/pygls/client.py +176 -0
  54. jaclang/vendor/pygls/constants.py +26 -0
  55. jaclang/vendor/pygls/exceptions.py +220 -0
  56. jaclang/vendor/pygls/feature_manager.py +241 -0
  57. jaclang/vendor/pygls/lsp/__init__.py +139 -0
  58. jaclang/vendor/pygls/lsp/client.py +2224 -0
  59. jaclang/vendor/pygls/lsprotocol/__init__.py +2 -0
  60. jaclang/vendor/pygls/lsprotocol/_hooks.py +1233 -0
  61. jaclang/vendor/pygls/lsprotocol/converters.py +17 -0
  62. jaclang/vendor/pygls/lsprotocol/types.py +12820 -0
  63. jaclang/vendor/pygls/lsprotocol/validators.py +47 -0
  64. jaclang/vendor/pygls/progress.py +79 -0
  65. jaclang/vendor/pygls/protocol.py +1184 -0
  66. jaclang/vendor/pygls/server.py +620 -0
  67. jaclang/vendor/pygls/uris.py +184 -0
  68. jaclang/vendor/pygls/workspace/__init__.py +81 -0
  69. jaclang/vendor/pygls/workspace/position.py +204 -0
  70. jaclang/vendor/pygls/workspace/text_document.py +234 -0
  71. jaclang/vendor/pygls/workspace/workspace.py +311 -0
  72. {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/METADATA +1 -1
  73. jaclang-0.0.8.dist-info/RECORD +118 -0
  74. jaclang/core/jaclang.jac +0 -62
  75. jaclang-0.0.6.dist-info/RECORD +0 -76
  76. /jaclang/{utils → vendor}/sly/__init__.py +0 -0
  77. /jaclang/{utils → vendor}/sly/docparse.py +0 -0
  78. /jaclang/{utils → vendor}/sly/lex.py +0 -0
  79. /jaclang/{utils → vendor}/sly/yacc.py +0 -0
  80. {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/WHEEL +0 -0
  81. {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/entry_points.txt +0 -0
  82. {jaclang-0.0.6.dist-info → jaclang-0.0.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,176 @@
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
+ import asyncio
18
+ import logging
19
+ import re
20
+ from threading import Event
21
+ from typing import Any
22
+ from typing import Callable
23
+ from typing import List
24
+ from typing import Optional
25
+ from typing import Type
26
+ from typing import Union
27
+
28
+ from cattrs import Converter
29
+
30
+ from jaclang.vendor.pygls.exceptions import PyglsError, JsonRpcException
31
+ from jaclang.vendor.pygls.protocol import JsonRPCProtocol, default_converter
32
+
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ async def aio_readline(stop_event, reader, message_handler):
38
+ CONTENT_LENGTH_PATTERN = re.compile(rb"^Content-Length: (\d+)\r\n$")
39
+
40
+ # Initialize message buffer
41
+ message = []
42
+ content_length = 0
43
+
44
+ while not stop_event.is_set():
45
+ # Read a header line
46
+ header = await reader.readline()
47
+ if not header:
48
+ break
49
+ message.append(header)
50
+
51
+ # Extract content length if possible
52
+ if not content_length:
53
+ match = CONTENT_LENGTH_PATTERN.fullmatch(header)
54
+ if match:
55
+ content_length = int(match.group(1))
56
+ logger.debug("Content length: %s", content_length)
57
+
58
+ # Check if all headers have been read (as indicated by an empty line \r\n)
59
+ if content_length and not header.strip():
60
+ # Read body
61
+ body = await reader.readexactly(content_length)
62
+ if not body:
63
+ break
64
+ message.append(body)
65
+
66
+ # Pass message to protocol
67
+ message_handler(b"".join(message))
68
+
69
+ # Reset the buffer
70
+ message = []
71
+ content_length = 0
72
+
73
+
74
+ class JsonRPCClient:
75
+ """Base JSON-RPC client."""
76
+
77
+ def __init__(
78
+ self,
79
+ protocol_cls: Type[JsonRPCProtocol] = JsonRPCProtocol,
80
+ converter_factory: Callable[[], Converter] = default_converter,
81
+ ):
82
+ # Strictly speaking `JsonRPCProtocol` wants a `LanguageServer`, not a
83
+ # `JsonRPCClient`. However there similar enough for our purposes, which is
84
+ # that this client will mostly be used in testing contexts.
85
+ self.protocol = protocol_cls(self, converter_factory()) # type: ignore
86
+
87
+ self._server: Optional[asyncio.subprocess.Process] = None
88
+ self._stop_event = Event()
89
+ self._async_tasks: List[asyncio.Task] = []
90
+
91
+ @property
92
+ def stopped(self) -> bool:
93
+ """Return ``True`` if the client has been stopped."""
94
+ return self._stop_event.is_set()
95
+
96
+ def feature(
97
+ self,
98
+ feature_name: str,
99
+ options: Optional[Any] = None,
100
+ ):
101
+ """Decorator used to register LSP features.
102
+
103
+ Example
104
+ -------
105
+ ::
106
+
107
+ import logging
108
+ from jaclang.vendor.pygls.client import JsonRPCClient
109
+
110
+ ls = JsonRPCClient()
111
+
112
+ @ls.feature('window/logMessage')
113
+ def completions(ls, params):
114
+ logging.info("%s", params.message)
115
+ """
116
+ return self.protocol.fm.feature(feature_name, options)
117
+
118
+ async def start_io(self, cmd: str, *args, **kwargs):
119
+ """Start the given server and communicate with it over stdio."""
120
+
121
+ logger.debug("Starting server process: %s", " ".join([cmd, *args]))
122
+ server = await asyncio.create_subprocess_exec(
123
+ cmd,
124
+ *args,
125
+ stdout=asyncio.subprocess.PIPE,
126
+ stdin=asyncio.subprocess.PIPE,
127
+ stderr=asyncio.subprocess.PIPE,
128
+ **kwargs,
129
+ )
130
+
131
+ self.protocol.connection_made(server.stdin) # type: ignore
132
+ connection = asyncio.create_task(
133
+ aio_readline(self._stop_event, server.stdout, self.protocol.data_received)
134
+ )
135
+ notify_exit = asyncio.create_task(self._server_exit())
136
+
137
+ self._server = server
138
+ self._async_tasks.extend([connection, notify_exit])
139
+
140
+ async def _server_exit(self):
141
+ if self._server is not None:
142
+ await self._server.wait()
143
+ logger.debug(
144
+ "Server process %s exited with return code: %s",
145
+ self._server.pid,
146
+ self._server.returncode,
147
+ )
148
+ await self.server_exit(self._server)
149
+ self._stop_event.set()
150
+
151
+ async def server_exit(self, server: asyncio.subprocess.Process):
152
+ """Called when the server process exits."""
153
+
154
+ def _report_server_error(
155
+ self, error: Exception, source: Union[PyglsError, JsonRpcException]
156
+ ):
157
+ try:
158
+ self.report_server_error(error, source)
159
+ except Exception:
160
+ logger.error("Unable to report error", exc_info=True)
161
+
162
+ def report_server_error(
163
+ self, error: Exception, source: Union[PyglsError, JsonRpcException]
164
+ ):
165
+ """Called when the server does something unexpected e.g. respond with malformed
166
+ JSON."""
167
+
168
+ async def stop(self):
169
+ self._stop_event.set()
170
+
171
+ if self._server is not None and self._server.returncode is None:
172
+ logger.debug("Terminating server process: %s", self._server.pid)
173
+ self._server.terminate()
174
+
175
+ if len(self._async_tasks) > 0:
176
+ await asyncio.gather(*self._async_tasks)
@@ -0,0 +1,26 @@
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
+
18
+ # Dynamically assigned attributes
19
+ ATTR_EXECUTE_IN_THREAD = "execute_in_thread"
20
+ ATTR_COMMAND_TYPE = "command"
21
+ ATTR_FEATURE_TYPE = "feature"
22
+ ATTR_REGISTERED_NAME = "reg_name"
23
+ ATTR_REGISTERED_TYPE = "reg_type"
24
+
25
+ # Parameters
26
+ PARAM_LS = "ls"
@@ -0,0 +1,220 @@
1
+ ############################################################################
2
+ # Original work Copyright 2018 Palantir Technologies, Inc. #
3
+ # Original work licensed under the MIT License. #
4
+ # See ThirdPartyNotices.txt in the project root for license information. #
5
+ # All modifications Copyright (c) Open Law Library. All rights reserved. #
6
+ # #
7
+ # Licensed under the Apache License, Version 2.0 (the "License") #
8
+ # you may not use this file except in compliance with the License. #
9
+ # You may obtain a copy of the License at #
10
+ # #
11
+ # http: // www.apache.org/licenses/LICENSE-2.0 #
12
+ # #
13
+ # Unless required by applicable law or agreed to in writing, software #
14
+ # distributed under the License is distributed on an "AS IS" BASIS, #
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
16
+ # See the License for the specific language governing permissions and #
17
+ # limitations under the License. #
18
+ ############################################################################
19
+ import traceback
20
+ from typing import Set
21
+ from typing import Type
22
+
23
+
24
+ class JsonRpcException(Exception):
25
+ """A class used as a base class for json rpc exceptions."""
26
+
27
+ def __init__(self, message=None, code=None, data=None):
28
+ message = message or getattr(self.__class__, "MESSAGE")
29
+ super().__init__(message)
30
+ self.message = message
31
+ self.code = code or getattr(self.__class__, "CODE")
32
+ self.data = data
33
+
34
+ def __eq__(self, other):
35
+ return (
36
+ isinstance(other, self.__class__)
37
+ and self.code == other.code
38
+ and self.message == other.message
39
+ )
40
+
41
+ def __hash__(self):
42
+ return hash((self.code, self.message))
43
+
44
+ @staticmethod
45
+ def from_error(error):
46
+ for exc_class in _EXCEPTIONS:
47
+ if exc_class.supports_code(error.code):
48
+ return exc_class(
49
+ code=error.code, message=error.message, data=error.data
50
+ )
51
+
52
+ return JsonRpcException(code=error.code, message=error.message, data=error.data)
53
+
54
+ @classmethod
55
+ def supports_code(cls, code):
56
+ # Defaults to UnknownErrorCode
57
+ return getattr(cls, "CODE", -32001) == code
58
+
59
+ def to_dict(self):
60
+ exception_dict = {
61
+ "code": self.code,
62
+ "message": self.message,
63
+ }
64
+ if self.data is not None:
65
+ exception_dict["data"] = str(self.data)
66
+ return exception_dict
67
+
68
+
69
+ class JsonRpcInternalError(JsonRpcException):
70
+ CODE = -32603
71
+ MESSAGE = "Internal Error"
72
+
73
+ @classmethod
74
+ def of(cls, exc_info):
75
+ exc_type, exc_value, exc_tb = exc_info
76
+ return cls(
77
+ message="".join(
78
+ traceback.format_exception_only(exc_type, exc_value)
79
+ ).strip(),
80
+ data={"traceback": traceback.format_tb(exc_tb)},
81
+ )
82
+
83
+
84
+ class JsonRpcInvalidParams(JsonRpcException):
85
+ CODE = -32602
86
+ MESSAGE = "Invalid Params"
87
+
88
+
89
+ class JsonRpcInvalidRequest(JsonRpcException):
90
+ CODE = -32600
91
+ MESSAGE = "Invalid Request"
92
+
93
+
94
+ class JsonRpcMethodNotFound(JsonRpcException):
95
+ CODE = -32601
96
+ MESSAGE = "Method Not Found"
97
+
98
+ @classmethod
99
+ def of(cls, method):
100
+ return cls(message=cls.MESSAGE + ": " + method)
101
+
102
+
103
+ class JsonRpcParseError(JsonRpcException):
104
+ CODE = -32700
105
+ MESSAGE = "Parse Error"
106
+
107
+
108
+ class JsonRpcRequestCancelled(JsonRpcException):
109
+ CODE = -32800
110
+ MESSAGE = "Request Cancelled"
111
+
112
+
113
+ class JsonRpcContentModified(JsonRpcException):
114
+ CODE = -32801
115
+ MESSAGE = "Content Modified"
116
+
117
+
118
+ class JsonRpcServerNotInitialized(JsonRpcException):
119
+ CODE = -32002
120
+ MESSAGE = "ServerNotInitialized"
121
+
122
+
123
+ class JsonRpcUnknownErrorCode(JsonRpcException):
124
+ CODE = -32001
125
+ MESSAGE = "UnknownErrorCode"
126
+
127
+
128
+ class JsonRpcReservedErrorRangeStart(JsonRpcException):
129
+ CODE = -32099
130
+ MESSAGE = "jsonrpcReservedErrorRangeStart"
131
+
132
+
133
+ class JsonRpcReservedErrorRangeEnd(JsonRpcException):
134
+ CODE = -32000
135
+ MESSAGE = "jsonrpcReservedErrorRangeEnd"
136
+
137
+
138
+ class LspReservedErrorRangeStart(JsonRpcException):
139
+ CODE = -32899
140
+ MESSAGE = "lspReservedErrorRangeStart"
141
+
142
+
143
+ class LspReservedErrorRangeEnd(JsonRpcException):
144
+ CODE = -32800
145
+ MESSAGE = "lspReservedErrorRangeEnd"
146
+
147
+
148
+ class JsonRpcServerError(JsonRpcException):
149
+ def __init__(self, message, code, data=None):
150
+ if not _is_server_error_code(code):
151
+ raise ValueError("Error code should be in range -32099 - -32000")
152
+ super().__init__(message=message, code=code, data=data)
153
+
154
+ @classmethod
155
+ def supports_code(cls, code):
156
+ return _is_server_error_code(code)
157
+
158
+
159
+ def _is_server_error_code(code):
160
+ return -32099 <= code <= -32000
161
+
162
+
163
+ _EXCEPTIONS: Set[Type[JsonRpcException]] = {
164
+ JsonRpcInternalError,
165
+ JsonRpcInvalidParams,
166
+ JsonRpcInvalidRequest,
167
+ JsonRpcMethodNotFound,
168
+ JsonRpcParseError,
169
+ JsonRpcRequestCancelled,
170
+ JsonRpcServerError,
171
+ }
172
+
173
+
174
+ class PyglsError(Exception):
175
+ pass
176
+
177
+
178
+ class CommandAlreadyRegisteredError(PyglsError):
179
+ def __init__(self, command_name):
180
+ self.command_name = command_name
181
+
182
+ def __repr__(self):
183
+ return f'Command "{self.command_name}" is already registered.'
184
+
185
+
186
+ class FeatureAlreadyRegisteredError(PyglsError):
187
+ def __init__(self, feature_name):
188
+ self.feature_name = feature_name
189
+
190
+ def __repr__(self):
191
+ return f'Feature "{self.feature_name}" is already registered.'
192
+
193
+
194
+ class FeatureRequestError(PyglsError):
195
+ pass
196
+
197
+
198
+ class FeatureNotificationError(PyglsError):
199
+ pass
200
+
201
+
202
+ class MethodTypeNotRegisteredError(PyglsError):
203
+ def __init__(self, name):
204
+ self.name = name
205
+
206
+ def __repr__(self):
207
+ return f'"{self.name}" is not added to `pygls.lsp.LSP_METHODS_MAP`.'
208
+
209
+
210
+ class ThreadDecoratorError(PyglsError):
211
+ pass
212
+
213
+
214
+ class ValidationError(PyglsError):
215
+ def __init__(self, errors=None):
216
+ self.errors = errors or []
217
+
218
+ def __repr__(self):
219
+ opt_errs = "\n-".join([e for e in self.errors])
220
+ return f"Missing options: {opt_errs}"
@@ -0,0 +1,241 @@
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
+ import asyncio
18
+ import functools
19
+ import inspect
20
+ import itertools
21
+ import logging
22
+ from typing import Any, Callable, Dict, Optional, get_type_hints
23
+
24
+ from jaclang.vendor.pygls.constants import (
25
+ ATTR_COMMAND_TYPE,
26
+ ATTR_EXECUTE_IN_THREAD,
27
+ ATTR_FEATURE_TYPE,
28
+ ATTR_REGISTERED_NAME,
29
+ ATTR_REGISTERED_TYPE,
30
+ PARAM_LS,
31
+ )
32
+ from jaclang.vendor.pygls.exceptions import (
33
+ CommandAlreadyRegisteredError,
34
+ FeatureAlreadyRegisteredError,
35
+ ThreadDecoratorError,
36
+ ValidationError,
37
+ )
38
+ from jaclang.vendor.pygls.lsp import get_method_options_type, is_instance
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ def assign_help_attrs(f, reg_name, reg_type):
44
+ setattr(f, ATTR_REGISTERED_NAME, reg_name)
45
+ setattr(f, ATTR_REGISTERED_TYPE, reg_type)
46
+
47
+
48
+ def assign_thread_attr(f):
49
+ setattr(f, ATTR_EXECUTE_IN_THREAD, True)
50
+
51
+
52
+ def get_help_attrs(f):
53
+ return getattr(f, ATTR_REGISTERED_NAME, None), getattr(
54
+ f, ATTR_REGISTERED_TYPE, None
55
+ )
56
+
57
+
58
+ def has_ls_param_or_annotation(f, annotation):
59
+ """Returns true if callable has first parameter named `ls` or type of
60
+ annotation"""
61
+ try:
62
+ sig = inspect.signature(f)
63
+ first_p = next(itertools.islice(sig.parameters.values(), 0, 1))
64
+ return first_p.name == PARAM_LS or get_type_hints(f)[first_p.name] == annotation
65
+ except Exception:
66
+ return False
67
+
68
+
69
+ def is_thread_function(f):
70
+ return getattr(f, ATTR_EXECUTE_IN_THREAD, False)
71
+
72
+
73
+ def wrap_with_server(f, server):
74
+ """Returns a new callable/coroutine with server as first argument."""
75
+ if not has_ls_param_or_annotation(f, type(server)):
76
+ return f
77
+
78
+ if asyncio.iscoroutinefunction(f):
79
+
80
+ async def wrapped(*args, **kwargs):
81
+ return await f(server, *args, **kwargs)
82
+
83
+ else:
84
+ wrapped = functools.partial(f, server)
85
+ if is_thread_function(f):
86
+ assign_thread_attr(wrapped)
87
+
88
+ return wrapped
89
+
90
+
91
+ class FeatureManager:
92
+ """A class for managing server features.
93
+
94
+ Attributes:
95
+ _builtin_features(dict): Predefined set of lsp methods
96
+ _feature_options(dict): Registered feature's options
97
+ _features(dict): Registered features
98
+ _commands(dict): Registered commands
99
+ server(LanguageServer): Reference to the language server
100
+ If passed, server will be passed to registered
101
+ features/commands with first parameter:
102
+ 1. ls - parameter naming convention
103
+ 2. name: LanguageServer - add typings
104
+ """
105
+
106
+ def __init__(self, server=None):
107
+ self._builtin_features = {}
108
+ self._feature_options = {}
109
+ self._features = {}
110
+ self._commands = {}
111
+ self.server = server
112
+
113
+ def add_builtin_feature(self, feature_name: str, func: Callable) -> None:
114
+ """Registers builtin (predefined) feature."""
115
+ self._builtin_features[feature_name] = func
116
+ logger.info("Registered builtin feature %s", feature_name)
117
+
118
+ @property
119
+ def builtin_features(self) -> Dict:
120
+ """Returns server builtin features."""
121
+ return self._builtin_features
122
+
123
+ def command(self, command_name: str) -> Callable:
124
+ """Decorator used to register custom commands.
125
+
126
+ Example:
127
+ @ls.command('myCustomCommand')
128
+ """
129
+
130
+ def decorator(f):
131
+ # Validate
132
+ if command_name is None or command_name.strip() == "":
133
+ logger.error("Missing command name.")
134
+ raise ValidationError("Command name is required.")
135
+
136
+ # Check if not already registered
137
+ if command_name in self._commands:
138
+ logger.error('Command "%s" is already registered.', command_name)
139
+ raise CommandAlreadyRegisteredError(command_name)
140
+
141
+ assign_help_attrs(f, command_name, ATTR_COMMAND_TYPE)
142
+
143
+ wrapped = wrap_with_server(f, self.server)
144
+ # Assign help attributes for thread decorator
145
+ assign_help_attrs(wrapped, command_name, ATTR_COMMAND_TYPE)
146
+
147
+ self._commands[command_name] = wrapped
148
+
149
+ logger.info('Command "%s" is successfully registered.', command_name)
150
+
151
+ return f
152
+
153
+ return decorator
154
+
155
+ @property
156
+ def commands(self) -> Dict:
157
+ """Returns registered custom commands."""
158
+ return self._commands
159
+
160
+ def feature(
161
+ self,
162
+ feature_name: str,
163
+ options: Optional[Any] = None,
164
+ ) -> Callable:
165
+ """Decorator used to register LSP features.
166
+
167
+ Example:
168
+ @ls.feature('textDocument/completion', CompletionItems(trigger_characters=['.']))
169
+ """
170
+
171
+ def decorator(f):
172
+ # Validate
173
+ if feature_name is None or feature_name.strip() == "":
174
+ logger.error("Missing feature name.")
175
+ raise ValidationError("Feature name is required.")
176
+
177
+ # Add feature if not exists
178
+ if feature_name in self._features:
179
+ logger.error('Feature "%s" is already registered.', feature_name)
180
+ raise FeatureAlreadyRegisteredError(feature_name)
181
+
182
+ assign_help_attrs(f, feature_name, ATTR_FEATURE_TYPE)
183
+
184
+ wrapped = wrap_with_server(f, self.server)
185
+ # Assign help attributes for thread decorator
186
+ assign_help_attrs(wrapped, feature_name, ATTR_FEATURE_TYPE)
187
+
188
+ self._features[feature_name] = wrapped
189
+
190
+ if options:
191
+ options_type = get_method_options_type(feature_name)
192
+ if options_type and not is_instance(options, options_type):
193
+ raise TypeError(
194
+ (
195
+ f'Options of method "{feature_name}"'
196
+ f" should be instance of type {options_type}"
197
+ )
198
+ )
199
+ self._feature_options[feature_name] = options
200
+
201
+ logger.info('Registered "%s" with options "%s"', feature_name, options)
202
+
203
+ return f
204
+
205
+ return decorator
206
+
207
+ @property
208
+ def feature_options(self) -> Dict:
209
+ """Returns feature options for registered features."""
210
+ return self._feature_options
211
+
212
+ @property
213
+ def features(self) -> Dict:
214
+ """Returns registered features"""
215
+ return self._features
216
+
217
+ def thread(self) -> Callable:
218
+ """Decorator that mark function to execute it in a thread."""
219
+
220
+ def decorator(f):
221
+ if asyncio.iscoroutinefunction(f):
222
+ raise ThreadDecoratorError(
223
+ f'Thread decorator cannot be used with async functions "{f.__name__}"'
224
+ )
225
+
226
+ # Allow any decorator order
227
+ try:
228
+ reg_name = getattr(f, ATTR_REGISTERED_NAME)
229
+ reg_type = getattr(f, ATTR_REGISTERED_TYPE)
230
+
231
+ if reg_type is ATTR_FEATURE_TYPE:
232
+ assign_thread_attr(self.features[reg_name])
233
+ elif reg_type is ATTR_COMMAND_TYPE:
234
+ assign_thread_attr(self.commands[reg_name])
235
+
236
+ except AttributeError:
237
+ assign_thread_attr(f)
238
+
239
+ return f
240
+
241
+ return decorator