robotcode-language-server 2.6.2__py3-none-any.whl → 2.8.0.dev548__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.
- robotcode/language_server/__version__.py +1 -1
- robotcode/language_server/common/parts/diagnostics.py +50 -17
- robotcode/language_server/common/parts/documents.py +34 -31
- robotcode/language_server/common/parts/workspace.py +57 -55
- robotcode/language_server/common/protocol.py +3 -1
- robotcode/language_server/robotframework/configuration.py +3 -0
- robotcode/language_server/robotframework/parts/code_action_documentation.py +440 -129
- robotcode/language_server/robotframework/parts/code_action_helper_mixin.py +17 -2
- robotcode/language_server/robotframework/parts/code_action_quick_fixes.py +6 -25
- robotcode/language_server/robotframework/parts/code_action_refactor.py +8 -6
- robotcode/language_server/robotframework/parts/code_lens.py +3 -4
- robotcode/language_server/robotframework/parts/completion.py +193 -21
- robotcode/language_server/robotframework/parts/debugging_utils.py +75 -6
- robotcode/language_server/robotframework/parts/diagnostics.py +15 -3
- robotcode/language_server/robotframework/parts/documents_cache.py +5 -1
- robotcode/language_server/robotframework/parts/hover.py +103 -21
- robotcode/language_server/robotframework/parts/http_server.py +17 -7
- robotcode/language_server/robotframework/parts/inline_value.py +69 -7
- robotcode/language_server/robotframework/parts/keywords_treeview.py +151 -53
- robotcode/language_server/robotframework/parts/model_variables.py +331 -0
- robotcode/language_server/robotframework/parts/references.py +47 -31
- robotcode/language_server/robotframework/parts/rename.py +1 -2
- robotcode/language_server/robotframework/parts/robot_workspace.py +229 -25
- robotcode/language_server/robotframework/parts/selection_range.py +26 -13
- robotcode/language_server/robotframework/parts/semantic_tokens.py +414 -113
- robotcode/language_server/robotframework/parts/signature_help.py +162 -70
- robotcode/language_server/robotframework/protocol.py +1 -0
- {robotcode_language_server-2.6.2.dist-info → robotcode_language_server-2.8.0.dev548.dist-info}/METADATA +7 -6
- {robotcode_language_server-2.6.2.dist-info → robotcode_language_server-2.8.0.dev548.dist-info}/RECORD +31 -30
- {robotcode_language_server-2.6.2.dist-info → robotcode_language_server-2.8.0.dev548.dist-info}/WHEEL +1 -1
- {robotcode_language_server-2.6.2.dist-info → robotcode_language_server-2.8.0.dev548.dist-info}/entry_points.txt +0 -0
|
@@ -39,7 +39,6 @@ from robot.parsing.model.statements import (
|
|
|
39
39
|
TestTemplate,
|
|
40
40
|
Variable,
|
|
41
41
|
VariablesImport,
|
|
42
|
-
WhileHeader,
|
|
43
42
|
)
|
|
44
43
|
from robot.utils.escaping import unescape
|
|
45
44
|
|
|
@@ -67,8 +66,14 @@ from robotcode.robot.diagnostics.library_doc import (
|
|
|
67
66
|
)
|
|
68
67
|
from robotcode.robot.diagnostics.model_helper import ModelHelper
|
|
69
68
|
from robotcode.robot.diagnostics.namespace import Namespace
|
|
70
|
-
from robotcode.robot.diagnostics.semantic_analyzer.enums import TokenKind
|
|
69
|
+
from robotcode.robot.diagnostics.semantic_analyzer.enums import NodeKind, TokenKind, TokenModifier
|
|
71
70
|
from robotcode.robot.diagnostics.semantic_analyzer.model import SemanticModel
|
|
71
|
+
from robotcode.robot.diagnostics.semantic_analyzer.nodes import (
|
|
72
|
+
RunKeywordCallStatement,
|
|
73
|
+
SemanticStatement,
|
|
74
|
+
SemanticToken,
|
|
75
|
+
)
|
|
76
|
+
from robotcode.robot.diagnostics.semantic_analyzer.variable_tokenizer import VariableSite, build_variable_sub_tokens
|
|
72
77
|
from robotcode.robot.utils import RF_VERSION
|
|
73
78
|
from robotcode.robot.utils.ast import (
|
|
74
79
|
cached_isinstance,
|
|
@@ -1034,46 +1039,51 @@ class SemanticTokenGenerator:
|
|
|
1034
1039
|
creating and managing its own token mapper and keyword analyzer.
|
|
1035
1040
|
"""
|
|
1036
1041
|
|
|
1037
|
-
#
|
|
1038
|
-
#
|
|
1039
|
-
|
|
1042
|
+
# ------------------------------------------------------------------
|
|
1043
|
+
# Model-based path (Tier 1): declarative rendering of the SemanticModel.
|
|
1044
|
+
#
|
|
1045
|
+
# After analysis every token carries its final render semantics (kind +
|
|
1046
|
+
# modifiers), so rendering is: leaf descent -> static table lookup ->
|
|
1047
|
+
# delta encoding, plus a small legacy-compat emission policy keyed only
|
|
1048
|
+
# on TokenKind / NodeKind / TokenModifier (design D6/D7). No RF version
|
|
1049
|
+
# checks, no tokenization, no statement-type semantics here.
|
|
1050
|
+
# ------------------------------------------------------------------
|
|
1051
|
+
|
|
1052
|
+
_TOKEN_KIND_TO_SEM_TOKEN: ClassVar[Dict[TokenKind, Tuple[AnyTokenType, Optional[Set[AnyTokenModifier]]]]] = {
|
|
1040
1053
|
TokenKind.KEYWORD: (RobotSemTokenTypes.KEYWORD, None),
|
|
1054
|
+
TokenKind.KEYWORD_INNER: (RobotSemTokenTypes.KEYWORD_INNER, None),
|
|
1041
1055
|
TokenKind.BDD_PREFIX: (RobotSemTokenTypes.BDD_PREFIX, None),
|
|
1042
1056
|
TokenKind.NAMESPACE: (RobotSemTokenTypes.NAMESPACE, None),
|
|
1057
|
+
# Variables render their name only (see `_render_variable_part`).
|
|
1043
1058
|
TokenKind.VARIABLE: (RobotSemTokenTypes.VARIABLE, None),
|
|
1044
1059
|
TokenKind.VARIABLE_NOT_FOUND: (RobotSemTokenTypes.VARIABLE, None),
|
|
1045
|
-
TokenKind.
|
|
1046
|
-
TokenKind.VARIABLE_OPEN_BRACE: (RobotSemTokenTypes.VARIABLE_BEGIN, None),
|
|
1047
|
-
TokenKind.VARIABLE_CLOSE_BRACE: (RobotSemTokenTypes.VARIABLE_END, None),
|
|
1048
|
-
TokenKind.VARIABLE_BASE: (RobotSemTokenTypes.VARIABLE, None),
|
|
1049
|
-
TokenKind.VARIABLE_EXTENDED: (RobotSemTokenTypes.VARIABLE, None),
|
|
1050
|
-
TokenKind.VARIABLE_TYPE_SEPARATOR: (SemanticTokenTypes.OPERATOR, None),
|
|
1051
|
-
TokenKind.VARIABLE_TYPE_HINT: (SemanticTokenTypes.TYPE, None),
|
|
1052
|
-
TokenKind.VARIABLE_DEFAULT_SEPARATOR: (SemanticTokenTypes.OPERATOR, None),
|
|
1060
|
+
TokenKind.VARIABLE_NAME: (RobotSemTokenTypes.VARIABLE, None),
|
|
1053
1061
|
TokenKind.VARIABLE_DEFAULT_VALUE: (RobotSemTokenTypes.ARGUMENT, None),
|
|
1054
|
-
TokenKind.VARIABLE_PATTERN_SEPARATOR: (SemanticTokenTypes.OPERATOR, None),
|
|
1055
1062
|
TokenKind.VARIABLE_PATTERN: (RobotSemTokenTypes.ARGUMENT, None),
|
|
1056
|
-
TokenKind.VARIABLE_ASSIGN_MARK: (RobotSemTokenTypes.VARIABLE, None),
|
|
1057
|
-
TokenKind.VARIABLE_EXPRESSION_OPEN: (RobotSemTokenTypes.EXPRESSION_BEGIN, None),
|
|
1058
|
-
TokenKind.VARIABLE_EXPRESSION_CLOSE: (RobotSemTokenTypes.EXPRESSION_END, None),
|
|
1059
|
-
TokenKind.PYTHON_EXPRESSION: (RobotSemTokenTypes.VARIABLE_EXPRESSION, None),
|
|
1060
|
-
TokenKind.PYTHON_VARIABLE_REF: (RobotSemTokenTypes.VARIABLE, None),
|
|
1061
|
-
TokenKind.VARIABLE_INDEX: (RobotSemTokenTypes.VARIABLE, None),
|
|
1062
|
-
TokenKind.VARIABLE_INDEX_OPEN: (RobotSemTokenTypes.VARIABLE_BEGIN, None),
|
|
1063
|
-
TokenKind.VARIABLE_INDEX_CLOSE: (RobotSemTokenTypes.VARIABLE_END, None),
|
|
1064
|
-
TokenKind.VARIABLE_INDEX_CONTENT: (RobotSemTokenTypes.VARIABLE, None),
|
|
1065
1063
|
TokenKind.TEXT_FRAGMENT: (RobotSemTokenTypes.ARGUMENT, None),
|
|
1066
1064
|
TokenKind.ARGUMENT: (RobotSemTokenTypes.ARGUMENT, None),
|
|
1067
1065
|
TokenKind.NAMED_ARGUMENT_NAME: (RobotSemTokenTypes.NAMED_ARGUMENT, None),
|
|
1068
1066
|
TokenKind.NAMED_ARGUMENT_VALUE: (RobotSemTokenTypes.ARGUMENT, None),
|
|
1067
|
+
TokenKind.PARAMETER: (SemanticTokenTypes.PARAMETER, None),
|
|
1069
1068
|
TokenKind.CONTROL_FLOW: (RobotSemTokenTypes.CONTROL_FLOW, None),
|
|
1070
1069
|
TokenKind.CONDITION: (RobotSemTokenTypes.ARGUMENT, None),
|
|
1070
|
+
TokenKind.FOR_SEPARATOR: (RobotSemTokenTypes.FOR_SEPARATOR, None),
|
|
1071
|
+
TokenKind.VAR_MARKER: (RobotSemTokenTypes.VAR, None),
|
|
1072
|
+
TokenKind.OPTION: (RobotSemTokenTypes.CONTROL_FLOW, None),
|
|
1073
|
+
TokenKind.OPTION_VALUE: (RobotSemTokenTypes.CONTROL_FLOW, None),
|
|
1071
1074
|
TokenKind.TEST_NAME: (RobotSemTokenTypes.TESTCASE_NAME, {SemanticTokenModifiers.DECLARATION}),
|
|
1072
1075
|
TokenKind.KEYWORD_NAME: (RobotSemTokenTypes.KEYWORD_NAME, {SemanticTokenModifiers.DECLARATION}),
|
|
1073
|
-
TokenKind.VARIABLE_NAME: (RobotSemTokenTypes.VARIABLE, {SemanticTokenModifiers.DECLARATION}),
|
|
1074
1076
|
TokenKind.SETTING_NAME: (RobotSemTokenTypes.SETTING, None),
|
|
1075
|
-
TokenKind.
|
|
1077
|
+
TokenKind.SETTING_IMPORT: (RobotSemTokenTypes.SETTING_IMPORT, None),
|
|
1078
|
+
TokenKind.IMPORT_NAME: (RobotSemTokenTypes.NAMESPACE, None),
|
|
1079
|
+
TokenKind.OPERATOR: (SemanticTokenTypes.OPERATOR, None),
|
|
1076
1080
|
TokenKind.HEADER: (RobotSemTokenTypes.HEADER, None),
|
|
1081
|
+
TokenKind.HEADER_SETTINGS: (RobotSemTokenTypes.HEADER_SETTINGS, None),
|
|
1082
|
+
TokenKind.HEADER_VARIABLE: (RobotSemTokenTypes.HEADER_VARIABLE, None),
|
|
1083
|
+
TokenKind.HEADER_TESTCASE: (RobotSemTokenTypes.HEADER_TESTCASE, None),
|
|
1084
|
+
TokenKind.HEADER_TASK: (RobotSemTokenTypes.HEADER_TASK, None),
|
|
1085
|
+
TokenKind.HEADER_KEYWORD: (RobotSemTokenTypes.HEADER_KEYWORD, None),
|
|
1086
|
+
TokenKind.HEADER_COMMENT: (RobotSemTokenTypes.HEADER_COMMENT, None),
|
|
1077
1087
|
TokenKind.SEPARATOR: (RobotSemTokenTypes.SEPARATOR, None),
|
|
1078
1088
|
TokenKind.CONTINUATION: (RobotSemTokenTypes.CONTINUATION, None),
|
|
1079
1089
|
TokenKind.COMMENT: (SemanticTokenTypes.COMMENT, None),
|
|
@@ -1082,6 +1092,61 @@ class SemanticTokenGenerator:
|
|
|
1082
1092
|
TokenKind.ERROR: (RobotSemTokenTypes.ERROR, None),
|
|
1083
1093
|
}
|
|
1084
1094
|
|
|
1095
|
+
_TOKEN_MODIFIER_TO_SEM: ClassVar[Dict[TokenModifier, AnyTokenModifier]] = {
|
|
1096
|
+
TokenModifier.BUILTIN: RobotSemTokenModifiers.BUILTIN,
|
|
1097
|
+
TokenModifier.EMBEDDED: RobotSemTokenModifiers.EMBEDDED,
|
|
1098
|
+
TokenModifier.DECLARATION: SemanticTokenModifiers.DECLARATION,
|
|
1099
|
+
TokenModifier.DOCUMENTATION: SemanticTokenModifiers.DOCUMENTATION,
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
# --- Legacy-compat emission policy (design D7) ---
|
|
1103
|
+
|
|
1104
|
+
# Whole options of control structures render atomically: one control-flow
|
|
1105
|
+
# token per option, no descent into their sub-structure.
|
|
1106
|
+
_ATOMIC_KINDS: ClassVar[FrozenSet[TokenKind]] = frozenset({TokenKind.OPTION, TokenKind.OPTION_VALUE})
|
|
1107
|
+
# Variables and parameters render only their name (and type hint); the
|
|
1108
|
+
# grammar shows prefix, braces, item access, `=`, patterns, defaults and
|
|
1109
|
+
# inline Python.
|
|
1110
|
+
_VARIABLE_KINDS: ClassVar[FrozenSet[TokenKind]] = frozenset(
|
|
1111
|
+
{TokenKind.VARIABLE, TokenKind.VARIABLE_NOT_FOUND, TokenKind.VARIABLE_NAME, TokenKind.PARAMETER}
|
|
1112
|
+
)
|
|
1113
|
+
# Model-only kinds: carried for model consumers (inline values, debug
|
|
1114
|
+
# variable extraction), never rendered — legacy highlights no bare-`$var`
|
|
1115
|
+
# expression references.
|
|
1116
|
+
_MODEL_ONLY_KINDS: ClassVar[FrozenSet[TokenKind]] = frozenset({TokenKind.PYTHON_VARIABLE_REF})
|
|
1117
|
+
# Plain argument text is suppressed by the legacy path...
|
|
1118
|
+
_ARGUMENT_TEXT_KINDS: ClassVar[FrozenSet[TokenKind]] = frozenset(
|
|
1119
|
+
{
|
|
1120
|
+
TokenKind.ARGUMENT,
|
|
1121
|
+
TokenKind.TEXT_FRAGMENT,
|
|
1122
|
+
TokenKind.TAG,
|
|
1123
|
+
TokenKind.CONDITION,
|
|
1124
|
+
TokenKind.NAMED_ARGUMENT_VALUE,
|
|
1125
|
+
TokenKind.VARIABLE_DEFAULT_VALUE,
|
|
1126
|
+
TokenKind.VARIABLE_PATTERN,
|
|
1127
|
+
}
|
|
1128
|
+
)
|
|
1129
|
+
# ...except in template argument rows and metadata values...
|
|
1130
|
+
# (TEMPLATE_KEYWORD: an unresolved template name keeps kind ARGUMENT and
|
|
1131
|
+
# must still render, matching legacy.)
|
|
1132
|
+
_YIELD_ARGUMENT_STMT_KINDS: ClassVar[FrozenSet[NodeKind]] = frozenset(
|
|
1133
|
+
{NodeKind.TEMPLATE_DATA, NodeKind.SETTING_METADATA, NodeKind.TEMPLATE_KEYWORD}
|
|
1134
|
+
)
|
|
1135
|
+
# Comments only render on keyword-call / import statements (and inside
|
|
1136
|
+
# invalid sections).
|
|
1137
|
+
_COMMENT_STMT_KINDS: ClassVar[FrozenSet[NodeKind]] = frozenset(
|
|
1138
|
+
{NodeKind.KEYWORD_CALL, NodeKind.SETUP, NodeKind.TEARDOWN, NodeKind.TEMPLATE_KEYWORD, NodeKind.IMPORT}
|
|
1139
|
+
)
|
|
1140
|
+
# Statements whose keyword name comes from a Token.NAME cell -- their
|
|
1141
|
+
# BDD-prefix gap token renders as `argument` (the NAME cell's initial
|
|
1142
|
+
# legacy mapping), not as a keyword.
|
|
1143
|
+
_NAME_KEYWORD_STMT_KINDS: ClassVar[FrozenSet[NodeKind]] = frozenset(
|
|
1144
|
+
{NodeKind.SETUP, NodeKind.TEARDOWN, NodeKind.TEMPLATE_KEYWORD}
|
|
1145
|
+
)
|
|
1146
|
+
_BDD_FOLLOW_KINDS: ClassVar[FrozenSet[TokenKind]] = frozenset(
|
|
1147
|
+
{TokenKind.KEYWORD, TokenKind.KEYWORD_INNER, TokenKind.NAMESPACE, TokenKind.ARGUMENT}
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1085
1150
|
def __init__(self) -> None:
|
|
1086
1151
|
"""Initialize the generator with its own dependencies."""
|
|
1087
1152
|
self.token_mapper = SemanticTokenMapper()
|
|
@@ -1098,8 +1163,9 @@ class SemanticTokenGenerator:
|
|
|
1098
1163
|
"""Collect semantic tokens from the pre-built SemanticModel.
|
|
1099
1164
|
|
|
1100
1165
|
This is the Tier 1 model-based path, used when the semantic model
|
|
1101
|
-
feature flag is enabled.
|
|
1102
|
-
|
|
1166
|
+
feature flag is enabled. The model carries final render semantics,
|
|
1167
|
+
so this is a declarative mapping (see the class-level tables) plus
|
|
1168
|
+
UTF-16 delta encoding.
|
|
1103
1169
|
"""
|
|
1104
1170
|
data: List[int] = []
|
|
1105
1171
|
last_line = 0
|
|
@@ -1109,30 +1175,21 @@ class SemanticTokenGenerator:
|
|
|
1109
1175
|
for stmt in semantic_model.statements:
|
|
1110
1176
|
check_current_task_canceled()
|
|
1111
1177
|
|
|
1112
|
-
for
|
|
1113
|
-
if
|
|
1178
|
+
for info in self._iter_model_sem_tokens(stmt, semantic_model):
|
|
1179
|
+
if info.length == 0:
|
|
1114
1180
|
continue
|
|
1115
1181
|
|
|
1182
|
+
token_line_0 = info.lineno - 1
|
|
1116
1183
|
# Range filtering
|
|
1117
|
-
token_line_0
|
|
1118
|
-
if range is not None:
|
|
1119
|
-
if token_line_0 < range.start.line:
|
|
1120
|
-
continue
|
|
1121
|
-
if token_line_0 > range.end.line:
|
|
1122
|
-
break
|
|
1123
|
-
|
|
1124
|
-
sem_info = self._TOKEN_KIND_TO_SEM_TOKEN.get(token.kind)
|
|
1125
|
-
if sem_info is None:
|
|
1184
|
+
if range is not None and (token_line_0 < range.start.line or token_line_0 > range.end.line):
|
|
1126
1185
|
continue
|
|
1127
1186
|
|
|
1128
|
-
sem_type, sem_mods = sem_info
|
|
1129
|
-
|
|
1130
1187
|
# Convert to UTF-16 positions
|
|
1131
1188
|
token_range = range_to_utf16(
|
|
1132
1189
|
lines,
|
|
1133
1190
|
Range(
|
|
1134
|
-
start=Position(line=token_line_0, character=
|
|
1135
|
-
end=Position(line=token_line_0, character=
|
|
1191
|
+
start=Position(line=token_line_0, character=info.col_offset),
|
|
1192
|
+
end=Position(line=token_line_0, character=info.col_offset + info.length),
|
|
1136
1193
|
),
|
|
1137
1194
|
)
|
|
1138
1195
|
|
|
@@ -1154,11 +1211,244 @@ class SemanticTokenGenerator:
|
|
|
1154
1211
|
last_line = current_line
|
|
1155
1212
|
|
|
1156
1213
|
data.append(token_length)
|
|
1157
|
-
data.append(token_types.index(
|
|
1158
|
-
data.append(
|
|
1214
|
+
data.append(token_types.index(info.sem_token_type))
|
|
1215
|
+
data.append(
|
|
1216
|
+
reduce(operator.or_, [2 ** token_modifiers.index(e) for e in info.sem_modifiers])
|
|
1217
|
+
if info.sem_modifiers
|
|
1218
|
+
else 0
|
|
1219
|
+
)
|
|
1159
1220
|
|
|
1160
1221
|
return SemanticTokens(data=data)
|
|
1161
1222
|
|
|
1223
|
+
def _emit_model_token(self, token: SemanticToken) -> Optional[SemTokenInfo]:
|
|
1224
|
+
"""Map one model token through the static kind / modifier tables."""
|
|
1225
|
+
sem_info = self._TOKEN_KIND_TO_SEM_TOKEN.get(token.kind)
|
|
1226
|
+
if sem_info is None:
|
|
1227
|
+
return None
|
|
1228
|
+
sem_type, static_mods = sem_info
|
|
1229
|
+
sem_mods: Optional[Set[AnyTokenModifier]] = set(static_mods) if static_mods else None
|
|
1230
|
+
if token.modifiers:
|
|
1231
|
+
mapped = {self._TOKEN_MODIFIER_TO_SEM[m] for m in token.modifiers if m in self._TOKEN_MODIFIER_TO_SEM}
|
|
1232
|
+
if mapped:
|
|
1233
|
+
sem_mods = (sem_mods or set()) | mapped
|
|
1234
|
+
return SemTokenInfo(token.line, token.col_offset, token.length, sem_type, sem_mods)
|
|
1235
|
+
|
|
1236
|
+
def _iter_model_sem_tokens(
|
|
1237
|
+
self, stmt: SemanticStatement, model: SemanticModel, is_inner: bool = False
|
|
1238
|
+
) -> Iterator[SemTokenInfo]:
|
|
1239
|
+
"""Yield SemTokenInfo for one statement, matching the legacy output.
|
|
1240
|
+
|
|
1241
|
+
`is_inner` marks a Run Keyword inner call (its BDD gap token renders
|
|
1242
|
+
as keywordCallInner).
|
|
1243
|
+
"""
|
|
1244
|
+
# Documentation statements emit only their continuation markers
|
|
1245
|
+
# (legacy renders neither the setting name nor the body).
|
|
1246
|
+
if stmt.kind is NodeKind.SETTING_DOCUMENTATION:
|
|
1247
|
+
for token in stmt.tokens:
|
|
1248
|
+
if token.kind is TokenKind.CONTINUATION:
|
|
1249
|
+
yield SemTokenInfo(token.line, token.col_offset, token.length, RobotSemTokenTypes.CONTINUATION)
|
|
1250
|
+
return
|
|
1251
|
+
|
|
1252
|
+
section = model.enclosing_section(stmt)
|
|
1253
|
+
emit_comments = stmt.kind in self._COMMENT_STMT_KINDS or (
|
|
1254
|
+
section is not None and section.kind is NodeKind.INVALID_SECTION
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
if cached_isinstance(stmt, RunKeywordCallStatement):
|
|
1258
|
+
kw_doc = stmt.keyword_doc
|
|
1259
|
+
# Legacy only decomposes the hardcoded BuiltIn run keywords;
|
|
1260
|
+
# register-only run keywords render flat.
|
|
1261
|
+
if kw_doc is not None and kw_doc.is_any_run_keyword():
|
|
1262
|
+
yield from self._iter_run_keyword_sem_tokens(stmt, model, emit_comments, is_inner)
|
|
1263
|
+
return
|
|
1264
|
+
|
|
1265
|
+
yield_arguments = stmt.kind in self._YIELD_ARGUMENT_STMT_KINDS
|
|
1266
|
+
|
|
1267
|
+
prev: Optional[SemanticToken] = None
|
|
1268
|
+
for token in stmt.tokens:
|
|
1269
|
+
gap = self._bdd_gap_token(stmt, prev, token, is_inner)
|
|
1270
|
+
if gap is not None:
|
|
1271
|
+
yield gap
|
|
1272
|
+
yield from self._render_model_token(token, emit_comments, yield_arguments)
|
|
1273
|
+
prev = token
|
|
1274
|
+
|
|
1275
|
+
def _bdd_gap_token(
|
|
1276
|
+
self,
|
|
1277
|
+
stmt: SemanticStatement,
|
|
1278
|
+
prev: Optional[SemanticToken],
|
|
1279
|
+
token: SemanticToken,
|
|
1280
|
+
is_inner: bool,
|
|
1281
|
+
) -> Optional[SemTokenInfo]:
|
|
1282
|
+
"""Legacy quirk: a BDD prefix (`Given`/`When`/...) emits a length-1
|
|
1283
|
+
keyword-typed token for the separator character between the prefix
|
|
1284
|
+
word and the keyword name. The model folds that space into the prefix
|
|
1285
|
+
offset, so it is synthesized here from adjacent leaf kinds."""
|
|
1286
|
+
if (
|
|
1287
|
+
prev is None
|
|
1288
|
+
or prev.kind is not TokenKind.BDD_PREFIX
|
|
1289
|
+
or token.kind not in self._BDD_FOLLOW_KINDS
|
|
1290
|
+
or token.col_offset <= prev.col_offset + prev.length
|
|
1291
|
+
):
|
|
1292
|
+
return None
|
|
1293
|
+
if is_inner:
|
|
1294
|
+
sem_type: AnyTokenType = RobotSemTokenTypes.KEYWORD_INNER
|
|
1295
|
+
elif stmt.kind in self._NAME_KEYWORD_STMT_KINDS:
|
|
1296
|
+
sem_type = RobotSemTokenTypes.ARGUMENT
|
|
1297
|
+
else:
|
|
1298
|
+
sem_type = RobotSemTokenTypes.KEYWORD
|
|
1299
|
+
gap_col = prev.col_offset + prev.length
|
|
1300
|
+
return SemTokenInfo(prev.line, gap_col, token.col_offset - gap_col, sem_type)
|
|
1301
|
+
|
|
1302
|
+
def _render_model_token(
|
|
1303
|
+
self,
|
|
1304
|
+
token: SemanticToken,
|
|
1305
|
+
emit_comments: bool,
|
|
1306
|
+
yield_arguments: bool,
|
|
1307
|
+
) -> Iterator[SemTokenInfo]:
|
|
1308
|
+
"""Render one SemanticToken (recursively descending into sub-tokens)
|
|
1309
|
+
through the static tables and the declarative emission policy."""
|
|
1310
|
+
kind = token.kind
|
|
1311
|
+
mods = token.modifiers
|
|
1312
|
+
|
|
1313
|
+
if kind in self._MODEL_ONLY_KINDS:
|
|
1314
|
+
return
|
|
1315
|
+
|
|
1316
|
+
if kind in self._VARIABLE_KINDS and token.sub_tokens:
|
|
1317
|
+
for sub in token.sub_tokens:
|
|
1318
|
+
yield from self._render_variable_part(sub, token)
|
|
1319
|
+
return
|
|
1320
|
+
|
|
1321
|
+
# Atomic kinds render whole; their sub-structure stays model-only.
|
|
1322
|
+
if kind in self._ATOMIC_KINDS:
|
|
1323
|
+
info = self._emit_model_token(token)
|
|
1324
|
+
if info is not None:
|
|
1325
|
+
yield info
|
|
1326
|
+
return
|
|
1327
|
+
|
|
1328
|
+
# Legacy quirk: `[Documentation]` / `Metadata` setting names render
|
|
1329
|
+
# as nothing.
|
|
1330
|
+
if kind is TokenKind.SETTING_NAME and mods and TokenModifier.DOCUMENTATION in mods:
|
|
1331
|
+
return
|
|
1332
|
+
|
|
1333
|
+
# Legacy quirk: an embedded keyword name that does not match its own
|
|
1334
|
+
# pattern (parent carries EMBEDDED, no sub-tokens) renders as nothing.
|
|
1335
|
+
if (
|
|
1336
|
+
kind in (TokenKind.KEYWORD, TokenKind.KEYWORD_INNER)
|
|
1337
|
+
and mods
|
|
1338
|
+
and TokenModifier.EMBEDDED in mods
|
|
1339
|
+
and not token.sub_tokens
|
|
1340
|
+
):
|
|
1341
|
+
return
|
|
1342
|
+
|
|
1343
|
+
# Leaf descent: emit the leaves instead of the parent.
|
|
1344
|
+
if token.sub_tokens:
|
|
1345
|
+
for sub in token.sub_tokens:
|
|
1346
|
+
yield from self._render_model_token(sub, emit_comments, yield_arguments)
|
|
1347
|
+
return
|
|
1348
|
+
|
|
1349
|
+
# Whitespace / line-structure tokens are never rendered.
|
|
1350
|
+
if kind is TokenKind.SEPARATOR or kind is TokenKind.EOL:
|
|
1351
|
+
return
|
|
1352
|
+
|
|
1353
|
+
if kind is TokenKind.COMMENT:
|
|
1354
|
+
if emit_comments:
|
|
1355
|
+
yield SemTokenInfo(token.line, token.col_offset, token.length, SemanticTokenTypes.COMMENT)
|
|
1356
|
+
return
|
|
1357
|
+
|
|
1358
|
+
# Plain argument text renders only in template rows / metadata values
|
|
1359
|
+
# and for embedded-argument fragments.
|
|
1360
|
+
if kind in self._ARGUMENT_TEXT_KINDS and not yield_arguments and not (mods and TokenModifier.EMBEDDED in mods):
|
|
1361
|
+
return
|
|
1362
|
+
|
|
1363
|
+
info = self._emit_model_token(token)
|
|
1364
|
+
if info is not None:
|
|
1365
|
+
yield info
|
|
1366
|
+
|
|
1367
|
+
def _render_variable_part(self, token: SemanticToken, variable: SemanticToken) -> Iterator[SemTokenInfo]:
|
|
1368
|
+
"""Render one sub-token of `variable`: its name with the type and
|
|
1369
|
+
modifiers of the variable, its type hint as `type`, nested variables as
|
|
1370
|
+
variables of their own, and nothing else."""
|
|
1371
|
+
kind = token.kind
|
|
1372
|
+
if kind in self._VARIABLE_KINDS:
|
|
1373
|
+
yield from self._render_model_token(token, emit_comments=False, yield_arguments=False)
|
|
1374
|
+
return
|
|
1375
|
+
if kind is TokenKind.PYTHON_EXPRESSION:
|
|
1376
|
+
return
|
|
1377
|
+
if token.sub_tokens:
|
|
1378
|
+
for sub in token.sub_tokens:
|
|
1379
|
+
yield from self._render_variable_part(sub, variable)
|
|
1380
|
+
return
|
|
1381
|
+
if kind is TokenKind.VARIABLE_BASE:
|
|
1382
|
+
info = self._emit_model_token(variable)
|
|
1383
|
+
if info is not None:
|
|
1384
|
+
yield SemTokenInfo(token.line, token.col_offset, token.length, info.sem_token_type, info.sem_modifiers)
|
|
1385
|
+
elif kind is TokenKind.VARIABLE_TYPE_HINT:
|
|
1386
|
+
yield SemTokenInfo(token.line, token.col_offset, token.length, SemanticTokenTypes.TYPE)
|
|
1387
|
+
|
|
1388
|
+
def _iter_run_keyword_sem_tokens(
|
|
1389
|
+
self,
|
|
1390
|
+
stmt: "RunKeywordCallStatement",
|
|
1391
|
+
model: SemanticModel,
|
|
1392
|
+
emit_comments: bool,
|
|
1393
|
+
is_inner: bool,
|
|
1394
|
+
) -> Iterator[SemTokenInfo]:
|
|
1395
|
+
"""Render a Run Keyword variant, position-merging inner calls with the
|
|
1396
|
+
outer token stream. Cells covered by an inner call render from the
|
|
1397
|
+
inner (KEYWORD_INNER) decomposition; the remaining outer tokens (outer
|
|
1398
|
+
keyword, condition, ELSE/AND control-flow cells) render from the outer
|
|
1399
|
+
list. The merge is purely structural."""
|
|
1400
|
+
inner_infos: List[SemTokenInfo] = []
|
|
1401
|
+
covered: List[Tuple[Tuple[int, int], Tuple[int, int]]] = []
|
|
1402
|
+
for inner in stmt.inner_calls:
|
|
1403
|
+
span = self._statement_span(inner)
|
|
1404
|
+
if span is not None:
|
|
1405
|
+
covered.append(span)
|
|
1406
|
+
inner_infos.extend(self._iter_model_sem_tokens(inner, model, is_inner=True))
|
|
1407
|
+
|
|
1408
|
+
outer_infos: List[SemTokenInfo] = []
|
|
1409
|
+
prev: Optional[SemanticToken] = None
|
|
1410
|
+
for token in stmt.tokens:
|
|
1411
|
+
pos = (token.line, token.col_offset)
|
|
1412
|
+
if any(start <= pos < end for start, end in covered):
|
|
1413
|
+
prev = token
|
|
1414
|
+
continue
|
|
1415
|
+
gap = self._bdd_gap_token(stmt, prev, token, is_inner)
|
|
1416
|
+
if gap is not None:
|
|
1417
|
+
outer_infos.append(gap)
|
|
1418
|
+
outer_infos.extend(self._render_model_token(token, emit_comments, False))
|
|
1419
|
+
prev = token
|
|
1420
|
+
|
|
1421
|
+
merged = inner_infos + outer_infos
|
|
1422
|
+
merged.sort(key=lambda i: (i.lineno, i.col_offset))
|
|
1423
|
+
yield from merged
|
|
1424
|
+
|
|
1425
|
+
@staticmethod
|
|
1426
|
+
def _statement_span(
|
|
1427
|
+
stmt: SemanticStatement,
|
|
1428
|
+
) -> Optional[Tuple[Tuple[int, int], Tuple[int, int]]]:
|
|
1429
|
+
"""Source span (start, end) as (line, col) tuples over all tokens of a
|
|
1430
|
+
statement, recursing into nested Run Keyword inner calls."""
|
|
1431
|
+
start: Optional[Tuple[int, int]] = None
|
|
1432
|
+
end: Optional[Tuple[int, int]] = None
|
|
1433
|
+
|
|
1434
|
+
def visit(s: SemanticStatement) -> None:
|
|
1435
|
+
nonlocal start, end
|
|
1436
|
+
for token in s.tokens:
|
|
1437
|
+
lo = (token.line, token.col_offset)
|
|
1438
|
+
hi = (token.line, token.col_offset + token.length)
|
|
1439
|
+
if start is None or lo < start:
|
|
1440
|
+
start = lo
|
|
1441
|
+
if end is None or hi > end:
|
|
1442
|
+
end = hi
|
|
1443
|
+
if cached_isinstance(s, RunKeywordCallStatement):
|
|
1444
|
+
for inner in s.inner_calls:
|
|
1445
|
+
visit(inner)
|
|
1446
|
+
|
|
1447
|
+
visit(stmt)
|
|
1448
|
+
if start is None or end is None:
|
|
1449
|
+
return None
|
|
1450
|
+
return start, end
|
|
1451
|
+
|
|
1162
1452
|
def _get_tokens_after(self, tokens: List[Token], target_token: Token) -> List[Token]:
|
|
1163
1453
|
"""Get all tokens after target token efficiently.
|
|
1164
1454
|
|
|
@@ -1194,6 +1484,7 @@ class SemanticTokenGenerator:
|
|
|
1194
1484
|
col_offset: Optional[int] = None,
|
|
1195
1485
|
length: Optional[int] = None,
|
|
1196
1486
|
yield_arguments: bool = False,
|
|
1487
|
+
site: VariableSite = VariableSite.USAGE,
|
|
1197
1488
|
) -> Iterator[SemTokenInfo]:
|
|
1198
1489
|
"""Generate semantic token information for Robot Framework tokens.
|
|
1199
1490
|
|
|
@@ -1205,6 +1496,7 @@ class SemanticTokenGenerator:
|
|
|
1205
1496
|
col_offset: Optional column offset override
|
|
1206
1497
|
length: Optional length override
|
|
1207
1498
|
yield_arguments: Whether to yield argument tokens
|
|
1499
|
+
site: Where a variable token stands
|
|
1208
1500
|
|
|
1209
1501
|
Yields:
|
|
1210
1502
|
SemTokenInfo: Semantic token information for LSP client
|
|
@@ -1393,37 +1685,8 @@ class SemanticTokenGenerator:
|
|
|
1393
1685
|
col_offset,
|
|
1394
1686
|
length,
|
|
1395
1687
|
)
|
|
1396
|
-
elif token.type
|
|
1397
|
-
|
|
1398
|
-
cached_isinstance(node, ExceptHeader) or cached_isinstance(node, WhileHeader)
|
|
1399
|
-
) and "=" in token.value:
|
|
1400
|
-
if col_offset is None:
|
|
1401
|
-
col_offset = token.col_offset
|
|
1402
|
-
|
|
1403
|
-
name, value = token.value.split("=", 1)
|
|
1404
|
-
yield SemTokenInfo.from_token(
|
|
1405
|
-
token,
|
|
1406
|
-
RobotSemTokenTypes.VARIABLE,
|
|
1407
|
-
sem_mod,
|
|
1408
|
-
col_offset,
|
|
1409
|
-
len(name),
|
|
1410
|
-
)
|
|
1411
|
-
yield SemTokenInfo.from_token(
|
|
1412
|
-
token,
|
|
1413
|
-
SemanticTokenTypes.OPERATOR,
|
|
1414
|
-
sem_mod,
|
|
1415
|
-
col_offset + len(name),
|
|
1416
|
-
1,
|
|
1417
|
-
)
|
|
1418
|
-
yield SemTokenInfo.from_token(
|
|
1419
|
-
token,
|
|
1420
|
-
sem_type,
|
|
1421
|
-
sem_mod,
|
|
1422
|
-
col_offset + len(name) + 1,
|
|
1423
|
-
len(value),
|
|
1424
|
-
)
|
|
1425
|
-
else:
|
|
1426
|
-
yield SemTokenInfo.from_token(token, sem_type, sem_mod, col_offset, length)
|
|
1688
|
+
elif token.type in (Token.VARIABLE, Token.ASSIGN):
|
|
1689
|
+
yield from self._generate_variable_sem_tokens(namespace, token, sem_type, sem_mod, site)
|
|
1427
1690
|
elif (
|
|
1428
1691
|
token.type in Token.SETTING_TOKENS and token.value and token.value[0] == "[" and token.value[-1] == "]"
|
|
1429
1692
|
):
|
|
@@ -1471,33 +1734,37 @@ class SemanticTokenGenerator:
|
|
|
1471
1734
|
if token.type in {Token.ARGUMENT, Token.TESTCASE_NAME, Token.KEYWORD_NAME} or (
|
|
1472
1735
|
token.type == Token.NAME and cached_isinstance(node, VariablesImport, LibraryImport, ResourceImport)
|
|
1473
1736
|
):
|
|
1474
|
-
if (
|
|
1737
|
+
if cached_isinstance(node, Arguments):
|
|
1738
|
+
# The name of an argument is a parameter; a default value continues as argument.
|
|
1739
|
+
name, value = split_from_equals(token.value)
|
|
1740
|
+
if name:
|
|
1741
|
+
yield from self._generate_variable_sem_tokens(
|
|
1742
|
+
namespace,
|
|
1743
|
+
Token(Token.VARIABLE, name, token.lineno, token.col_offset),
|
|
1744
|
+
SemanticTokenTypes.PARAMETER,
|
|
1745
|
+
None,
|
|
1746
|
+
VariableSite.DECLARATION,
|
|
1747
|
+
)
|
|
1748
|
+
token = Token(
|
|
1749
|
+
token.type,
|
|
1750
|
+
value or "",
|
|
1751
|
+
token.lineno,
|
|
1752
|
+
token.col_offset + len(name) + (1 if value is not None else 0),
|
|
1753
|
+
token.error,
|
|
1754
|
+
)
|
|
1755
|
+
elif (
|
|
1475
1756
|
cached_isinstance(node, Variable) and token.type == Token.ARGUMENT and node.name and node.name[0] == "&"
|
|
1476
|
-
)
|
|
1757
|
+
):
|
|
1477
1758
|
name, value = split_from_equals(token.value)
|
|
1478
1759
|
if value is not None:
|
|
1479
1760
|
length = len(name)
|
|
1480
1761
|
|
|
1481
1762
|
yield SemTokenInfo.from_token(
|
|
1482
|
-
Token(
|
|
1483
|
-
|
|
1484
|
-
name,
|
|
1485
|
-
token.lineno,
|
|
1486
|
-
token.col_offset,
|
|
1487
|
-
),
|
|
1488
|
-
(
|
|
1489
|
-
RobotSemTokenTypes.NAMED_ARGUMENT
|
|
1490
|
-
if cached_isinstance(node, Variable)
|
|
1491
|
-
else SemanticTokenTypes.PARAMETER
|
|
1492
|
-
),
|
|
1763
|
+
Token(ROBOT_NAMED_ARGUMENT, name, token.lineno, token.col_offset),
|
|
1764
|
+
RobotSemTokenTypes.NAMED_ARGUMENT,
|
|
1493
1765
|
)
|
|
1494
1766
|
yield SemTokenInfo.from_token(
|
|
1495
|
-
Token(
|
|
1496
|
-
ROBOT_OPERATOR,
|
|
1497
|
-
"=",
|
|
1498
|
-
token.lineno,
|
|
1499
|
-
token.col_offset + length,
|
|
1500
|
-
),
|
|
1767
|
+
Token(ROBOT_OPERATOR, "=", token.lineno, token.col_offset + length),
|
|
1501
1768
|
SemanticTokenTypes.OPERATOR,
|
|
1502
1769
|
)
|
|
1503
1770
|
token = Token(
|
|
@@ -1507,36 +1774,70 @@ class SemanticTokenGenerator:
|
|
|
1507
1774
|
token.col_offset + length + 1,
|
|
1508
1775
|
token.error,
|
|
1509
1776
|
)
|
|
1510
|
-
elif cached_isinstance(node, Arguments) and name:
|
|
1511
|
-
yield SemTokenInfo.from_token(
|
|
1512
|
-
Token(
|
|
1513
|
-
ROBOT_NAMED_ARGUMENT,
|
|
1514
|
-
name,
|
|
1515
|
-
token.lineno,
|
|
1516
|
-
token.col_offset,
|
|
1517
|
-
),
|
|
1518
|
-
RobotSemTokenTypes.NAMED_ARGUMENT,
|
|
1519
|
-
)
|
|
1520
|
-
token = Token(
|
|
1521
|
-
token.type,
|
|
1522
|
-
"",
|
|
1523
|
-
token.lineno,
|
|
1524
|
-
token.col_offset + len(name),
|
|
1525
|
-
token.error,
|
|
1526
|
-
)
|
|
1527
1777
|
|
|
1778
|
+
site = VariableSite.KEYWORD_NAME if token.type == Token.KEYWORD_NAME else VariableSite.USAGE
|
|
1528
1779
|
for sub_token in ModelHelper.tokenize_variables(
|
|
1529
1780
|
token,
|
|
1530
1781
|
ignore_errors=True,
|
|
1531
1782
|
identifiers="$" if token.type == Token.KEYWORD_NAME else "$@&%",
|
|
1532
1783
|
):
|
|
1533
|
-
for e in self.generate_sem_sub_tokens(namespace, builtin_library_doc, sub_token, node):
|
|
1784
|
+
for e in self.generate_sem_sub_tokens(namespace, builtin_library_doc, sub_token, node, site=site):
|
|
1534
1785
|
yield e
|
|
1535
1786
|
|
|
1536
1787
|
else:
|
|
1537
|
-
|
|
1788
|
+
# A whole VARIABLE or ASSIGN cell defines a variable.
|
|
1789
|
+
site = (
|
|
1790
|
+
VariableSite.UNTYPED_DECLARATION if cached_isinstance(node, ExceptHeader) else VariableSite.DECLARATION
|
|
1791
|
+
)
|
|
1792
|
+
for e in self.generate_sem_sub_tokens(namespace, builtin_library_doc, token, node, site=site):
|
|
1538
1793
|
yield e
|
|
1539
1794
|
|
|
1795
|
+
def _generate_variable_sem_tokens(
|
|
1796
|
+
self,
|
|
1797
|
+
namespace: Namespace,
|
|
1798
|
+
token: Token,
|
|
1799
|
+
sem_type: AnyTokenType,
|
|
1800
|
+
sem_mod: Optional[Set[AnyTokenModifier]],
|
|
1801
|
+
site: VariableSite,
|
|
1802
|
+
) -> Iterator[SemTokenInfo]:
|
|
1803
|
+
"""A variable sends its name with `sem_type` and its type hint as `type`;
|
|
1804
|
+
prefix, braces, item access, `=`, patterns, defaults and inline Python
|
|
1805
|
+
are left to the grammar. The parts are split like the semantic model
|
|
1806
|
+
splits them."""
|
|
1807
|
+
position = Position(line=token.lineno - 1, character=token.col_offset)
|
|
1808
|
+
|
|
1809
|
+
def is_defined(name: str) -> bool:
|
|
1810
|
+
return namespace.find_variable(name, position=position) is not None
|
|
1811
|
+
|
|
1812
|
+
sub_tokens = build_variable_sub_tokens(
|
|
1813
|
+
token.value, token.lineno, token.col_offset, site=site, is_defined=is_defined
|
|
1814
|
+
)
|
|
1815
|
+
if not sub_tokens:
|
|
1816
|
+
yield SemTokenInfo.from_token(token, sem_type, sem_mod)
|
|
1817
|
+
return
|
|
1818
|
+
yield from self._iter_variable_name_sem_tokens(sub_tokens, sem_type, sem_mod)
|
|
1819
|
+
|
|
1820
|
+
def _iter_variable_name_sem_tokens(
|
|
1821
|
+
self,
|
|
1822
|
+
sub_tokens: Sequence[SemanticToken],
|
|
1823
|
+
sem_type: AnyTokenType,
|
|
1824
|
+
sem_mod: Optional[Set[AnyTokenModifier]],
|
|
1825
|
+
) -> Iterator[SemTokenInfo]:
|
|
1826
|
+
for sub in sub_tokens:
|
|
1827
|
+
if sub.kind is TokenKind.VARIABLE:
|
|
1828
|
+
if sub.sub_tokens:
|
|
1829
|
+
yield from self._iter_variable_name_sem_tokens(sub.sub_tokens, RobotSemTokenTypes.VARIABLE, None)
|
|
1830
|
+
else:
|
|
1831
|
+
yield SemTokenInfo(sub.line, sub.col_offset, sub.length, RobotSemTokenTypes.VARIABLE)
|
|
1832
|
+
elif sub.kind is TokenKind.PYTHON_EXPRESSION:
|
|
1833
|
+
continue
|
|
1834
|
+
elif sub.sub_tokens:
|
|
1835
|
+
yield from self._iter_variable_name_sem_tokens(sub.sub_tokens, sem_type, sem_mod)
|
|
1836
|
+
elif sub.kind is TokenKind.VARIABLE_BASE:
|
|
1837
|
+
yield SemTokenInfo(sub.line, sub.col_offset, sub.length, sem_type, sem_mod)
|
|
1838
|
+
elif sub.kind is TokenKind.VARIABLE_TYPE_HINT:
|
|
1839
|
+
yield SemTokenInfo(sub.line, sub.col_offset, sub.length, SemanticTokenTypes.TYPE)
|
|
1840
|
+
|
|
1540
1841
|
def collect_tokens(
|
|
1541
1842
|
self,
|
|
1542
1843
|
document: TextDocument,
|