Typhon-Language 0.1.2__py3-none-any.whl → 0.1.4__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.
Files changed (57) hide show
  1. Typhon/Driver/configs.py +14 -0
  2. Typhon/Driver/debugging.py +148 -5
  3. Typhon/Driver/diagnostic.py +4 -3
  4. Typhon/Driver/language_server.py +25 -0
  5. Typhon/Driver/run.py +1 -1
  6. Typhon/Driver/translate.py +16 -11
  7. Typhon/Driver/utils.py +39 -1
  8. Typhon/Grammar/_typhon_parser.py +2920 -2718
  9. Typhon/Grammar/parser.py +80 -53
  10. Typhon/Grammar/parser_helper.py +68 -87
  11. Typhon/Grammar/syntax_errors.py +41 -20
  12. Typhon/Grammar/token_factory_custom.py +541 -485
  13. Typhon/Grammar/tokenizer_custom.py +52 -0
  14. Typhon/Grammar/typhon_ast.py +754 -76
  15. Typhon/Grammar/typhon_ast_error.py +438 -0
  16. Typhon/Grammar/unparse_custom.py +25 -0
  17. Typhon/LanguageServer/__init__.py +3 -0
  18. Typhon/LanguageServer/client/__init__.py +42 -0
  19. Typhon/LanguageServer/client/pyrefly.py +115 -0
  20. Typhon/LanguageServer/client/pyright.py +173 -0
  21. Typhon/LanguageServer/semantic_tokens.py +446 -0
  22. Typhon/LanguageServer/server.py +376 -0
  23. Typhon/LanguageServer/utils.py +65 -0
  24. Typhon/SourceMap/ast_match_based_map.py +199 -152
  25. Typhon/SourceMap/ast_matching.py +102 -87
  26. Typhon/SourceMap/datatype.py +275 -264
  27. Typhon/SourceMap/defined_name_retrieve.py +145 -0
  28. Typhon/Transform/comprehension_to_function.py +2 -5
  29. Typhon/Transform/const_member_to_final.py +12 -7
  30. Typhon/Transform/extended_patterns.py +139 -0
  31. Typhon/Transform/forbidden_statements.py +25 -0
  32. Typhon/Transform/if_while_let.py +122 -11
  33. Typhon/Transform/inline_statement_block_capture.py +22 -15
  34. Typhon/Transform/optional_operators_to_checked.py +14 -6
  35. Typhon/Transform/placeholder_to_function.py +0 -1
  36. Typhon/Transform/record_to_dataclass.py +22 -238
  37. Typhon/Transform/scope_check_rename.py +109 -29
  38. Typhon/Transform/transform.py +16 -12
  39. Typhon/Transform/type_abbrev_desugar.py +11 -15
  40. Typhon/Transform/type_annotation_check_expand.py +2 -2
  41. Typhon/Transform/utils/__init__.py +0 -0
  42. Typhon/Transform/utils/imports.py +83 -0
  43. Typhon/Transform/{utils.py → utils/jump_away.py} +2 -38
  44. Typhon/Transform/utils/make_class.py +135 -0
  45. Typhon/Transform/visitor.py +25 -0
  46. Typhon/Typing/pyrefly.py +145 -0
  47. Typhon/Typing/pyright.py +141 -144
  48. Typhon/Typing/result_diagnostic.py +1 -1
  49. Typhon/__main__.py +15 -1
  50. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/METADATA +13 -6
  51. typhon_language-0.1.4.dist-info/RECORD +65 -0
  52. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/WHEEL +1 -1
  53. typhon_language-0.1.4.dist-info/licenses/LICENSE +201 -0
  54. typhon_language-0.1.2.dist-info/RECORD +0 -48
  55. typhon_language-0.1.2.dist-info/licenses/LICENSE +0 -21
  56. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/entry_points.txt +0 -0
  57. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,173 @@
1
+ import sys
2
+ import copy
3
+ from typing import Any
4
+ import logging
5
+ import json
6
+ import traceback
7
+ from pathlib import Path
8
+ from cattrs.gen import make_dict_structure_fn
9
+ from pygls.lsp.client import LanguageClient
10
+ from lsprotocol import types
11
+ import random
12
+
13
+ from ...Driver.debugging import is_debug_verbose
14
+
15
+
16
+ _PENDING_SEMANTIC_TOKENS: set[Any] = set()
17
+
18
+
19
+ def create_pyright_client() -> LanguageClient:
20
+ client = LanguageClient("pyright-language-client", "v0.1.4")
21
+ return client
22
+
23
+
24
+ async def start_pyright_client(client: LanguageClient):
25
+ original_structure = client.protocol.structure_message
26
+ if is_debug_verbose():
27
+
28
+ def leaf_structure_message(data: Any):
29
+ logger = logging.getLogger("pygls.client.pyright")
30
+ try:
31
+ logger.debug(f"DEBUG: Raw data: {json.dumps(data, indent=2)}")
32
+ result = original_structure(data)
33
+ return result
34
+ except Exception as e:
35
+ logger.error(f"DEBUG: Failed to deserialize: {data}")
36
+ logger.error(traceback.format_exc())
37
+ raise e
38
+
39
+ client.protocol.structure_message = leaf_structure_message
40
+
41
+ # Also capture outgoing JSON. `structure_message` only sees inbound data.
42
+ logger = logging.getLogger("pygls.client.pyright")
43
+ original_send_data = client.protocol._send_data # type: ignore[attr-defined]
44
+
45
+ def leaf_send_data(data: Any):
46
+ try:
47
+ if is_debug_verbose():
48
+ logger.debug(f"DEBUG: Outgoing data: {data}")
49
+ except Exception:
50
+ logger.error("DEBUG: Failed to serialize outgoing data")
51
+ logger.error(traceback.format_exc())
52
+ return original_send_data(data)
53
+
54
+ client.protocol._send_data = leaf_send_data # type: ignore[attr-defined]
55
+
56
+ invoke_script = (
57
+ "import sys; "
58
+ "from basedpyright.langserver import main; "
59
+ "sys.argv = ['basedpyright-langserver', '--stdio', '--max-old-space-size=3072']; "
60
+ "sys.exit(main())"
61
+ )
62
+ # '--cancellationReceive=file:{'private'}'
63
+ await client.start_io( # type: ignore
64
+ sys.executable,
65
+ "-c",
66
+ invoke_script,
67
+ )
68
+ # basedpyright_langserver_cmd = (
69
+ # Path(sys.executable).parent
70
+ # / f"basedpyright-langserver{'' if sys.platform != 'win32' else '.exe'}"
71
+ # )
72
+ # await client.start_io( # type: ignore
73
+ # str(basedpyright_langserver_cmd),
74
+ # "--stdio",
75
+ # "--max-old-space-size=3072",
76
+ # )
77
+
78
+
79
+ def configure_pyright_client_option(
80
+ param: types.InitializeParams,
81
+ ) -> types.InitializeParams:
82
+ # Configure Pyright-specific client options if needed.
83
+ # For example, set specific capabilities or initialization options.
84
+ # This is a placeholder for any Pyright-specific configuration.
85
+
86
+ # basedpyright/packages/vscode-pyright/src/extension.ts
87
+
88
+ # const clientOptions: LanguageClientOptions = {
89
+ # // Register the server for python source files.
90
+ # documentSelector: [
91
+ # { scheme: 'file', language: 'python' },
92
+ # { scheme: 'untitled', language: 'python' },
93
+ # { notebook: '*', language: 'python' },
94
+ # ],
95
+ # synchronize: {
96
+ # // Synchronize the setting section to the server.
97
+ # configurationSection: ['python', 'basedpyright'],
98
+ # },
99
+ # connectionOptions: { cancellationStrategy: cancellationStrategy },
100
+ # middleware: {...},
101
+ # initializationOptions: {
102
+ # diagnosticMode: workspace.getConfiguration('basedpyright.analysis').get('diagnosticMode'),
103
+ # disablePullDiagnostics: workspace.getConfiguration('basedpyright').get('disablePullDiagnostics'),
104
+ # },
105
+ # }
106
+ cloned_params = copy.deepcopy(param)
107
+ # text_document = result.capabilities.text_document
108
+ text_document = cloned_params.capabilities.text_document
109
+ if text_document:
110
+ # Whitelist
111
+ cloned_params.capabilities.text_document = types.TextDocumentClientCapabilities(
112
+ # semantic_tokens=semantic_token_capabilities().text_document.semantic_tokens,
113
+ synchronization=text_document.synchronization,
114
+ filters=text_document.filters,
115
+ completion=text_document.completion,
116
+ hover=text_document.hover,
117
+ signature_help=text_document.signature_help,
118
+ declaration=text_document.declaration,
119
+ definition=text_document.definition,
120
+ type_definition=text_document.type_definition,
121
+ implementation=text_document.implementation,
122
+ references=text_document.references,
123
+ document_highlight=text_document.document_highlight,
124
+ document_symbol=text_document.document_symbol,
125
+ code_action=text_document.code_action,
126
+ code_lens=text_document.code_lens,
127
+ document_link=text_document.document_link,
128
+ color_provider=text_document.color_provider,
129
+ formatting=text_document.formatting,
130
+ range_formatting=text_document.range_formatting,
131
+ on_type_formatting=text_document.on_type_formatting,
132
+ rename=text_document.rename,
133
+ folding_range=text_document.folding_range,
134
+ selection_range=text_document.selection_range,
135
+ publish_diagnostics=text_document.publish_diagnostics,
136
+ call_hierarchy=text_document.call_hierarchy,
137
+ semantic_tokens=text_document.semantic_tokens,
138
+ linked_editing_range=text_document.linked_editing_range,
139
+ moniker=text_document.moniker,
140
+ type_hierarchy=text_document.type_hierarchy,
141
+ inline_value=text_document.inline_value,
142
+ inlay_hint=text_document.inlay_hint,
143
+ # diagnostic=text_document.diagnostic, # TODO: This make crashing pyright?. Why?
144
+ inline_completion=text_document.inline_completion,
145
+ )
146
+ workspace = cloned_params.capabilities.workspace
147
+ if workspace:
148
+ cloned_params.capabilities.workspace = types.WorkspaceClientCapabilities(
149
+ apply_edit=workspace.apply_edit,
150
+ workspace_edit=workspace.workspace_edit,
151
+ did_change_configuration=workspace.did_change_configuration,
152
+ did_change_watched_files=workspace.did_change_watched_files,
153
+ symbol=workspace.symbol,
154
+ execute_command=workspace.execute_command,
155
+ # workspace_folders=workspace.workspace_folders, # TODO: This make hanging pyright
156
+ # workspace_folders=(
157
+ # types.WorkspaceFoldersServerCapabilities( # type: ignore
158
+ # supported=True, change_notifications=True
159
+ # )
160
+ # if workspace.workspace_folders
161
+ # else None
162
+ # ), # TODO: type mismatch??? This make initialization fail
163
+ configuration=workspace.configuration,
164
+ semantic_tokens=workspace.semantic_tokens,
165
+ code_lens=workspace.code_lens,
166
+ file_operations=workspace.file_operations,
167
+ inline_value=workspace.inline_value,
168
+ inlay_hint=workspace.inlay_hint,
169
+ diagnostics=workspace.diagnostics,
170
+ folding_range=workspace.folding_range,
171
+ text_document_content=workspace.text_document_content,
172
+ )
173
+ return cloned_params
@@ -0,0 +1,446 @@
1
+ from typing import Sequence
2
+ import attrs
3
+ import enum
4
+ import operator
5
+ import ast
6
+ from functools import reduce
7
+ from tokenize import (
8
+ OP,
9
+ NAME,
10
+ STRING,
11
+ COMMENT,
12
+ NUMBER,
13
+ FSTRING_START,
14
+ FSTRING_MIDDLE,
15
+ FSTRING_END,
16
+ )
17
+ from lsprotocol import types
18
+ from pygls.workspace import TextDocument
19
+
20
+ from ..Grammar.typhon_ast import is_internal_name
21
+ from ..Grammar.tokenizer_custom import TokenInfo
22
+ from ..Driver.debugging import debug_file_write_verbose
23
+ from ..SourceMap.ast_match_based_map import MatchBasedSourceMap
24
+ from ..SourceMap.datatype import Range, Pos
25
+
26
+ # https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide
27
+ TOKEN_TYPES = [
28
+ "namespace", # For identifiers that declare or reference a namespace, module, or package.
29
+ "class", # For identifiers that declare or reference a class type.
30
+ "enum", # For identifiers that declare or reference an enumeration type.
31
+ "interface", # For identifiers that declare or reference an interface type.
32
+ "struct", # For identifiers that declare or reference a struct type.
33
+ "typeParameter", # For identifiers that declare or reference a type parameter.
34
+ "type", # For identifiers that declare or reference a type that is not covered above.
35
+ "parameter", # For identifiers that declare or reference a function or method parameters.
36
+ "variable", # For identifiers that declare or reference a local or global variable.
37
+ "property", # For identifiers that declare or reference a member property, member field, or member variable.
38
+ "enumMember", # For identifiers that declare or reference an enumeration property, constant, or member.
39
+ "decorator", # For identifiers that declare or reference decorators and annotations.
40
+ "event", # For identifiers that declare an event property.
41
+ "function", # For identifiers that declare a function.
42
+ "method", # For identifiers that declare a member function or method.
43
+ "macro", # For identifiers that declare a macro.
44
+ "label", # For identifiers that declare a label.
45
+ "comment", # For tokens that represent a comment.
46
+ "string", # For tokens that represent a string literal.
47
+ "keyword", # For tokens that represent a language keyword.
48
+ "number", # For tokens that represent a number literal.
49
+ "regexp", # For tokens that represent a regular expression literal.
50
+ "operator", # For tokens that represent an operator.
51
+ # Extensions beyond the standard LSP semantic token types:
52
+ "selfParameter", # For the 'self' or equivalent parameter in methods. Pyright specific.
53
+ "clsParameter", # For the 'cls' or equivalent parameter in class methods. Pyright specific.
54
+ ]
55
+ TOKEN_TYPES_MAP = {t: i for i, t in enumerate(TOKEN_TYPES)}
56
+
57
+
58
+ class TokenModifier(enum.IntFlag):
59
+ declaration = enum.auto() # For declarations of symbols.
60
+ definition = (
61
+ enum.auto()
62
+ ) # For definitions of symbols, for example, in header files.
63
+ readonly = enum.auto() # For readonly variables and member fields (constants).
64
+ static = enum.auto() # For class members (static members).
65
+ deprecated = enum.auto() # For symbols that should no longer be used.
66
+ abstract = enum.auto() # For types and member functions that are abstract.
67
+ async_ = enum.auto() # For functions that are marked async.
68
+ modification = (
69
+ enum.auto()
70
+ ) # For variable references where the variable is assigned to.
71
+ documentation = enum.auto() # For occurrences of symbols in documentation.
72
+ defaultLibrary = enum.auto() # For symbols that are part of the standard library.
73
+
74
+
75
+ TokenModifierMap: dict[str, TokenModifier] = {
76
+ (e.name if e.name != "async_" else "async") or "": e for e in TokenModifier
77
+ }
78
+
79
+
80
+ def semantic_legend() -> types.SemanticTokensLegend:
81
+ return types.SemanticTokensLegend(
82
+ token_types=TOKEN_TYPES,
83
+ token_modifiers=[
84
+ m.name if m.name != "async_" else "async"
85
+ for m in TokenModifier
86
+ if m.name is not None
87
+ ],
88
+ )
89
+
90
+
91
+ @attrs.define
92
+ class SemanticToken:
93
+ line: int
94
+ offset: int # Column offset from the previous token
95
+ length: int # Length is derived from text
96
+ start_col: int
97
+ end_col: int
98
+ text: str
99
+ tok_type: str = ""
100
+ tok_modifiers: list[TokenModifier] = attrs.field(factory=list[TokenModifier])
101
+
102
+
103
+ def encode_semantic_tokens(
104
+ tokens: list[SemanticToken],
105
+ ) -> types.SemanticTokens:
106
+ encoded_tokens: list[list[int]] = []
107
+ prev_line = 0
108
+ prev_start_col = 0
109
+ for token in tokens:
110
+ delta_line = token.line - prev_line
111
+ if delta_line == 0:
112
+ delta_offset = token.start_col - prev_start_col
113
+ else:
114
+ delta_offset = token.start_col
115
+ prev_start_col = 0
116
+ encoded_tokens.append(
117
+ [
118
+ delta_line,
119
+ delta_offset,
120
+ token.length,
121
+ TOKEN_TYPES_MAP.get(token.tok_type, 0),
122
+ reduce(operator.or_, token.tok_modifiers, 0),
123
+ ]
124
+ )
125
+ prev_line = token.line
126
+ prev_start_col = token.start_col
127
+ return types.SemanticTokens(data=[i for token in encoded_tokens for i in token])
128
+
129
+
130
+ def decode_semantic_tokens(
131
+ tokens: types.SemanticTokens, client_legend: dict[int, str]
132
+ ) -> list[SemanticToken]:
133
+ decoded_tokens: list[SemanticToken] = []
134
+ i = 0
135
+ line = 0
136
+ offset = 0
137
+ while i < len(tokens.data):
138
+ delta_line = tokens.data[i]
139
+ delta_offset = tokens.data[i + 1]
140
+ length = tokens.data[i + 2]
141
+ tok_type_index = tokens.data[i + 3]
142
+ tok_modifiers_bitmask = tokens.data[i + 4]
143
+ i += 5
144
+ line += delta_line
145
+ if delta_line == 0:
146
+ offset = offset + delta_offset
147
+ else:
148
+ offset = delta_offset
149
+ debug_file_write_verbose(
150
+ f"Decoding semantic token: delta_line={delta_line}, delta_offset={delta_offset}, length={length}, tok_type_index={tok_type_index}, tok_modifiers_bitmask={tok_modifiers_bitmask}, line={line}, offset={offset}"
151
+ )
152
+ tok_type = client_legend.get(tok_type_index, TOKEN_TYPES[tok_type_index])
153
+ tok_modifiers: list[TokenModifier] = []
154
+ for mod in TokenModifier:
155
+ if tok_modifiers_bitmask & mod:
156
+ tok_modifiers.append(mod)
157
+ decoded_tokens.append(
158
+ SemanticToken(
159
+ line=line,
160
+ offset=delta_offset,
161
+ length=length,
162
+ start_col=offset,
163
+ end_col=offset + length,
164
+ text="", # Text is not needed for mapping
165
+ tok_type=tok_type,
166
+ tok_modifiers=tok_modifiers,
167
+ )
168
+ )
169
+ debug_file_write_verbose(f" Decoded Semantic Token: {decoded_tokens[-1]}")
170
+ return decoded_tokens
171
+
172
+
173
+ def map_semantic_tokens(
174
+ tokens: types.SemanticTokens,
175
+ mapping: MatchBasedSourceMap,
176
+ client_legend: dict[int, str],
177
+ ) -> types.SemanticTokens:
178
+ # First decode the tokens into SemanticTokens
179
+ decoded_tokens = decode_semantic_tokens(tokens, client_legend)
180
+ debug_file_write_verbose(f"Decoded tokens for mapping: {decoded_tokens}")
181
+ # Map each token position
182
+ mapped_tokens: list[SemanticToken] = []
183
+
184
+ for token in decoded_tokens:
185
+ token_range = Range(
186
+ start=Pos(line=token.line, column=token.start_col),
187
+ end=Pos(line=token.line, column=token.end_col),
188
+ )
189
+ # For debugging to see text.
190
+ token.text = Range.of_string(token_range, mapping.unparsed_code)
191
+ debug_file_write_verbose(
192
+ f"Mapping token from decoded: {token} at range: {token_range}"
193
+ )
194
+ if mapped_node := mapping.unparsed_range_to_origin_node(token_range, ast.Name):
195
+ if isinstance(mapped_node, ast.Name) and not is_internal_name(mapped_node):
196
+ if mapped_range := Range.from_ast_node(mapped_node):
197
+ line = mapped_range.start.line
198
+ # TODO Wrong now.
199
+ debug_file_write_verbose(
200
+ f"Mapping token to node with range OK: {token}\n --> {ast.dump(mapped_node)} (internal={is_internal_name(mapped_node)})@{mapped_range}"
201
+ )
202
+ debug_file_write_verbose(
203
+ f" line: {line}, start_col: {mapped_range.start.column}, end_col: {mapped_range.end.column}"
204
+ )
205
+ mapped_tokens.append(
206
+ SemanticToken(
207
+ line=line,
208
+ offset=-1,
209
+ length=mapped_range.end.column - mapped_range.start.column,
210
+ start_col=mapped_range.start.column,
211
+ end_col=mapped_range.end.column,
212
+ text=Range.of_string(mapped_range, mapping.source_code),
213
+ tok_type=token.tok_type, # TODO: map this
214
+ tok_modifiers=token.tok_modifiers,
215
+ )
216
+ )
217
+ continue
218
+ # Sort tokens by position because with statement and so on are out of order in tokens.
219
+ sorted_tokens = list(sorted(mapped_tokens, key=lambda t: (t.line, t.start_col)))
220
+ # Calculate offsets
221
+ prev_line = 0
222
+ prev_end_col = 0
223
+ for token in sorted_tokens:
224
+ if token.line == prev_line:
225
+ token.offset = token.start_col - prev_end_col
226
+ else:
227
+ token.offset = token.start_col
228
+ prev_line = token.line
229
+ prev_end_col = token.end_col
230
+ debug_file_write_verbose(f"Mapped semantic tokens(before encode): {sorted_tokens}")
231
+ return encode_semantic_tokens(sorted_tokens)
232
+
233
+
234
+ def get_semantic_token_text(token: SemanticToken, lines: list[str]) -> str:
235
+ """Retrieve the text of a semantic token from the document."""
236
+ if token.line < 0 or token.line >= len(lines):
237
+ return ""
238
+ line_text = lines[token.line]
239
+ if token.start_col < 0 or token.end_col > len(line_text):
240
+ return ""
241
+ return line_text[token.start_col : token.end_col]
242
+
243
+
244
+ def semantic_legends_of_initialized_response(
245
+ legend: types.SemanticTokensLegend,
246
+ ) -> dict[int, str]:
247
+ return {i: tok_type for i, tok_type in enumerate(legend.token_types)}
248
+
249
+
250
+ # Fallback case that semantic tokens are not provided by the language server.
251
+ def token_to_type(tok: TokenInfo) -> str:
252
+ if tok.string in (
253
+ "def",
254
+ "class",
255
+ "let",
256
+ "var",
257
+ "import",
258
+ "from",
259
+ "as",
260
+ "if",
261
+ "else",
262
+ "elif",
263
+ "while",
264
+ "for",
265
+ "try",
266
+ "except",
267
+ "finally",
268
+ "with",
269
+ "match",
270
+ "case",
271
+ "return",
272
+ "raise",
273
+ "yield",
274
+ "break",
275
+ "continue",
276
+ "async",
277
+ "static",
278
+ "in",
279
+ "is",
280
+ "not",
281
+ ):
282
+ return "keyword"
283
+ elif tok.type == NAME:
284
+ return "variable"
285
+ elif tok.type == OP:
286
+ return "operator"
287
+ elif tok.type == STRING:
288
+ return "string"
289
+ elif tok.type == COMMENT:
290
+ return "comment"
291
+ elif tok.type == NUMBER:
292
+ return "number"
293
+ elif tok.type in (FSTRING_START, FSTRING_MIDDLE, FSTRING_END):
294
+ return "string"
295
+ else:
296
+ return "operator"
297
+
298
+
299
+ # Fallback implementation that makes semantic tokens from tokens only.
300
+ def ast_tokens_to_semantic_tokens(
301
+ node: ast.AST | None, tokens: list[TokenInfo], doc: TextDocument
302
+ ) -> tuple[list[SemanticToken], Sequence[int]]:
303
+ semantic_tokens: list[SemanticToken] = []
304
+ prev_line = 0
305
+ prev_end_offset = 0
306
+ for tok in tokens:
307
+ # Offset encoding
308
+ line = tok.start[0] - 1 - prev_line
309
+ offset = tok.start[1]
310
+ if line == 0:
311
+ offset -= prev_end_offset # Offset is from previous token in the same line
312
+ debug_file_write_verbose(
313
+ f"Semantic token encode Token: {tok}, Line: {line}, Offset: {offset} prev_line: {prev_line}, prev_end_offset: {prev_end_offset}"
314
+ )
315
+ prev_line = tok.end[0] - 1
316
+ prev_end_offset = tok.start[1]
317
+ semantic_tokens.append(
318
+ SemanticToken(
319
+ line=line,
320
+ offset=offset,
321
+ start_col=tok.start[1],
322
+ end_col=tok.end[1],
323
+ length=len(tok.string),
324
+ text=tok.string,
325
+ tok_type=token_to_type(tok),
326
+ )
327
+ )
328
+ debug_file_write_verbose(
329
+ f" Added Semantic Token: {semantic_tokens[-1]}, {encode_semantic_tokens([semantic_tokens[-1]]).data}"
330
+ )
331
+ return semantic_tokens, encode_semantic_tokens(semantic_tokens).data
332
+
333
+
334
+ # Mainly for testing
335
+ def semantic_token_capabilities() -> types.ClientCapabilities:
336
+ return types.ClientCapabilities(
337
+ text_document=types.TextDocumentClientCapabilities(
338
+ semantic_tokens=types.SemanticTokensClientCapabilities(
339
+ requests=types.ClientSemanticTokensRequestOptions(
340
+ range=True,
341
+ # full=True,
342
+ full=types.ClientSemanticTokensRequestFullDelta(delta=True),
343
+ ),
344
+ token_types=TOKEN_TYPES,
345
+ token_modifiers=[
346
+ m.name if m.name != "async_" else "async"
347
+ for m in TokenModifier
348
+ if m.name is not None
349
+ ],
350
+ formats=(types.TokenFormat.Relative,),
351
+ dynamic_registration=True,
352
+ overlapping_token_support=False,
353
+ multiline_token_support=False,
354
+ server_cancel_support=True,
355
+ augments_syntax_tokens=True,
356
+ )
357
+ )
358
+ )
359
+
360
+
361
+ # Pyrefly server currently not seems to return semantic token legend in capabilities.
362
+ # We use here a fixed legend from the source code of Pyrefly.
363
+
364
+ # pyrefly/lib/state/semantic_tokens.rs
365
+ #
366
+ # impl SemanticTokensLegends {
367
+ # pub fn lsp_semantic_token_legends() -> SemanticTokensLegend {
368
+ # SemanticTokensLegend {
369
+ # token_types: vec![
370
+ # SemanticTokenType::NAMESPACE,
371
+ # SemanticTokenType::TYPE,
372
+ # SemanticTokenType::CLASS,
373
+ # SemanticTokenType::ENUM,
374
+ # SemanticTokenType::INTERFACE,
375
+ # SemanticTokenType::STRUCT,
376
+ # SemanticTokenType::TYPE_PARAMETER,
377
+ # SemanticTokenType::PARAMETER,
378
+ # SemanticTokenType::VARIABLE,
379
+ # SemanticTokenType::PROPERTY,
380
+ # SemanticTokenType::ENUM_MEMBER,
381
+ # SemanticTokenType::EVENT,
382
+ # SemanticTokenType::FUNCTION,
383
+ # SemanticTokenType::METHOD,
384
+ # SemanticTokenType::MACRO,
385
+ # SemanticTokenType::KEYWORD,
386
+ # SemanticTokenType::MODIFIER,
387
+ # SemanticTokenType::COMMENT,
388
+ # SemanticTokenType::STRING,
389
+ # SemanticTokenType::NUMBER,
390
+ # SemanticTokenType::REGEXP,
391
+ # SemanticTokenType::OPERATOR,
392
+ # SemanticTokenType::DECORATOR,
393
+ # ],
394
+ # token_modifiers: vec![
395
+ # SemanticTokenModifier::DECLARATION,
396
+ # SemanticTokenModifier::DEFINITION,
397
+ # SemanticTokenModifier::READONLY,
398
+ # SemanticTokenModifier::STATIC,
399
+ # SemanticTokenModifier::DEPRECATED,
400
+ # SemanticTokenModifier::ABSTRACT,
401
+ # SemanticTokenModifier::ASYNC,
402
+ # SemanticTokenModifier::MODIFICATION,
403
+ # SemanticTokenModifier::DOCUMENTATION,
404
+ # SemanticTokenModifier::DEFAULT_LIBRARY,
405
+ # ],
406
+ # }
407
+
408
+ PYREFLY_TOKEN_TYPES: list[str] = [
409
+ "namespace",
410
+ "type",
411
+ "class",
412
+ "enum",
413
+ "interface",
414
+ "struct",
415
+ "typeParameter",
416
+ "parameter",
417
+ "variable",
418
+ "property",
419
+ "enumMember",
420
+ "event",
421
+ "function",
422
+ "method",
423
+ "macro",
424
+ "keyword",
425
+ "modifier",
426
+ "comment",
427
+ "string",
428
+ "number",
429
+ "regexp",
430
+ "operator",
431
+ "decorator",
432
+ ]
433
+ PYREFLY_TOKEN_TYPES_MAP: dict[str, int] = {
434
+ t: i for i, t in enumerate(PYREFLY_TOKEN_TYPES)
435
+ }
436
+
437
+
438
+ def pyrefly_semantic_legend() -> types.SemanticTokensLegend:
439
+ return types.SemanticTokensLegend(
440
+ token_types=PYREFLY_TOKEN_TYPES,
441
+ token_modifiers=[
442
+ m.name if m.name != "async_" else "async"
443
+ for m in TokenModifier
444
+ if m.name is not None
445
+ ],
446
+ )