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,184 @@
1
+ ############################################################################
2
+ # Original work Copyright 2017 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
+ """
20
+ A collection of URI utilities with logic built on the VSCode URI library.
21
+
22
+ https://github.com/Microsoft/vscode-uri/blob/e59cab84f5df6265aed18ae5f43552d3eef13bb9/lib/index.ts
23
+ """
24
+ from typing import Optional, Tuple
25
+
26
+ import re
27
+ from urllib import parse
28
+
29
+ from jaclang.vendor.pygls import IS_WIN
30
+
31
+ RE_DRIVE_LETTER_PATH = re.compile(r"^\/[a-zA-Z]:")
32
+
33
+ URLParts = Tuple[str, str, str, str, str, str]
34
+
35
+
36
+ def _normalize_win_path(path: str):
37
+ netloc = ""
38
+
39
+ # normalize to fwd-slashes on windows,
40
+ # on other systems bwd-slashes are valid
41
+ # filename character, eg /f\oo/ba\r.txt
42
+ if IS_WIN:
43
+ path = path.replace("\\", "/")
44
+
45
+ # check for authority as used in UNC shares
46
+ # or use the path as given
47
+ if path[:2] == "//":
48
+ idx = path.index("/", 2)
49
+ if idx == -1:
50
+ netloc = path[2:]
51
+ else:
52
+ netloc = path[2:idx]
53
+ path = path[idx:]
54
+
55
+ # Ensure that path starts with a slash
56
+ # or that it is at least a slash
57
+ if not path.startswith("/"):
58
+ path = "/" + path
59
+
60
+ # Normalize drive paths to lower case
61
+ if RE_DRIVE_LETTER_PATH.match(path):
62
+ path = path[0] + path[1].lower() + path[2:]
63
+
64
+ return path, netloc
65
+
66
+
67
+ def from_fs_path(path: str):
68
+ """Returns a URI for the given filesystem path."""
69
+ try:
70
+ scheme = "file"
71
+ params, query, fragment = "", "", ""
72
+ path, netloc = _normalize_win_path(path)
73
+ return urlunparse((scheme, netloc, path, params, query, fragment))
74
+ except (AttributeError, TypeError):
75
+ return None
76
+
77
+
78
+ def to_fs_path(uri: str):
79
+ """
80
+ Returns the filesystem path of the given URI.
81
+
82
+ Will handle UNC paths and normalize windows drive letters to lower-case.
83
+ Also uses the platform specific path separator. Will *not* validate the
84
+ path for invalid characters and semantics.
85
+ Will *not* look at the scheme of this URI.
86
+ """
87
+ try:
88
+ # scheme://netloc/path;parameters?query#fragment
89
+ scheme, netloc, path, _, _, _ = urlparse(uri)
90
+
91
+ if netloc and path and scheme == "file":
92
+ # unc path: file://shares/c$/far/boo
93
+ value = f"//{netloc}{path}"
94
+
95
+ elif RE_DRIVE_LETTER_PATH.match(path):
96
+ # windows drive letter: file:///C:/far/boo
97
+ value = path[1].lower() + path[2:]
98
+
99
+ else:
100
+ # Other path
101
+ value = path
102
+
103
+ if IS_WIN:
104
+ value = value.replace("/", "\\")
105
+
106
+ return value
107
+ except TypeError:
108
+ return None
109
+
110
+
111
+ def uri_scheme(uri: str):
112
+ try:
113
+ return urlparse(uri)[0]
114
+ except (TypeError, IndexError):
115
+ return None
116
+
117
+
118
+ # TODO: Use `URLParts` type
119
+ def uri_with(
120
+ uri: str,
121
+ scheme: Optional[str] = None,
122
+ netloc: Optional[str] = None,
123
+ path: Optional[str] = None,
124
+ params: Optional[str] = None,
125
+ query: Optional[str] = None,
126
+ fragment: Optional[str] = None,
127
+ ):
128
+ """
129
+ Return a URI with the given part(s) replaced.
130
+ Parts are decoded / encoded.
131
+ """
132
+ old_scheme, old_netloc, old_path, old_params, old_query, old_fragment = urlparse(
133
+ uri
134
+ )
135
+
136
+ if path is None:
137
+ raise Exception("`path` must not be None")
138
+
139
+ path, _ = _normalize_win_path(path)
140
+ return urlunparse(
141
+ (
142
+ scheme or old_scheme,
143
+ netloc or old_netloc,
144
+ path or old_path,
145
+ params or old_params,
146
+ query or old_query,
147
+ fragment or old_fragment,
148
+ )
149
+ )
150
+
151
+
152
+ def urlparse(uri: str):
153
+ """Parse and decode the parts of a URI."""
154
+ scheme, netloc, path, params, query, fragment = parse.urlparse(uri)
155
+ return (
156
+ parse.unquote(scheme),
157
+ parse.unquote(netloc),
158
+ parse.unquote(path),
159
+ parse.unquote(params),
160
+ parse.unquote(query),
161
+ parse.unquote(fragment),
162
+ )
163
+
164
+
165
+ def urlunparse(parts: URLParts) -> str:
166
+ """Unparse and encode parts of a URI."""
167
+ scheme, netloc, path, params, query, fragment = parts
168
+
169
+ # Avoid encoding the windows drive letter colon
170
+ if RE_DRIVE_LETTER_PATH.match(path):
171
+ quoted_path = path[:3] + parse.quote(path[3:])
172
+ else:
173
+ quoted_path = parse.quote(path)
174
+
175
+ return parse.urlunparse(
176
+ (
177
+ parse.quote(scheme),
178
+ parse.quote(netloc),
179
+ quoted_path,
180
+ parse.quote(params),
181
+ parse.quote(query),
182
+ parse.quote(fragment),
183
+ )
184
+ )
@@ -0,0 +1,81 @@
1
+ from typing import List
2
+ import warnings
3
+
4
+ from jaclang.vendor.pygls.lsprotocol import types
5
+
6
+ from .workspace import Workspace
7
+ from .text_document import TextDocument
8
+ from .position import Position
9
+
10
+ Workspace = Workspace
11
+ TextDocument = TextDocument
12
+ Position = Position
13
+
14
+ # For backwards compatibility
15
+ Document = TextDocument
16
+
17
+
18
+ def utf16_unit_offset(chars: str):
19
+ warnings.warn(
20
+ "'utf16_unit_offset' has been deprecated, use "
21
+ "'Position.utf16_unit_offset' instead",
22
+ DeprecationWarning,
23
+ stacklevel=2,
24
+ )
25
+ _position = Position()
26
+ return _position.utf16_unit_offset(chars)
27
+
28
+
29
+ def utf16_num_units(chars: str):
30
+ warnings.warn(
31
+ "'utf16_num_units' has been deprecated, use "
32
+ "'Position.client_num_units' instead",
33
+ DeprecationWarning,
34
+ stacklevel=2,
35
+ )
36
+ _position = Position()
37
+ return _position.client_num_units(chars)
38
+
39
+
40
+ def position_from_utf16(lines: List[str], position: types.Position):
41
+ warnings.warn(
42
+ "'position_from_utf16' has been deprecated, use "
43
+ "'Position.position_from_client_units' instead",
44
+ DeprecationWarning,
45
+ stacklevel=2,
46
+ )
47
+ _position = Position()
48
+ return _position.position_from_client_units(lines, position)
49
+
50
+
51
+ def position_to_utf16(lines: List[str], position: types.Position):
52
+ warnings.warn(
53
+ "'position_to_utf16' has been deprecated, use "
54
+ "'Position.position_to_client_units' instead",
55
+ DeprecationWarning,
56
+ stacklevel=2,
57
+ )
58
+ _position = Position()
59
+ return _position.position_to_client_units(lines, position)
60
+
61
+
62
+ def range_from_utf16(lines: List[str], range: types.Range):
63
+ warnings.warn(
64
+ "'range_from_utf16' has been deprecated, use "
65
+ "'Position.range_from_client_units' instead",
66
+ DeprecationWarning,
67
+ stacklevel=2,
68
+ )
69
+ _position = Position()
70
+ return _position.range_from_client_units(lines, range)
71
+
72
+
73
+ def range_to_utf16(lines: List[str], range: types.Range):
74
+ warnings.warn(
75
+ "'range_to_utf16' has been deprecated, use "
76
+ "'Position.range_to_client_units' instead",
77
+ DeprecationWarning,
78
+ stacklevel=2,
79
+ )
80
+ _position = Position()
81
+ return _position.range_to_client_units(lines, range)
@@ -0,0 +1,204 @@
1
+ ############################################################################
2
+ # Original work Copyright 2017 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 logging
20
+ from typing import List, Optional, Union
21
+
22
+ from jaclang.vendor.pygls.lsprotocol import types
23
+
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+
28
+ class Position:
29
+ def __init__(
30
+ self,
31
+ encoding: Optional[
32
+ Union[types.PositionEncodingKind, str]
33
+ ] = types.PositionEncodingKind.Utf16,
34
+ ):
35
+ self.encoding = encoding
36
+
37
+ @classmethod
38
+ def is_char_beyond_multilingual_plane(cls, char: str) -> bool:
39
+ return ord(char) > 0xFFFF
40
+
41
+ def utf16_unit_offset(self, chars: str):
42
+ """
43
+ Calculate the number of characters which need two utf-16 code units.
44
+
45
+ Arguments:
46
+ chars (str): The string to count occurrences of utf-16 code units for.
47
+ """
48
+ return sum(self.is_char_beyond_multilingual_plane(ch) for ch in chars)
49
+
50
+ def client_num_units(self, chars: str):
51
+ """
52
+ Calculate the length of `str` in utf-16 code units.
53
+
54
+ Arguments:
55
+ chars (str): The string to return the length in utf-16 code units for.
56
+ """
57
+ utf32_units = len(chars)
58
+ if self.encoding == types.PositionEncodingKind.Utf32:
59
+ return utf32_units
60
+
61
+ if self.encoding == types.PositionEncodingKind.Utf8:
62
+ return utf32_units + (self.utf16_unit_offset(chars) * 2)
63
+
64
+ return utf32_units + self.utf16_unit_offset(chars)
65
+
66
+ def position_from_client_units(
67
+ self, lines: List[str], position: types.Position
68
+ ) -> types.Position:
69
+ """
70
+ Convert the position.character from UTF-[32|16|8] code units to UTF-32.
71
+
72
+ A python application can't use the character member of `Position`
73
+ directly. As per specification it is represented as a zero-based line and
74
+ character offset based on posible a UTF-[32|16|8] string representation.
75
+
76
+ All characters whose code point exceeds the Basic Multilingual Plane are
77
+ represented by 2 UTF-16 or 4 UTF-8 code units.
78
+
79
+ The offset of the closing quotation mark in x="😋" is
80
+ - 7 in UTF-8 representation
81
+ - 5 in UTF-16 representation
82
+ - 4 in UTF-32 representation
83
+
84
+ see: https://github.com/microsoft/language-server-protocol/issues/376
85
+
86
+ Arguments:
87
+ lines (list):
88
+ The content of the document which the position refers to.
89
+ position (Position):
90
+ The line and character offset in UTF-[32|16|8] code units.
91
+
92
+ Returns:
93
+ The position with `character` being converted to UTF-32 code units.
94
+ """
95
+ if len(lines) == 0:
96
+ return types.Position(0, 0)
97
+ if position.line >= len(lines):
98
+ return types.Position(len(lines) - 1, self.client_num_units(lines[-1]))
99
+
100
+ _line = lines[position.line]
101
+ _line = _line.replace("\r\n", "\n") # TODO: it's a bit of a hack
102
+ _client_len = self.client_num_units(_line)
103
+ _utf32_len = len(_line)
104
+
105
+ if _client_len == 0:
106
+ return types.Position(position.line, 0)
107
+
108
+ _client_end_of_line = self.client_num_units(_line)
109
+ if position.character > _client_end_of_line:
110
+ position.character = _client_end_of_line - 1
111
+
112
+ _client_index = 0
113
+ utf32_index = 0
114
+ while True:
115
+ _is_searching_queried_position = _client_index < position.character
116
+ _is_before_end_of_line = utf32_index < _utf32_len
117
+ _is_searching_for_position = (
118
+ _is_searching_queried_position and _is_before_end_of_line
119
+ )
120
+ if not _is_searching_for_position:
121
+ break
122
+
123
+ _current_char = _line[utf32_index]
124
+ _is_double_width = Position.is_char_beyond_multilingual_plane(_current_char)
125
+ if _is_double_width:
126
+ if self.encoding == types.PositionEncodingKind.Utf32:
127
+ _client_index += 1
128
+ if self.encoding == types.PositionEncodingKind.Utf8:
129
+ _client_index += 4
130
+ _client_index += 2
131
+ else:
132
+ _client_index += 1
133
+ utf32_index += 1
134
+
135
+ position = types.Position(line=position.line, character=utf32_index)
136
+ return position
137
+
138
+ def position_to_client_units(
139
+ self, lines: List[str], position: types.Position
140
+ ) -> types.Position:
141
+ """
142
+ Convert the position.character from its internal UTF-32 representation
143
+ to client-supported UTF-[32|16|8] code units.
144
+
145
+ Arguments:
146
+ lines (list):
147
+ The content of the document which the position refers to.
148
+ position (Position):
149
+ The line and character offset in UTF-32 code units.
150
+
151
+ Returns:
152
+ The position with `character` being converted to UTF-[32|16|8] code units.
153
+ """
154
+ try:
155
+ character = self.client_num_units(
156
+ lines[position.line][: position.character]
157
+ )
158
+ return types.Position(
159
+ line=position.line,
160
+ character=character,
161
+ )
162
+ except IndexError:
163
+ return types.Position(line=len(lines), character=0)
164
+
165
+ def range_from_client_units(
166
+ self, lines: List[str], range: types.Range
167
+ ) -> types.Range:
168
+ """
169
+ Convert range.[start|end].character from UTF-[32|16|8] code units to UTF-32.
170
+
171
+ Arguments:
172
+ lines (list):
173
+ The content of the document which the range refers to.
174
+ range (Range):
175
+ The line and character offset in UTF-[32|16|8] code units.
176
+
177
+ Returns:
178
+ The range with `character` offsets being converted to UTF-32 code units.
179
+ """
180
+ range_new = types.Range(
181
+ start=self.position_from_client_units(lines, range.start),
182
+ end=self.position_from_client_units(lines, range.end),
183
+ )
184
+ return range_new
185
+
186
+ def range_to_client_units(
187
+ self, lines: List[str], range: types.Range
188
+ ) -> types.Range:
189
+ """
190
+ Convert range.[start|end].character from UTF-32 to UTF-[32|16|8] code units.
191
+
192
+ Arguments:
193
+ lines (list):
194
+ The content of the document which the range refers to.
195
+ range (Range):
196
+ The line and character offset in code units.
197
+
198
+ Returns:
199
+ The range with `character` offsets being converted to UTF-[32|16|8] code units.
200
+ """
201
+ return types.Range(
202
+ start=self.position_to_client_units(lines, range.start),
203
+ end=self.position_to_client_units(lines, range.end),
204
+ )
@@ -0,0 +1,234 @@
1
+ ############################################################################
2
+ # Original work Copyright 2017 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 io
20
+ import logging
21
+ import os
22
+ import re
23
+ from typing import List, Optional, Pattern, Union
24
+
25
+ from jaclang.vendor.pygls.lsprotocol import types
26
+
27
+ from jaclang.vendor.pygls.uris import to_fs_path
28
+ from .position import Position
29
+
30
+ # TODO: this is not the best e.g. we capture numbers
31
+ RE_END_WORD = re.compile("^[A-Za-z_0-9]*")
32
+ RE_START_WORD = re.compile("[A-Za-z_0-9]*$")
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ class TextDocument(object):
38
+ def __init__(
39
+ self,
40
+ uri: str,
41
+ source: Optional[str] = None,
42
+ version: Optional[int] = None,
43
+ language_id: Optional[str] = None,
44
+ local: bool = True,
45
+ sync_kind: types.TextDocumentSyncKind = types.TextDocumentSyncKind.Incremental,
46
+ position_encoding: Optional[
47
+ Union[types.PositionEncodingKind, str]
48
+ ] = types.PositionEncodingKind.Utf16,
49
+ ):
50
+ self.uri = uri
51
+ self.version = version
52
+ path = to_fs_path(uri)
53
+ if path is None:
54
+ raise Exception("`path` cannot be None")
55
+ self.path = path
56
+ self.language_id = language_id
57
+ self.filename: Optional[str] = os.path.basename(self.path)
58
+
59
+ self._local = local
60
+ self._source = source
61
+
62
+ self._is_sync_kind_full = sync_kind == types.TextDocumentSyncKind.Full
63
+ self._is_sync_kind_incremental = (
64
+ sync_kind == types.TextDocumentSyncKind.Incremental
65
+ )
66
+ self._is_sync_kind_none = sync_kind == types.TextDocumentSyncKind.None_
67
+
68
+ self.position = Position(encoding=position_encoding)
69
+
70
+ def __str__(self):
71
+ return str(self.uri)
72
+
73
+ def _apply_incremental_change(
74
+ self, change: types.TextDocumentContentChangeEvent_Type1
75
+ ) -> None:
76
+ """Apply an ``Incremental`` text change to the document"""
77
+ lines = self.lines
78
+ text = change.text
79
+ change_range = change.range
80
+
81
+ range = self.position.range_from_client_units(lines, change_range)
82
+ start_line = range.start.line
83
+ start_col = range.start.character
84
+ end_line = range.end.line
85
+ end_col = range.end.character
86
+
87
+ # Check for an edit occurring at the very end of the file
88
+ if start_line == len(lines):
89
+ self._source = self.source + text
90
+ return
91
+
92
+ new = io.StringIO()
93
+
94
+ # Iterate over the existing document until we hit the edit range,
95
+ # at which point we write the new text, then loop until we hit
96
+ # the end of the range and continue writing.
97
+ for i, line in enumerate(lines):
98
+ if i < start_line:
99
+ new.write(line)
100
+ continue
101
+
102
+ if i > end_line:
103
+ new.write(line)
104
+ continue
105
+
106
+ if i == start_line:
107
+ new.write(line[:start_col])
108
+ new.write(text)
109
+
110
+ if i == end_line:
111
+ new.write(line[end_col:])
112
+
113
+ self._source = new.getvalue()
114
+
115
+ def _apply_full_change(self, change: types.TextDocumentContentChangeEvent) -> None:
116
+ """Apply a ``Full`` text change to the document."""
117
+ self._source = change.text
118
+
119
+ def _apply_none_change(self, _: types.TextDocumentContentChangeEvent) -> None:
120
+ """Apply a ``None`` text change to the document
121
+
122
+ Currently does nothing, provided for consistency.
123
+ """
124
+ pass
125
+
126
+ def apply_change(self, change: types.TextDocumentContentChangeEvent) -> None:
127
+ """Apply a text change to a document, considering TextDocumentSyncKind
128
+
129
+ Performs either
130
+ :attr:`~lsprotocol.types.TextDocumentSyncKind.Incremental`,
131
+ :attr:`~lsprotocol.types.TextDocumentSyncKind.Full`, or no synchronization
132
+ based on both the client request and server capabilities.
133
+
134
+ .. admonition:: ``Incremental`` versus ``Full`` synchronization
135
+
136
+ Even if a server accepts ``Incremantal`` SyncKinds, clients may request
137
+ a ``Full`` SyncKind. In LSP 3.x, clients make this request by omitting
138
+ both Range and RangeLength from their request. Consequently, the
139
+ attributes "range" and "rangeLength" will be missing from ``Full``
140
+ content update client requests in the pygls Python library.
141
+
142
+ """
143
+ if isinstance(change, types.TextDocumentContentChangeEvent_Type1):
144
+ if self._is_sync_kind_incremental:
145
+ self._apply_incremental_change(change)
146
+ return
147
+ # Log an error, but still perform full update to preserve existing
148
+ # assumptions in test_document/test_document_full_edit. Test breaks
149
+ # otherwise, and fixing the tests would require a broader fix to
150
+ # protocol.py.
151
+ logger.error(
152
+ "Unsupported client-provided TextDocumentContentChangeEvent. "
153
+ "Please update / submit a Pull Request to your LSP client."
154
+ )
155
+
156
+ if self._is_sync_kind_none:
157
+ self._apply_none_change(change)
158
+ else:
159
+ self._apply_full_change(change)
160
+
161
+ @property
162
+ def lines(self) -> List[str]:
163
+ return self.source.splitlines(True)
164
+
165
+ def offset_at_position(self, client_position: types.Position) -> int:
166
+ """Return the character offset pointed at by the given client_position."""
167
+ lines = self.lines
168
+ server_position = self.position.position_from_client_units(
169
+ lines, client_position
170
+ )
171
+ row, col = server_position.line, server_position.character
172
+ return col + sum(self.position.client_num_units(line) for line in lines[:row])
173
+
174
+ @property
175
+ def source(self) -> str:
176
+ if self._source is None:
177
+ with io.open(self.path, "r", encoding="utf-8") as f:
178
+ return f.read()
179
+ return self._source
180
+
181
+ def word_at_position(
182
+ self,
183
+ client_position: types.Position,
184
+ re_start_word: Pattern[str] = RE_START_WORD,
185
+ re_end_word: Pattern[str] = RE_END_WORD,
186
+ ) -> str:
187
+ """Return the word at position.
188
+
189
+ The word is constructed in two halves, the first half is found by taking
190
+ the first match of ``re_start_word`` on the line up until
191
+ ``position.character``.
192
+
193
+ The second half is found by taking ``position.character`` up until the
194
+ last match of ``re_end_word`` on the line.
195
+
196
+ :func:`python:re.findall` is used to find the matches.
197
+
198
+ Parameters
199
+ ----------
200
+ position
201
+ The line and character offset.
202
+
203
+ re_start_word
204
+ The regular expression for extracting the word backward from
205
+ position. The default pattern is ``[A-Za-z_0-9]*$``.
206
+
207
+ re_end_word
208
+ The regular expression for extracting the word forward from
209
+ position. The default pattern is ``^[A-Za-z_0-9]*``.
210
+
211
+ Returns
212
+ -------
213
+ str
214
+ The word (obtained by concatenating the two matches) at position.
215
+ """
216
+ lines = self.lines
217
+ if client_position.line >= len(lines):
218
+ return ""
219
+
220
+ server_position = self.position.position_from_client_units(
221
+ lines, client_position
222
+ )
223
+ row, col = server_position.line, server_position.character
224
+ line = lines[row]
225
+ # Split word in two
226
+ start = line[:col]
227
+ end = line[col:]
228
+
229
+ # Take end of start and start of end to find word
230
+ # These are guaranteed to match, even if they match the empty string
231
+ m_start = re_start_word.findall(start)
232
+ m_end = re_end_word.findall(end)
233
+
234
+ return m_start[0] + m_end[-1]