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.
- Typhon/Driver/configs.py +14 -0
- Typhon/Driver/debugging.py +148 -5
- Typhon/Driver/diagnostic.py +4 -3
- Typhon/Driver/language_server.py +25 -0
- Typhon/Driver/run.py +1 -1
- Typhon/Driver/translate.py +16 -11
- Typhon/Driver/utils.py +39 -1
- Typhon/Grammar/_typhon_parser.py +2920 -2718
- Typhon/Grammar/parser.py +80 -53
- Typhon/Grammar/parser_helper.py +68 -87
- Typhon/Grammar/syntax_errors.py +41 -20
- Typhon/Grammar/token_factory_custom.py +541 -485
- Typhon/Grammar/tokenizer_custom.py +52 -0
- Typhon/Grammar/typhon_ast.py +754 -76
- Typhon/Grammar/typhon_ast_error.py +438 -0
- Typhon/Grammar/unparse_custom.py +25 -0
- Typhon/LanguageServer/__init__.py +3 -0
- Typhon/LanguageServer/client/__init__.py +42 -0
- Typhon/LanguageServer/client/pyrefly.py +115 -0
- Typhon/LanguageServer/client/pyright.py +173 -0
- Typhon/LanguageServer/semantic_tokens.py +446 -0
- Typhon/LanguageServer/server.py +376 -0
- Typhon/LanguageServer/utils.py +65 -0
- Typhon/SourceMap/ast_match_based_map.py +199 -152
- Typhon/SourceMap/ast_matching.py +102 -87
- Typhon/SourceMap/datatype.py +275 -264
- Typhon/SourceMap/defined_name_retrieve.py +145 -0
- Typhon/Transform/comprehension_to_function.py +2 -5
- Typhon/Transform/const_member_to_final.py +12 -7
- Typhon/Transform/extended_patterns.py +139 -0
- Typhon/Transform/forbidden_statements.py +25 -0
- Typhon/Transform/if_while_let.py +122 -11
- Typhon/Transform/inline_statement_block_capture.py +22 -15
- Typhon/Transform/optional_operators_to_checked.py +14 -6
- Typhon/Transform/placeholder_to_function.py +0 -1
- Typhon/Transform/record_to_dataclass.py +22 -238
- Typhon/Transform/scope_check_rename.py +109 -29
- Typhon/Transform/transform.py +16 -12
- Typhon/Transform/type_abbrev_desugar.py +11 -15
- Typhon/Transform/type_annotation_check_expand.py +2 -2
- Typhon/Transform/utils/__init__.py +0 -0
- Typhon/Transform/utils/imports.py +83 -0
- Typhon/Transform/{utils.py → utils/jump_away.py} +2 -38
- Typhon/Transform/utils/make_class.py +135 -0
- Typhon/Transform/visitor.py +25 -0
- Typhon/Typing/pyrefly.py +145 -0
- Typhon/Typing/pyright.py +141 -144
- Typhon/Typing/result_diagnostic.py +1 -1
- Typhon/__main__.py +15 -1
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/METADATA +13 -6
- typhon_language-0.1.4.dist-info/RECORD +65 -0
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/WHEEL +1 -1
- typhon_language-0.1.4.dist-info/licenses/LICENSE +201 -0
- typhon_language-0.1.2.dist-info/RECORD +0 -48
- typhon_language-0.1.2.dist-info/licenses/LICENSE +0 -21
- {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/entry_points.txt +0 -0
- {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
|
+
)
|