robotcode-language-server 2.5.1__tar.gz → 2.6.1__tar.gz

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 (78) hide show
  1. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/.gitignore +5 -1
  2. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/PKG-INFO +5 -5
  3. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/pyproject.toml +4 -4
  4. robotcode_language_server-2.6.1/src/robotcode/language_server/__version__.py +1 -0
  5. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/diagnostics.py +1 -1
  6. robotcode_language_server-2.6.1/src/robotcode/language_server/robotframework/parts/code_action_documentation.py +375 -0
  7. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/code_action_quick_fixes.py +171 -93
  8. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/code_action_refactor.py +107 -67
  9. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/diagnostics.py +2 -1
  10. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/formatting.py +1 -1
  11. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/hover.py +1 -1
  12. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/inlay_hint.py +230 -3
  13. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/semantic_tokens.py +132 -0
  14. robotcode_language_server-2.6.1/src/robotcode/language_server/robotframework/parts/signature_help.py +744 -0
  15. robotcode_language_server-2.5.1/src/robotcode/language_server/__version__.py +0 -1
  16. robotcode_language_server-2.5.1/src/robotcode/language_server/robotframework/parts/code_action_documentation.py +0 -229
  17. robotcode_language_server-2.5.1/src/robotcode/language_server/robotframework/parts/signature_help.py +0 -348
  18. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/README.md +0 -0
  19. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/__init__.py +0 -0
  20. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/cli.py +0 -0
  21. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/__init__.py +0 -0
  22. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/decorators.py +0 -0
  23. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/__init__.py +0 -0
  24. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/code_action.py +0 -0
  25. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/code_lens.py +0 -0
  26. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/commands.py +0 -0
  27. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/completion.py +0 -0
  28. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/declaration.py +0 -0
  29. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/definition.py +0 -0
  30. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/document_highlight.py +0 -0
  31. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/document_symbols.py +0 -0
  32. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/documents.py +0 -0
  33. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/folding_range.py +0 -0
  34. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/formatting.py +0 -0
  35. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/hover.py +0 -0
  36. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/implementation.py +0 -0
  37. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/inlay_hint.py +0 -0
  38. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/inline_value.py +0 -0
  39. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/linked_editing_ranges.py +0 -0
  40. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/protocol_part.py +0 -0
  41. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/references.py +0 -0
  42. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/rename.py +0 -0
  43. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/selection_range.py +0 -0
  44. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/semantic_tokens.py +0 -0
  45. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/signature_help.py +0 -0
  46. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/window.py +0 -0
  47. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/workspace.py +0 -0
  48. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/parts/workspace_symbols.py +0 -0
  49. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/protocol.py +0 -0
  50. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/common/server.py +0 -0
  51. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/hooks.py +0 -0
  52. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/py.typed +0 -0
  53. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/__init__.py +0 -0
  54. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/configuration.py +0 -0
  55. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/__init__.py +0 -0
  56. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/code_action_helper_mixin.py +0 -0
  57. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/code_lens.py +0 -0
  58. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/completion.py +0 -0
  59. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/debugging_utils.py +0 -0
  60. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/document_highlight.py +0 -0
  61. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/document_symbols.py +0 -0
  62. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/documents_cache.py +0 -0
  63. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/folding_range.py +0 -0
  64. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/goto.py +0 -0
  65. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/http_server.py +0 -0
  66. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/inline_value.py +0 -0
  67. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/keywords_treeview.py +0 -0
  68. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/project_info.py +0 -0
  69. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/protocol_part.py +0 -0
  70. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/references.py +0 -0
  71. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/rename.py +0 -0
  72. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/robocop_diagnostics.py +0 -0
  73. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/robocop_helper.py +0 -0
  74. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/robot_workspace.py +0 -0
  75. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/selection_range.py +0 -0
  76. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/parts/workspace_symbols.py +0 -0
  77. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/protocol.py +0 -0
  78. {robotcode_language_server-2.5.1 → robotcode_language_server-2.6.1}/src/robotcode/language_server/robotframework/server.py +0 -0
@@ -331,7 +331,7 @@ output.xml
331
331
  bundled/libs
332
332
 
333
333
  # robotframework
334
- results/
334
+ /results/
335
335
 
336
336
  # kilocode
337
337
  .kilocode/
@@ -339,3 +339,7 @@ results/
339
339
  # .agents
340
340
  .agents/
341
341
  skills-lock.json
342
+ .claude
343
+
344
+ # sarif files
345
+ /**/*.sarif.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotcode-language-server
3
- Version: 2.5.1
3
+ Version: 2.6.1
4
4
  Summary: RobotCode Language Server for Robot Framework
5
5
  Project-URL: Homepage, https://robotcode.io
6
6
  Project-URL: Donate, https://opencollective.com/robotcode
@@ -25,10 +25,10 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
25
25
  Classifier: Topic :: Utilities
26
26
  Classifier: Typing :: Typed
27
27
  Requires-Python: >=3.10
28
- Requires-Dist: robotcode
29
- Requires-Dist: robotcode-analyze
30
- Requires-Dist: robotcode-jsonrpc2
31
- Requires-Dist: robotcode-robot
28
+ Requires-Dist: robotcode-analyze==2.6.1
29
+ Requires-Dist: robotcode-jsonrpc2==2.6.1
30
+ Requires-Dist: robotcode-robot==2.6.1
31
+ Requires-Dist: robotcode==2.6.1
32
32
  Requires-Dist: robotframework>=5.0.0
33
33
  Description-Content-Type: text/markdown
34
34
 
@@ -28,10 +28,10 @@ classifiers = [
28
28
  ]
29
29
  dependencies = [
30
30
  "robotframework>=5.0.0",
31
- "robotcode-jsonrpc2",
32
- "robotcode-robot",
33
- "robotcode-analyze",
34
- "robotcode",
31
+ "robotcode-jsonrpc2==2.6.1",
32
+ "robotcode-robot==2.6.1",
33
+ "robotcode-analyze==2.6.1",
34
+ "robotcode==2.6.1",
35
35
  ]
36
36
  dynamic = ["version"]
37
37
 
@@ -436,7 +436,7 @@ class DiagnosticsProtocolPart(LanguageServerProtocolPart):
436
436
  documents_to_collect = [
437
437
  doc
438
438
  for doc in documents
439
- if doc.opened_in_editor or self.get_diagnostics_mode(document.uri) == DiagnosticsMode.WORKSPACE
439
+ if doc.opened_in_editor or self.get_diagnostics_mode(doc.uri) == DiagnosticsMode.WORKSPACE
440
440
  ]
441
441
 
442
442
  with self._logger.measure_time(
@@ -0,0 +1,375 @@
1
+ import urllib.parse
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union, cast
4
+
5
+ from robot.parsing.lexer.tokens import Token
6
+
7
+ from robotcode.core.language import language_id
8
+ from robotcode.core.lsp.types import (
9
+ CodeAction,
10
+ CodeActionContext,
11
+ CodeActionKind,
12
+ Command,
13
+ Position,
14
+ Range,
15
+ )
16
+ from robotcode.core.text_document import TextDocument
17
+ from robotcode.core.uri import Uri
18
+ from robotcode.core.utils.dataclasses import CamelSnakeMixin
19
+ from robotcode.core.utils.logging import LoggingDescriptor
20
+ from robotcode.jsonrpc2.protocol import rpc_method
21
+ from robotcode.robot.diagnostics.entities import LibraryEntry
22
+ from robotcode.robot.diagnostics.library_doc import KeywordDoc, resolve_robot_variables
23
+ from robotcode.robot.diagnostics.model_helper import ModelHelper
24
+ from robotcode.robot.diagnostics.namespace import Namespace
25
+ from robotcode.robot.diagnostics.semantic_analyzer.enums import ImportType, NodeKind, TokenKind
26
+ from robotcode.robot.diagnostics.semantic_analyzer.model import SemanticModel
27
+ from robotcode.robot.diagnostics.semantic_analyzer.nodes import (
28
+ DefinitionStatement,
29
+ ImportStatement,
30
+ KeywordCallStatement,
31
+ )
32
+ from robotcode.robot.utils.ast import get_node_at_position, range_from_token
33
+
34
+ from ...common.decorators import code_action_kinds
35
+ from .protocol_part import RobotLanguageServerProtocolPart
36
+
37
+ if TYPE_CHECKING:
38
+ from ..protocol import RobotLanguageServerProtocol
39
+
40
+
41
+ @dataclass(repr=False)
42
+ class ConvertUriParams(CamelSnakeMixin):
43
+ uri: str
44
+
45
+
46
+ class RobotCodeActionDocumentationProtocolPart(RobotLanguageServerProtocolPart, ModelHelper):
47
+ _logger = LoggingDescriptor()
48
+
49
+ def __init__(self, parent: "RobotLanguageServerProtocol") -> None:
50
+ super().__init__(parent)
51
+ self.parent.commands.register_all(self)
52
+
53
+ parent.code_action.collect.add(self.collect)
54
+
55
+ @language_id("robotframework")
56
+ @code_action_kinds([CodeActionKind.SOURCE])
57
+ @_logger.call
58
+ def collect(
59
+ self,
60
+ sender: Any,
61
+ document: TextDocument,
62
+ range: Range,
63
+ context: CodeActionContext,
64
+ ) -> Optional[List[Union[Command, CodeAction]]]:
65
+ namespace = self.parent.documents_cache.get_namespace(document)
66
+
67
+ # Tier 2 model-based path — used when the experimental SemanticAnalyzer
68
+ # is enabled. Reads everything off the SemanticModel: statement kind
69
+ # via `model.statement_at()`, the pre-resolved `keyword_doc`, the
70
+ # `import_name`, and SemanticTokens for cursor-position checks. No
71
+ # `find_keyword`, no AST walk.
72
+ semantic_model = namespace.semantic_model
73
+ if semantic_model is not None:
74
+ return self._collect_from_model(document, range, context, namespace, semantic_model)
75
+
76
+ return self._collect_legacy(document, range, context, namespace)
77
+
78
+ def _collect_legacy(
79
+ self,
80
+ document: TextDocument,
81
+ range: Range,
82
+ context: CodeActionContext,
83
+ namespace: Namespace,
84
+ ) -> Optional[List[Union[Command, CodeAction]]]:
85
+ from robot.parsing.lexer import Token as RobotToken
86
+ from robot.parsing.model.statements import (
87
+ Fixture,
88
+ KeywordCall,
89
+ KeywordName,
90
+ LibraryImport,
91
+ ResourceImport,
92
+ Template,
93
+ TestTemplate,
94
+ )
95
+
96
+ model = self.parent.documents_cache.get_model(document)
97
+ node = get_node_at_position(model, range.start)
98
+
99
+ if context.only and isinstance(node, (LibraryImport, ResourceImport)):
100
+ if CodeActionKind.SOURCE.value in context.only and range in range_from_token(
101
+ node.get_token(RobotToken.NAME)
102
+ ):
103
+ url = self.build_url(
104
+ node.name,
105
+ node.args if isinstance(node, LibraryImport) else (),
106
+ document,
107
+ namespace,
108
+ )
109
+
110
+ return [self.open_documentation_code_action(url)]
111
+
112
+ if isinstance(node, (KeywordCall, Fixture, TestTemplate, Template)):
113
+ # only source actions
114
+
115
+ result = self.get_keyworddoc_and_token_from_position(
116
+ (
117
+ node.value
118
+ if isinstance(node, (TestTemplate, Template))
119
+ else node.keyword
120
+ if isinstance(node, KeywordCall)
121
+ else node.name
122
+ ),
123
+ cast(
124
+ Token,
125
+ node.get_token(RobotToken.KEYWORD if isinstance(node, KeywordCall) else RobotToken.NAME),
126
+ ),
127
+ [cast(Token, t) for t in node.get_tokens(RobotToken.ARGUMENT)],
128
+ namespace,
129
+ range.start,
130
+ )
131
+
132
+ if range.start != range.end:
133
+ return None
134
+
135
+ if result is not None:
136
+ kw_doc, _ = result
137
+
138
+ if kw_doc is not None:
139
+ if context.only and CodeActionKind.SOURCE.value in context.only:
140
+ return self._build_keyword_action(kw_doc, document, namespace)
141
+
142
+ if isinstance(node, KeywordName):
143
+ name_token = node.get_token(RobotToken.KEYWORD_NAME)
144
+ if name_token is not None and range in range_from_token(name_token):
145
+ url = self.build_url(
146
+ str(document.uri.to_path().name),
147
+ (),
148
+ document,
149
+ namespace,
150
+ name_token.value,
151
+ )
152
+
153
+ return [self.open_documentation_code_action(url)]
154
+
155
+ return None
156
+
157
+ # ------------------------------------------------------------------
158
+ # Tier 2 model-based collection
159
+ # ------------------------------------------------------------------
160
+
161
+ def _collect_from_model(
162
+ self,
163
+ document: TextDocument,
164
+ range: Range,
165
+ context: CodeActionContext,
166
+ namespace: Namespace,
167
+ model: SemanticModel,
168
+ ) -> Optional[List[Union[Command, CodeAction]]]:
169
+ """Mirror legacy three-branch logic (import / keyword-call / keyword-def)
170
+ purely off the SemanticModel — no AST walks, no `find_keyword`.
171
+
172
+ Position checks use SemanticTokens; URL inputs read from pre-resolved
173
+ statement fields (`import_name`, `keyword_doc`, `name`).
174
+ """
175
+ # SemanticModel uses 1-indexed lines; LSP positions are 0-indexed.
176
+ stmt = model.statement_at(range.start.line + 1)
177
+ if stmt is None:
178
+ return None
179
+
180
+ # Branch 1: Library / Resource import — gated on context.only at entry.
181
+ if (
182
+ context.only
183
+ and isinstance(stmt, ImportStatement)
184
+ and stmt.import_type in (ImportType.LIBRARY, ImportType.RESOURCE)
185
+ and CodeActionKind.SOURCE.value in context.only
186
+ ):
187
+ return self._import_action_from_model(stmt, document, range, namespace)
188
+
189
+ # Branch 2: keyword call / fixture / template.
190
+ if isinstance(stmt, KeywordCallStatement):
191
+ if range.start != range.end:
192
+ return None
193
+ kw_doc = stmt.keyword_doc
194
+ if kw_doc is None:
195
+ return None
196
+ if not self._cursor_on_keyword_reference(range.start, stmt):
197
+ return None
198
+ if not (context.only and CodeActionKind.SOURCE.value in context.only):
199
+ return None
200
+ return self._build_keyword_action(kw_doc, document, namespace)
201
+
202
+ # Branch 3: keyword definition header — no context.only check
203
+ # (legacy doesn't gate this branch either).
204
+ if isinstance(stmt, DefinitionStatement) and stmt.kind is NodeKind.KEYWORD_DEF:
205
+ name_tok = next((t for t in stmt.tokens if t.kind is TokenKind.KEYWORD_NAME), None)
206
+ if name_tok is None or range not in name_tok.range:
207
+ return None
208
+ url = self.build_url(
209
+ str(document.uri.to_path().name),
210
+ (),
211
+ document,
212
+ namespace,
213
+ name_tok.value,
214
+ )
215
+ return [self.open_documentation_code_action(url)]
216
+
217
+ return None
218
+
219
+ def _import_action_from_model(
220
+ self,
221
+ stmt: ImportStatement,
222
+ document: TextDocument,
223
+ range: Range,
224
+ namespace: Namespace,
225
+ ) -> Optional[List[Union[Command, CodeAction]]]:
226
+ """Library / Resource import branch built off SemanticTokens.
227
+
228
+ - The import path lives in the IMPORT_NAME token (cursor-position check).
229
+ - Library `args` are the ARGUMENT tokens BEFORE the optional WITH NAME
230
+ marker (CONTROL_FLOW); anything after is the alias and must be
231
+ excluded — matches RF's `LibraryImport.args` semantics.
232
+ - Resource imports never carry args (RF API returns ()).
233
+ """
234
+ name_tok = next((t for t in stmt.tokens if t.kind is TokenKind.IMPORT_NAME), None)
235
+ if name_tok is None or range not in name_tok.range:
236
+ return None
237
+
238
+ if stmt.import_type is ImportType.LIBRARY:
239
+ arg_values: List[str] = []
240
+ for tok in stmt.tokens:
241
+ if tok.kind is TokenKind.CONTROL_FLOW:
242
+ break # WITH NAME — everything after is the alias
243
+ if tok.kind is TokenKind.ARGUMENT:
244
+ arg_values.append(tok.value)
245
+ args: Tuple[str, ...] = tuple(arg_values)
246
+ else:
247
+ args = ()
248
+
249
+ url = self.build_url(stmt.import_name or "", args, document, namespace)
250
+ return [self.open_documentation_code_action(url)]
251
+
252
+ @staticmethod
253
+ def _cursor_on_keyword_reference(pos: Position, stmt: KeywordCallStatement) -> bool:
254
+ """Cursor is within the NAMESPACE / SEPARATOR / KEYWORD SemanticTokens
255
+ that make up the keyword reference (BDD prefix excluded). Mirrors
256
+ the legacy `position.is_in_range(range_from_token(keyword_token))`
257
+ after the BDD-prefix strip that
258
+ `get_keyworddoc_and_token_from_position` does.
259
+ """
260
+ return any(
261
+ pos in t.range
262
+ for t in stmt.tokens
263
+ if t.kind in (TokenKind.NAMESPACE, TokenKind.SEPARATOR, TokenKind.KEYWORD)
264
+ )
265
+
266
+ def _build_keyword_action(
267
+ self,
268
+ kw_doc: KeywordDoc,
269
+ document: TextDocument,
270
+ namespace: Namespace,
271
+ ) -> Optional[List[Union[Command, CodeAction]]]:
272
+ """Resolve the LibraryEntry that owns `kw_doc` and build the
273
+ Open-Documentation action. Shared between legacy and model paths so
274
+ the URL construction stays identical."""
275
+ entry: Optional[LibraryEntry] = None
276
+
277
+ if kw_doc.libtype == "LIBRARY":
278
+ entry = next(
279
+ (v for v in namespace.libraries.values() if v.library_doc == kw_doc.parent),
280
+ None,
281
+ )
282
+
283
+ elif kw_doc.libtype == "RESOURCE":
284
+ entry = next(
285
+ (v for v in namespace.resources.values() if v.library_doc == kw_doc.parent),
286
+ None,
287
+ )
288
+
289
+ self_libdoc = namespace.library_doc
290
+ if entry is None and self_libdoc == kw_doc.parent:
291
+ entry = LibraryEntry(
292
+ self_libdoc.name,
293
+ str(document.uri.to_path().name),
294
+ self_libdoc,
295
+ )
296
+
297
+ if entry is None:
298
+ return None
299
+
300
+ url = self.build_url(
301
+ entry.import_name,
302
+ entry.args,
303
+ document,
304
+ namespace,
305
+ kw_doc.name,
306
+ )
307
+
308
+ return [self.open_documentation_code_action(url)]
309
+
310
+ def open_documentation_code_action(self, url: str) -> CodeAction:
311
+ return CodeAction(
312
+ "Open Documentation",
313
+ kind=CodeActionKind.SOURCE,
314
+ command=Command("Open Documentation", "robotcode.showDocumentation", [url]),
315
+ )
316
+
317
+ def build_url(
318
+ self,
319
+ name: str,
320
+ args: Tuple[Any, ...],
321
+ document: TextDocument,
322
+ namespace: Namespace,
323
+ target: Optional[str] = None,
324
+ ) -> str:
325
+ base_dir = document.uri.to_path().parent
326
+
327
+ workspace_folder = self.parent.workspace.get_workspace_folder(document.uri)
328
+ if workspace_folder is not None:
329
+ try:
330
+ base_dir = base_dir.relative_to(workspace_folder.uri.to_path())
331
+ except ValueError:
332
+ pass
333
+
334
+ robot_variables = resolve_robot_variables(
335
+ str(namespace.imports_manager.root_folder),
336
+ str(base_dir),
337
+ namespace.imports_manager.get_resolvable_command_line_variables(),
338
+ variables=namespace.get_resolvable_variables(),
339
+ )
340
+ try:
341
+ name = robot_variables.replace_string(name, ignore_errors=False)
342
+
343
+ args = tuple(robot_variables.replace_string(v, ignore_errors=False) for v in args)
344
+
345
+ except (SystemExit, KeyboardInterrupt):
346
+ raise
347
+ except BaseException:
348
+ pass
349
+
350
+ url_args = "::".join(args) if args else ""
351
+
352
+ base_url = f"http://localhost:{self.parent.http_server.port}"
353
+ params = urllib.parse.urlencode(
354
+ {
355
+ "name": name,
356
+ "args": url_args,
357
+ "basedir": str(base_dir),
358
+ "theme": "${theme}",
359
+ }
360
+ )
361
+
362
+ return f"{base_url}/?&{params}{f'#{target}' if target else ''}"
363
+
364
+ @rpc_method(name="robot/documentationServer/convertUri", param_type=ConvertUriParams, threaded=True)
365
+ def _convert_uri(self, uri: str, *args: Any, **kwargs: Any) -> Optional[str]:
366
+ real_uri = Uri(uri)
367
+
368
+ folder = self.parent.workspace.get_workspace_folder(real_uri)
369
+
370
+ if folder:
371
+ path = real_uri.to_path().relative_to(folder.uri.to_path())
372
+
373
+ return f"http://localhost:{self.parent.http_server.port}/{path.as_posix()}"
374
+
375
+ return None