robotcode-language-server 2.7.0__py3-none-any.whl → 2.8.0.dev8__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 (34) hide show
  1. robotcode/language_server/__version__.py +1 -1
  2. robotcode/language_server/cli.py +30 -5
  3. robotcode/language_server/common/parts/diagnostics.py +50 -17
  4. robotcode/language_server/common/parts/documents.py +32 -26
  5. robotcode/language_server/common/parts/workspace.py +57 -55
  6. robotcode/language_server/common/protocol.py +3 -1
  7. robotcode/language_server/robotframework/configuration.py +3 -0
  8. robotcode/language_server/robotframework/parts/code_action_documentation.py +437 -146
  9. robotcode/language_server/robotframework/parts/code_action_helper_mixin.py +17 -2
  10. robotcode/language_server/robotframework/parts/code_action_quick_fixes.py +6 -25
  11. robotcode/language_server/robotframework/parts/code_action_refactor.py +8 -6
  12. robotcode/language_server/robotframework/parts/code_lens.py +3 -4
  13. robotcode/language_server/robotframework/parts/completion.py +193 -21
  14. robotcode/language_server/robotframework/parts/debugging_utils.py +75 -6
  15. robotcode/language_server/robotframework/parts/diagnostics.py +15 -3
  16. robotcode/language_server/robotframework/parts/documentation_viewer.py +127 -0
  17. robotcode/language_server/robotframework/parts/documents_cache.py +5 -1
  18. robotcode/language_server/robotframework/parts/hover.py +103 -21
  19. robotcode/language_server/robotframework/parts/http_server.py +17 -7
  20. robotcode/language_server/robotframework/parts/inline_value.py +69 -7
  21. robotcode/language_server/robotframework/parts/keywords_treeview.py +151 -53
  22. robotcode/language_server/robotframework/parts/model_variables.py +331 -0
  23. robotcode/language_server/robotframework/parts/references.py +107 -45
  24. robotcode/language_server/robotframework/parts/rename.py +1 -2
  25. robotcode/language_server/robotframework/parts/robot_workspace.py +229 -25
  26. robotcode/language_server/robotframework/parts/selection_range.py +26 -13
  27. robotcode/language_server/robotframework/parts/semantic_tokens.py +446 -113
  28. robotcode/language_server/robotframework/parts/signature_help.py +162 -70
  29. robotcode/language_server/robotframework/protocol.py +12 -1
  30. robotcode/language_server/robotframework/server.py +4 -2
  31. {robotcode_language_server-2.7.0.dist-info → robotcode_language_server-2.8.0.dev8.dist-info}/METADATA +7 -6
  32. {robotcode_language_server-2.7.0.dist-info → robotcode_language_server-2.8.0.dev8.dist-info}/RECORD +34 -32
  33. {robotcode_language_server-2.7.0.dist-info → robotcode_language_server-2.8.0.dev8.dist-info}/WHEEL +1 -1
  34. {robotcode_language_server-2.7.0.dist-info → robotcode_language_server-2.8.0.dev8.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  import ast
2
- from itertools import dropwhile, takewhile
2
+ from itertools import chain, dropwhile, takewhile
3
3
  from typing import TYPE_CHECKING, Any, Iterator, List, Optional, Tuple
4
4
 
5
5
  from robot.parsing.lexer.tokens import Token
@@ -10,24 +10,33 @@ from robotcode.core.lsp.types import (
10
10
  InlineValue,
11
11
  InlineValueContext,
12
12
  InlineValueEvaluatableExpression,
13
+ Position,
13
14
  Range,
14
15
  )
15
16
  from robotcode.core.text_document import TextDocument
16
17
  from robotcode.core.utils.logging import LoggingDescriptor
17
18
  from robotcode.robot.diagnostics.model_helper import ModelHelper
19
+ from robotcode.robot.diagnostics.semantic_analyzer.enums import NodeKind
20
+ from robotcode.robot.diagnostics.semantic_analyzer.model import SemanticModel
21
+ from robotcode.robot.diagnostics.semantic_analyzer.nodes import SemanticToken
18
22
  from robotcode.robot.utils.ast import (
19
23
  iter_nodes,
20
24
  range_from_node,
21
25
  range_from_token,
22
26
  )
23
27
 
28
+ from .model_variables import (
29
+ iter_condition_ref_candidates,
30
+ iter_model_variable_candidates,
31
+ scannable_statement_tokens,
32
+ )
24
33
  from .protocol_part import RobotLanguageServerProtocolPart
25
34
 
26
35
  if TYPE_CHECKING:
27
36
  from ..protocol import RobotLanguageServerProtocol
28
37
 
29
38
 
30
- class RobotInlineValueProtocolPart(RobotLanguageServerProtocolPart, ModelHelper):
39
+ class RobotInlineValueProtocolPart(RobotLanguageServerProtocolPart):
31
40
  _logger = LoggingDescriptor()
32
41
 
33
42
  def __init__(self, parent: "RobotLanguageServerProtocol") -> None:
@@ -50,10 +59,14 @@ class RobotInlineValueProtocolPart(RobotLanguageServerProtocolPart, ModelHelper)
50
59
 
51
60
  namespace = self.parent.documents_cache.get_namespace(document)
52
61
 
53
- model = self.parent.documents_cache.get_model(document)
54
-
55
62
  real_range = Range(range.start, min(range.end, context.stopped_location.end))
56
63
 
64
+ semantic_model = namespace.semantic_model
65
+ if semantic_model is not None:
66
+ return self._collect_from_model(semantic_model, real_range, context)
67
+
68
+ model = self.parent.documents_cache.get_model(document)
69
+
57
70
  def get_tokens() -> Iterator[Tuple[Token, ast.AST]]:
58
71
  for n in iter_nodes(model):
59
72
  r = range_from_node(n)
@@ -71,15 +84,64 @@ class RobotInlineValueProtocolPart(RobotLanguageServerProtocolPart, ModelHelper)
71
84
  get_tokens(),
72
85
  ),
73
86
  ):
74
- if token.type == RobotToken.ARGUMENT and isinstance(node, self.get_expression_statement_types()):
75
- for t, var in self.iter_expression_variables_from_token(
87
+ if token.type == RobotToken.ARGUMENT and isinstance(node, ModelHelper.get_expression_statement_types()):
88
+ for t, var in ModelHelper.iter_expression_variables_from_token(
76
89
  token, namespace, context.stopped_location.start
77
90
  ):
78
91
  if var.name != "${CURDIR}":
79
92
  result.append(InlineValueEvaluatableExpression(range_from_token(t), var.name))
80
93
 
81
- for t, var in self.iter_variables_from_token(token, namespace, context.stopped_location.start):
94
+ for t, var in ModelHelper.iter_variables_from_token(token, namespace, context.stopped_location.start):
82
95
  if var.name != "${CURDIR}":
83
96
  result.append(InlineValueEvaluatableExpression(range_from_token(t), var.name))
84
97
 
85
98
  return result
99
+
100
+ def _collect_from_model(
101
+ self,
102
+ semantic_model: SemanticModel,
103
+ real_range: Range,
104
+ context: InlineValueContext,
105
+ ) -> List[InlineValue]:
106
+ """Model path: variable occurrences from the pre-built statement
107
+ tokens, resolved at the debugger's stopped location."""
108
+ resolve_line = context.stopped_location.start.line + 1
109
+ resolve_col = context.stopped_location.start.character
110
+
111
+ def get_model_tokens() -> Iterator[Tuple[NodeKind, SemanticToken]]:
112
+ for stmt in semantic_model.statements:
113
+ if not stmt.tokens:
114
+ continue
115
+ first = stmt.tokens[0]
116
+ last = stmt.tokens[-1]
117
+ start = Position(line=first.line - 1, character=first.col_offset)
118
+ end = Position(line=last.line - 1, character=last.col_offset + last.length)
119
+ if start in real_range or end in real_range:
120
+ for token in scannable_statement_tokens(stmt):
121
+ yield stmt.kind, token
122
+ if start > real_range.end:
123
+ break
124
+
125
+ result: List[InlineValue] = []
126
+ for stmt_kind, token in takewhile(
127
+ lambda t: t[1].line - 1 <= real_range.end.line,
128
+ dropwhile(
129
+ lambda t: Position(line=t[1].line - 1, character=t[1].col_offset) < real_range.start,
130
+ get_model_tokens(),
131
+ ),
132
+ ):
133
+ # Legacy per-token order: the condition cell's bare-`$var`
134
+ # expression refs first, then the regular variables.
135
+ for candidate in chain(
136
+ iter_condition_ref_candidates(token),
137
+ iter_model_variable_candidates(token, semantic_model, resolve_line, resolve_col, stmt_kind),
138
+ ):
139
+ if not candidate.primary:
140
+ continue
141
+ # `extended=False`: the extended-syntax fallback is already
142
+ # encoded in the candidates' lookup names.
143
+ var = semantic_model.find_variable(candidate.lookup_name, resolve_line, resolve_col, extended=False)
144
+ if var is not None and var.name != "${CURDIR}":
145
+ result.append(InlineValueEvaluatableExpression(candidate.range, var.name))
146
+
147
+ return result
@@ -1,13 +1,18 @@
1
1
  from dataclasses import dataclass
2
- from typing import TYPE_CHECKING, Any, List, Optional
2
+ from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Optional, Tuple
3
3
 
4
4
  from robotcode.core.lsp.types import TextDocumentIdentifier
5
+ from robotcode.core.text_document import TextDocument
5
6
  from robotcode.core.uri import Uri
6
7
  from robotcode.core.utils.dataclasses import CamelSnakeMixin
7
8
  from robotcode.core.utils.logging import LoggingDescriptor
8
9
  from robotcode.jsonrpc2.protocol import rpc_method
9
- from robotcode.robot.diagnostics.model_helper import ModelHelper
10
+ from robotcode.robot.diagnostics.entities import LibraryEntry
11
+ from robotcode.robot.diagnostics.library_doc import KeywordDoc, LibraryDoc
12
+ from robotcode.robot.diagnostics.namespace import Namespace
13
+ from robotcode.robot.utils.markdown_docs import LinkResolver, ReferenceTarget
10
14
 
15
+ from .code_action_documentation import DocumentationTarget, keyword_reference_targets, link_documentation
11
16
  from .protocol_part import RobotLanguageServerProtocolPart
12
17
 
13
18
  if TYPE_CHECKING:
@@ -72,7 +77,7 @@ class GetDocumentationUrl(CamelSnakeMixin):
72
77
  keyword_id: Optional[str] = None
73
78
 
74
79
 
75
- class RobotKeywordsTreeViewPart(RobotLanguageServerProtocolPart, ModelHelper):
80
+ class RobotKeywordsTreeViewPart(RobotLanguageServerProtocolPart):
76
81
  _logger = LoggingDescriptor()
77
82
 
78
83
  def __init__(self, parent: "RobotLanguageServerProtocol") -> None:
@@ -96,50 +101,117 @@ class RobotKeywordsTreeViewPart(RobotLanguageServerProtocolPart, ModelHelper):
96
101
  result = []
97
102
 
98
103
  for _k, v in namespace.libraries.items():
104
+ documentation, keywords = (
105
+ self._import_documentation(v, True, document, namespace) if not no_documentation else (None, None)
106
+ )
99
107
  result.append(
100
108
  DocumentImport(
101
109
  name=v.name,
102
110
  alias=v.alias,
103
111
  id=str(hash(v)),
104
112
  type="library",
105
- documentation=v.library_doc.to_markdown(add_signature=False) if not no_documentation else None,
106
- keywords=[
107
- Keyword(
108
- l.name,
109
- str(hash(l)),
110
- l.parameter_signature(),
111
- l.to_markdown(add_signature=False) if not no_documentation else None,
112
- )
113
- for l in v.library_doc.keywords.values()
114
- ]
115
- if not no_documentation
116
- else None,
113
+ documentation=documentation,
114
+ keywords=keywords,
117
115
  )
118
116
  )
119
117
  for _k, v in namespace.resources.items():
118
+ documentation, keywords = (
119
+ self._import_documentation(v, False, document, namespace) if not no_documentation else (None, None)
120
+ )
120
121
  result.append(
121
122
  DocumentImport(
122
123
  name=v.name,
123
124
  alias=None,
124
125
  id=str(hash(v)),
125
126
  type="resource",
126
- documentation=v.library_doc.to_markdown(add_signature=False) if not no_documentation else None,
127
- keywords=[
128
- Keyword(
129
- l.name,
130
- str(hash(l)),
131
- l.parameter_signature(),
132
- l.to_markdown(add_signature=False) if not no_documentation else None,
133
- )
134
- for l in v.library_doc.keywords.values()
135
- ]
136
- if not no_documentation
137
- else None,
127
+ documentation=documentation,
128
+ keywords=keywords,
138
129
  )
139
130
  )
140
131
 
141
132
  return result
142
133
 
134
+ @property
135
+ def _links(self) -> bool:
136
+ return self.parent.robot_initialization_options.documentation_viewer_links
137
+
138
+ def _import_documentation(
139
+ self, entry: LibraryEntry, is_library: bool, document: TextDocument, namespace: Namespace
140
+ ) -> Tuple[str, List[Keyword]]:
141
+ """The tooltips of an import and of its keywords."""
142
+ library_doc = entry.library_doc
143
+ target = (
144
+ self.parent.robot_code_action_documentation.entry_target(entry, is_library, document, namespace)
145
+ if self._links
146
+ else None
147
+ )
148
+ link_resolver = self._link_resolver(document, namespace, target, library_doc)
149
+ return (
150
+ self._documentation(
151
+ lambda link_resolver: library_doc.to_markdown(add_signature=False, link_resolver=link_resolver),
152
+ target,
153
+ link_resolver,
154
+ ),
155
+ self._keywords(list(library_doc.keywords.values()), target, link_resolver),
156
+ )
157
+
158
+ def _link_resolver(
159
+ self,
160
+ document: TextDocument,
161
+ namespace: Namespace,
162
+ target: Optional[DocumentationTarget],
163
+ library_doc: LibraryDoc,
164
+ ) -> Optional[LinkResolver]:
165
+ # one for the tooltips of an import and all of its keywords
166
+ if target is None:
167
+ return None
168
+ return self.parent.robot_code_action_documentation.link_resolver(document, namespace, target, library_doc)
169
+
170
+ def _keywords(
171
+ self,
172
+ keywords: List[KeywordDoc],
173
+ target: Optional[DocumentationTarget],
174
+ link_resolver: Optional[LinkResolver],
175
+ ) -> List[Keyword]:
176
+ """The keywords of a library, a resource file or the document with their tooltips."""
177
+ # once for all keywords, they share their library and its documentation format
178
+ targets = keyword_reference_targets(keywords[0], self._links) if keywords else None
179
+ return [self._keyword(kw, target, link_resolver, targets) for kw in keywords]
180
+
181
+ def _keyword(
182
+ self,
183
+ kw: KeywordDoc,
184
+ target: Optional[DocumentationTarget],
185
+ link_resolver: Optional[LinkResolver],
186
+ targets: Optional[Mapping[str, ReferenceTarget]],
187
+ ) -> Keyword:
188
+ return Keyword(
189
+ kw.name,
190
+ str(hash(kw)),
191
+ kw.parameter_signature(),
192
+ self._documentation(
193
+ lambda link_resolver: kw.to_markdown(
194
+ add_signature=False, link_resolver=link_resolver, reference_targets=targets
195
+ ),
196
+ target,
197
+ link_resolver,
198
+ heading=False,
199
+ ),
200
+ )
201
+
202
+ def _documentation(
203
+ self,
204
+ render: Callable[[Optional[LinkResolver]], str],
205
+ target: Optional[DocumentationTarget],
206
+ link_resolver: Optional[LinkResolver],
207
+ *,
208
+ heading: bool = True,
209
+ ) -> str:
210
+ """A tooltip; with links, linked into the Documentation Viewer at the page of `target`."""
211
+ if not self._links:
212
+ return render(None)
213
+ return link_documentation(render, target, None, heading=heading, link_resolver=link_resolver)
214
+
143
215
  @rpc_method(name="robot/keywordsview/getDocumentKeywords", param_type=GetDocumentKeywordsParams, threaded=True)
144
216
  @_logger.call
145
217
  def _get_document_keywords(
@@ -155,31 +227,27 @@ class RobotKeywordsTreeViewPart(RobotLanguageServerProtocolPart, ModelHelper):
155
227
 
156
228
  namespace = self.parent.documents_cache.get_namespace(document)
157
229
 
158
- return [
159
- Keyword(
160
- l.name,
161
- str(hash(l)),
162
- l.parameter_signature(),
163
- l.to_markdown(add_signature=False) if not no_documentation else None,
164
- )
165
- for l in namespace.library_doc.keywords.values()
166
- ]
230
+ keywords = list(namespace.library_doc.keywords.values())
231
+ if no_documentation:
232
+ return [Keyword(l.name, str(hash(l)), l.parameter_signature(), None) for l in keywords]
167
233
 
168
- @rpc_method(name="robot/keywordsview/getDocumentationUrl", param_type=GetDocumentationUrl, threaded=True)
169
- @_logger.call
170
- def _get_documentation_url(
234
+ target = (
235
+ self.parent.robot_code_action_documentation.document_target(document, namespace) if self._links else None
236
+ )
237
+ return self._keywords(keywords, target, self._link_resolver(document, namespace, target, namespace.library_doc))
238
+
239
+ def _documentation_target(
171
240
  self,
172
241
  text_document: TextDocumentIdentifier,
173
- import_id: Optional[str] = None,
174
- keyword_id: Optional[str] = None,
175
- *args: Any,
176
- **kwargs: Any,
177
- ) -> Optional[str]:
242
+ import_id: Optional[str],
243
+ keyword_id: Optional[str],
244
+ ) -> Optional[DocumentationTarget]:
178
245
  document = self.parent.documents.get(text_document.uri)
179
246
  if document is None:
180
247
  return None
181
248
 
182
249
  namespace = self.parent.documents_cache.get_namespace(document)
250
+ documentation = self.parent.robot_code_action_documentation
183
251
 
184
252
  keyword_name = None
185
253
 
@@ -189,25 +257,55 @@ class RobotKeywordsTreeViewPart(RobotLanguageServerProtocolPart, ModelHelper):
189
257
  if keyword is not None:
190
258
  keyword_name = keyword.name
191
259
 
192
- return self.parent.robot_code_action_documentation.build_url(
193
- str(document.uri.to_path().name), (), document, namespace, keyword_name
194
- )
260
+ return documentation.document_target(document, namespace, keyword_name)
195
261
 
262
+ is_library = True
196
263
  entry = next((l for l in namespace.libraries.values() if str(hash(l)) == import_id), None)
197
264
  if entry is None:
265
+ is_library = False
198
266
  entry = next((l for l in namespace.resources.values() if str(hash(l)) == import_id), None)
199
267
 
200
- if keyword_id and entry is not None:
268
+ if entry is None:
269
+ return None
270
+
271
+ if keyword_id:
201
272
  keyword = next((l for l in entry.library_doc.keywords.values() if str(hash(l)) == keyword_id), None)
202
273
  if keyword is not None:
203
274
  keyword_name = keyword.name
204
275
 
205
- if entry is not None:
206
- return self.parent.robot_code_action_documentation.build_url(
207
- entry.import_name, entry.args, document, namespace, keyword_name
208
- )
276
+ return documentation.entry_target(entry, is_library, document, namespace, keyword_name)
277
+
278
+ @rpc_method(name="robot/keywordsview/getDocumentationTarget", param_type=GetDocumentationUrl, threaded=True)
279
+ @_logger.call
280
+ def _get_documentation_target(
281
+ self,
282
+ text_document: TextDocumentIdentifier,
283
+ import_id: Optional[str] = None,
284
+ keyword_id: Optional[str] = None,
285
+ *args: Any,
286
+ **kwargs: Any,
287
+ ) -> Optional[DocumentationTarget]:
288
+ return self._documentation_target(text_document, import_id, keyword_id)
289
+
290
+ @rpc_method(name="robot/keywordsview/getDocumentationUrl", param_type=GetDocumentationUrl, threaded=True)
291
+ @_logger.call
292
+ def _get_documentation_url(
293
+ self,
294
+ text_document: TextDocumentIdentifier,
295
+ import_id: Optional[str] = None,
296
+ keyword_id: Optional[str] = None,
297
+ *args: Any,
298
+ **kwargs: Any,
299
+ ) -> Optional[str]:
300
+ target = self._documentation_target(text_document, import_id, keyword_id)
301
+ if target is None:
302
+ return None
303
+
304
+ document = self.parent.documents.get(text_document.uri)
305
+ if document is None:
306
+ return None
209
307
 
210
- return None
308
+ return self.parent.robot_code_action_documentation.build_url(target, document)
211
309
 
212
310
  @rpc_method(
213
311
  name="robot/keywordsview/getLibraryDocumentation", param_type=GetLibraryDocumentationParams, threaded=True