robotcode-language-server 0.64.0__tar.gz → 0.65.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 (90) hide show
  1. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/PKG-INFO +3 -3
  2. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/pyproject.toml +2 -2
  3. robotcode_language_server-0.65.1/src/robotcode/language_server/__version__.py +1 -0
  4. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/code_lens.py +1 -1
  5. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/library_doc.py +14 -7
  6. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/namespace.py +86 -121
  7. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/code_action_quick_fixes.py +11 -8
  8. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/hover.py +1 -1
  9. robotcode_language_server-0.64.0/src/robotcode/language_server/__version__.py +0 -1
  10. robotcode_language_server-0.64.0/src/robotcode/language_server/robotframework/utils/markdownformatter.py +0 -358
  11. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/.gitignore +0 -0
  12. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/LICENSE.txt +0 -0
  13. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/README.md +0 -0
  14. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/__init__.py +0 -0
  15. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/cli.py +0 -0
  16. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/__init__.py +0 -0
  17. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/decorators.py +0 -0
  18. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/has_extend_capabilities.py +0 -0
  19. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/__init__.py +0 -0
  20. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/code_action.py +0 -0
  21. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/commands.py +0 -0
  22. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/completion.py +0 -0
  23. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/declaration.py +0 -0
  24. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/definition.py +0 -0
  25. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/diagnostics.py +0 -0
  26. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/document_highlight.py +0 -0
  27. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/document_symbols.py +0 -0
  28. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/documents.py +0 -0
  29. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/folding_range.py +0 -0
  30. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/formatting.py +0 -0
  31. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/hover.py +0 -0
  32. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/implementation.py +0 -0
  33. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/inlay_hint.py +0 -0
  34. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/inline_value.py +0 -0
  35. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/linked_editing_ranges.py +0 -0
  36. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/protocol_part.py +0 -0
  37. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/references.py +0 -0
  38. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/rename.py +0 -0
  39. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/selection_range.py +0 -0
  40. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/semantic_tokens.py +0 -0
  41. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/signature_help.py +0 -0
  42. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/window.py +0 -0
  43. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/workspace.py +0 -0
  44. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/protocol.py +0 -0
  45. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/server.py +0 -0
  46. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/text_document.py +0 -0
  47. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/hooks.py +0 -0
  48. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/py.typed +0 -0
  49. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/__init__.py +0 -0
  50. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/configuration.py +0 -0
  51. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/__init__.py +0 -0
  52. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/analyzer.py +0 -0
  53. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/entities.py +0 -0
  54. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/errors.py +0 -0
  55. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/imports_manager.py +0 -0
  56. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/model_helper.py +0 -0
  57. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/languages.py +0 -0
  58. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/__init__.py +0 -0
  59. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/code_action_documentation.py +0 -0
  60. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/code_action_helper_mixin.py +0 -0
  61. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/code_action_refactor.py +0 -0
  62. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/codelens.py +0 -0
  63. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/completion.py +0 -0
  64. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/debugging_utils.py +0 -0
  65. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/diagnostics.py +0 -0
  66. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/document_highlight.py +0 -0
  67. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/document_symbols.py +0 -0
  68. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/documents_cache.py +0 -0
  69. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/folding_range.py +0 -0
  70. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/formatting.py +0 -0
  71. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/goto.py +0 -0
  72. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/inlay_hint.py +0 -0
  73. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/inline_value.py +0 -0
  74. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/protocol_part.py +0 -0
  75. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/references.py +0 -0
  76. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/rename.py +0 -0
  77. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/robocop_diagnostics.py +0 -0
  78. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/robot_workspace.py +0 -0
  79. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/selection_range.py +0 -0
  80. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/semantic_tokens.py +0 -0
  81. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/signature_help.py +0 -0
  82. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/protocol.py +0 -0
  83. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/server.py +0 -0
  84. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/__init__.py +0 -0
  85. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/_variables.py +0 -0
  86. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/ast_utils.py +0 -0
  87. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/async_ast.py +0 -0
  88. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/match.py +0 -0
  89. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/robot_path.py +0 -0
  90. {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/variables.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotcode-language-server
3
- Version: 0.64.0
3
+ Version: 0.65.1
4
4
  Summary: RobotCode Language Server for Robot Framework
5
5
  Project-URL: Homepage, https://robotcode.io
6
6
  Project-URL: Donate, https://github.com/sponsors/d-biehl
@@ -25,8 +25,8 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
25
25
  Classifier: Topic :: Utilities
26
26
  Classifier: Typing :: Typed
27
27
  Requires-Python: >=3.8
28
- Requires-Dist: robotcode-jsonrpc2==0.64.0
29
- Requires-Dist: robotcode==0.64.0
28
+ Requires-Dist: robotcode-jsonrpc2==0.65.1
29
+ Requires-Dist: robotcode==0.65.1
30
30
  Requires-Dist: robotframework>=4.1.0
31
31
  Description-Content-Type: text/markdown
32
32
 
@@ -27,8 +27,8 @@ classifiers = [
27
27
  ]
28
28
  dependencies = [
29
29
  "robotframework>=4.1.0",
30
- "robotcode-jsonrpc2==0.64.0",
31
- "robotcode==0.64.0",
30
+ "robotcode-jsonrpc2==0.65.1",
31
+ "robotcode==0.65.1",
32
32
  ]
33
33
  dynamic = ["version"]
34
34
 
@@ -109,4 +109,4 @@ class CodeLensProtocolPart(LanguageServerProtocolPart, HasExtendCapabilities):
109
109
  ):
110
110
  return
111
111
 
112
- await self.parent.send_request("workspace/codeLens/refresh")
112
+ await self.parent.send_request_async("workspace/codeLens/refresh")
@@ -34,10 +34,8 @@ from typing import (
34
34
  )
35
35
 
36
36
  from robotcode.core.lsp.types import Position, Range
37
- from robotcode.language_server.robotframework.utils.markdownformatter import (
38
- MarkDownFormatter,
39
- )
40
37
  from robotcode.robot.utils import get_robot_version
38
+ from robotcode.robot.utils.markdownformatter import MarkDownFormatter
41
39
 
42
40
  from ..utils.ast_utils import (
43
41
  HasError,
@@ -442,7 +440,8 @@ class ArgumentSpec:
442
440
  var_named: Any
443
441
  embedded: Any
444
442
  defaults: Any
445
- types: Any
443
+ types: Optional[Dict[str, str]] = None
444
+ return_type: Optional[str] = None
446
445
 
447
446
  @staticmethod
448
447
  def from_robot_argument_spec(spec: Any) -> ArgumentSpec:
@@ -456,7 +455,8 @@ class ArgumentSpec:
456
455
  var_named=spec.var_named,
457
456
  embedded=spec.embedded if get_robot_version() >= (7, 0) else None,
458
457
  defaults={k: str(v) for k, v in spec.defaults.items()} if spec.defaults else {},
459
- types=None,
458
+ types={k: str(v) for k, v in spec.types.items()} if get_robot_version() > (7, 0) and spec.types else None,
459
+ return_type=str(spec.return_type) if get_robot_version() > (7, 0) and spec.return_type else None,
460
460
  )
461
461
 
462
462
  def resolve(
@@ -489,7 +489,7 @@ class ArgumentSpec:
489
489
  self.named_only,
490
490
  self.var_named,
491
491
  self.defaults,
492
- self.types,
492
+ None,
493
493
  )
494
494
  else:
495
495
  self.__robot_arguments = RobotArgumentSpec(
@@ -502,7 +502,7 @@ class ArgumentSpec:
502
502
  self.var_named,
503
503
  self.embedded,
504
504
  self.defaults,
505
- self.types,
505
+ None,
506
506
  )
507
507
  self.__robot_arguments.name = self.name
508
508
  if validate:
@@ -553,6 +553,7 @@ class KeywordDoc(SourceEntity):
553
553
  is_registered_run_keyword: bool = field(default=False, compare=False)
554
554
  args_to_process: Optional[int] = field(default=None, compare=False)
555
555
  deprecated: bool = field(default=False, compare=False)
556
+ return_type: Optional[str] = field(default=None, compare=False)
556
557
 
557
558
  parent_digest: Optional[str] = field(default=None, init=False, metadata={"nosave": True})
558
559
  parent: Optional[LibraryDoc] = field(default=None, init=False, metadata={"nosave": True})
@@ -719,6 +720,11 @@ class KeywordDoc(SourceEntity):
719
720
  f"| {'=' if a.default_value is not None else ''} "
720
721
  f"| {f'`{a.default_value!s}`' if a.default_value else ''} |"
721
722
  )
723
+ if self.return_type:
724
+ if result:
725
+ result += "\n\n"
726
+
727
+ result += f"**Return Type**: `{self.return_type}`\n"
722
728
 
723
729
  if self.tags:
724
730
  if result:
@@ -1801,6 +1807,7 @@ def get_library_doc(
1801
1807
  arguments_spec=ArgumentSpec.from_robot_argument_spec(kw[1].arguments)
1802
1808
  if not kw[1].is_error_handler
1803
1809
  else None,
1810
+ return_type=str(kw[1].arguments.return_type) if get_robot_version() >= (7, 0) else None,
1804
1811
  )
1805
1812
  for kw in keyword_docs
1806
1813
  ],
@@ -24,9 +24,17 @@ from typing import (
24
24
  Set,
25
25
  Tuple,
26
26
  Union,
27
- cast,
28
27
  )
29
28
 
29
+ from robot.errors import VariableError
30
+ from robot.libraries import STDLIBS
31
+ from robot.parsing.lexer.tokens import Token
32
+ from robot.parsing.model.blocks import Keyword, SettingSection, TestCase, VariableSection
33
+ from robot.parsing.model.statements import Arguments, KeywordCall, KeywordName, Statement, Variable
34
+ from robot.parsing.model.statements import LibraryImport as RobotLibraryImport
35
+ from robot.parsing.model.statements import ResourceImport as RobotResourceImport
36
+ from robot.parsing.model.statements import VariablesImport as RobotVariablesImport
37
+ from robot.variables.search import is_scalar_assign, is_variable, search_variable
30
38
  from robotcode.core.async_tools import Lock, async_event
31
39
  from robotcode.core.logging import LoggingDescriptor
32
40
  from robotcode.core.lsp.types import (
@@ -42,16 +50,15 @@ from robotcode.core.lsp.types import (
42
50
  )
43
51
  from robotcode.core.uri import Uri
44
52
  from robotcode.robot.utils import get_robot_version
45
-
46
- from ...common.text_document import TextDocument
47
- from ..languages import Languages
48
- from ..utils.ast_utils import (
49
- Token,
53
+ from robotcode.robot.utils.ast import (
50
54
  range_from_node,
51
55
  range_from_token,
52
56
  strip_variable_token,
53
57
  tokenize_variables,
54
58
  )
59
+
60
+ from ...common.text_document import TextDocument
61
+ from ..languages import Languages
55
62
  from ..utils.async_ast import Visitor
56
63
  from ..utils.match import eq_namespace
57
64
  from ..utils.variables import BUILTIN_VARIABLES
@@ -111,19 +118,14 @@ class VariablesVisitor(Visitor):
111
118
  return self._results
112
119
 
113
120
  def visit_Section(self, node: ast.AST) -> None: # noqa: N802
114
- from robot.parsing.model.blocks import VariableSection
115
-
116
121
  if isinstance(node, VariableSection):
117
122
  self.generic_visit(node)
118
123
 
119
- def visit_Variable(self, node: ast.AST) -> None: # noqa: N802
120
- from robot.parsing.lexer.tokens import Token as RobotToken
121
- from robot.parsing.model.statements import Variable
122
- from robot.variables import search_variable
123
-
124
- variable = cast(Variable, node)
124
+ def visit_Variable(self, node: Variable) -> None: # noqa: N802
125
+ name_token = node.get_token(Token.VARIABLE)
126
+ if name_token is None:
127
+ return
125
128
 
126
- name_token = variable.get_token(RobotToken.VARIABLE)
127
129
  name = name_token.value
128
130
 
129
131
  if name is not None:
@@ -134,21 +136,21 @@ class VariablesVisitor(Visitor):
134
136
  if name.endswith("="):
135
137
  name = name[:-1].rstrip()
136
138
 
137
- has_value = bool(variable.value)
139
+ has_value = bool(node.value)
138
140
  value = tuple(
139
- s.replace("${CURDIR}", str(Path(self.source).parent).replace("\\", "\\\\")) for s in variable.value
141
+ s.replace("${CURDIR}", str(Path(self.source).parent).replace("\\", "\\\\")) for s in node.value
140
142
  )
141
143
 
142
144
  self._results.append(
143
145
  VariableDefinition(
144
- name=variable.name,
146
+ name=node.name,
145
147
  name_token=strip_variable_token(
146
- RobotToken(name_token.type, name, name_token.lineno, name_token.col_offset, name_token.error)
148
+ Token(name_token.type, name, name_token.lineno, name_token.col_offset, name_token.error)
147
149
  ),
148
- line_no=variable.lineno,
149
- col_offset=variable.col_offset,
150
- end_line_no=variable.lineno,
151
- end_col_offset=variable.end_col_offset,
150
+ line_no=node.lineno,
151
+ col_offset=node.col_offset,
152
+ end_line_no=node.lineno,
153
+ end_col_offset=node.end_col_offset,
152
154
  source=self.source,
153
155
  has_value=has_value,
154
156
  resolvable=True,
@@ -187,22 +189,17 @@ class BlockVariableVisitor(Visitor):
187
189
  finally:
188
190
  self.current_kw_doc = None
189
191
 
190
- def visit_KeywordName(self, node: ast.AST) -> None: # noqa: N802
191
- from robot.parsing.lexer.tokens import Token as RobotToken
192
- from robot.parsing.model.statements import KeywordName
193
- from robot.variables.search import search_variable
194
-
192
+ def visit_KeywordName(self, node: KeywordName) -> None: # noqa: N802
195
193
  from .model_helper import ModelHelperMixin
196
194
 
197
- n = cast(KeywordName, node)
198
- name_token = cast(Token, n.get_token(RobotToken.KEYWORD_NAME))
195
+ name_token = node.get_token(Token.KEYWORD_NAME)
199
196
 
200
197
  if name_token is not None and name_token.value:
201
198
  keyword = ModelHelperMixin.get_keyword_definition_at_token(self.library_doc, name_token)
202
199
  self.current_kw_doc = keyword
203
200
 
204
201
  for variable_token in filter(
205
- lambda e: e.type == RobotToken.VARIABLE,
202
+ lambda e: e.type == Token.VARIABLE,
206
203
  tokenize_variables(name_token, identifiers="$", ignore_errors=True),
207
204
  ):
208
205
  if variable_token.value:
@@ -225,30 +222,24 @@ class BlockVariableVisitor(Visitor):
225
222
  )
226
223
 
227
224
  def get_variable_token(self, token: Token) -> Optional[Token]:
228
- from robot.parsing.lexer.tokens import Token as RobotToken
229
-
230
225
  return next(
231
226
  (
232
227
  v
233
228
  for v in itertools.dropwhile(
234
- lambda t: t.type in RobotToken.NON_DATA_TOKENS,
235
- tokenize_variables(token, ignore_errors=True),
229
+ lambda t: t.type in Token.NON_DATA_TOKENS,
230
+ tokenize_variables(token, ignore_errors=True, extra_types={Token.VARIABLE}),
236
231
  )
237
- if v.type == RobotToken.VARIABLE
232
+ if v.type == Token.VARIABLE
238
233
  ),
239
234
  None,
240
235
  )
241
236
 
242
- def visit_Arguments(self, node: ast.AST) -> None: # noqa: N802
243
- from robot.errors import VariableError
244
- from robot.parsing.lexer.tokens import Token as RobotToken
245
- from robot.parsing.model.statements import Arguments
246
-
237
+ def visit_Arguments(self, node: Arguments) -> None: # noqa: N802
247
238
  args: List[str] = []
248
- n = cast(Arguments, node)
249
- arguments = n.get_tokens(RobotToken.ARGUMENT)
250
239
 
251
- for argument_token in (cast(RobotToken, e) for e in arguments):
240
+ arguments = node.get_tokens(Token.ARGUMENT)
241
+
242
+ for argument_token in arguments:
252
243
  try:
253
244
  argument = self.get_variable_token(argument_token)
254
245
 
@@ -278,14 +269,8 @@ class BlockVariableVisitor(Visitor):
278
269
  except VariableError:
279
270
  pass
280
271
 
281
- def visit_ExceptHeader(self, node: ast.AST) -> None: # noqa: N802
282
- from robot.errors import VariableError
283
- from robot.parsing.lexer.tokens import Token as RobotToken
284
- from robot.parsing.model.statements import ExceptHeader
285
- from robot.variables import is_scalar_assign
286
-
287
- n = cast(ExceptHeader, node)
288
- variables = n.get_tokens(RobotToken.VARIABLE)[:1]
272
+ def visit_ExceptHeader(self, node: Statement) -> None: # noqa: N802
273
+ variables = node.get_tokens(Token.VARIABLE)[:1]
289
274
  if variables and is_scalar_assign(variables[0].value):
290
275
  try:
291
276
  variable = self.get_variable_token(variables[0])
@@ -304,23 +289,17 @@ class BlockVariableVisitor(Visitor):
304
289
  except VariableError:
305
290
  pass
306
291
 
307
- def visit_KeywordCall(self, node: ast.AST) -> None: # noqa: N802
308
- from robot.errors import VariableError
309
- from robot.parsing.lexer.tokens import Token as RobotToken
310
- from robot.parsing.model.statements import KeywordCall
311
-
292
+ def visit_KeywordCall(self, node: KeywordCall) -> None: # noqa: N802
312
293
  # TODO analyze "Set Local/Global/Suite Variable"
313
294
 
314
- n = cast(KeywordCall, node)
315
-
316
- for assign_token in n.get_tokens(RobotToken.ASSIGN):
295
+ for assign_token in node.get_tokens(Token.ASSIGN):
317
296
  variable_token = self.get_variable_token(assign_token)
318
297
 
319
298
  try:
320
299
  if variable_token is not None:
321
300
  if (
322
301
  self.position is not None
323
- and self.position in range_from_node(n)
302
+ and self.position in range_from_node(node)
324
303
  and self.position > range_from_token(variable_token).end
325
304
  ):
326
305
  continue
@@ -339,21 +318,15 @@ class BlockVariableVisitor(Visitor):
339
318
  except VariableError:
340
319
  pass
341
320
 
342
- def visit_InlineIfHeader(self, node: ast.AST) -> None: # noqa: N802
343
- from robot.errors import VariableError
344
- from robot.parsing.lexer.tokens import Token as RobotToken
345
- from robot.parsing.model.statements import InlineIfHeader
346
-
347
- n = cast(InlineIfHeader, node)
348
-
349
- for assign_token in n.get_tokens(RobotToken.ASSIGN):
321
+ def visit_InlineIfHeader(self, node: Statement) -> None: # noqa: N802
322
+ for assign_token in node.get_tokens(Token.ASSIGN):
350
323
  variable_token = self.get_variable_token(assign_token)
351
324
 
352
325
  try:
353
326
  if variable_token is not None:
354
327
  if (
355
328
  self.position is not None
356
- and self.position in range_from_node(n)
329
+ and self.position in range_from_node(node)
357
330
  and self.position > range_from_token(variable_token).end
358
331
  ):
359
332
  continue
@@ -372,12 +345,8 @@ class BlockVariableVisitor(Visitor):
372
345
  except VariableError:
373
346
  pass
374
347
 
375
- def visit_ForHeader(self, node: ast.AST) -> None: # noqa: N802
376
- from robot.parsing.lexer.tokens import Token as RobotToken
377
- from robot.parsing.model.statements import ForHeader
378
-
379
- n = cast(ForHeader, node)
380
- variables = n.get_tokens(RobotToken.VARIABLE)
348
+ def visit_ForHeader(self, node: Statement) -> None: # noqa: N802
349
+ variables = node.get_tokens(Token.VARIABLE)
381
350
  for variable in variables:
382
351
  variable_token = self.get_variable_token(variable)
383
352
  if variable_token is not None and variable_token.value and variable_token.value not in self._results:
@@ -391,6 +360,27 @@ class BlockVariableVisitor(Visitor):
391
360
  source=self.source,
392
361
  )
393
362
 
363
+ def visit_Var(self, node: Statement) -> None: # noqa: N802
364
+ variable = node.get_token(Token.VARIABLE)
365
+ if variable is None:
366
+ return
367
+ try:
368
+ if not is_variable(variable.value):
369
+ return
370
+
371
+ self._results[variable.value] = LocalVariableDefinition(
372
+ name=variable.value,
373
+ name_token=strip_variable_token(variable),
374
+ line_no=variable.lineno,
375
+ col_offset=variable.col_offset,
376
+ end_line_no=variable.lineno,
377
+ end_col_offset=variable.end_col_offset,
378
+ source=self.source,
379
+ )
380
+
381
+ except VariableError:
382
+ pass
383
+
394
384
 
395
385
  class ImportVisitor(Visitor):
396
386
  def get(self, source: str, model: ast.AST) -> List[Import]:
@@ -400,31 +390,23 @@ class ImportVisitor(Visitor):
400
390
  return self._results
401
391
 
402
392
  def visit_Section(self, node: ast.AST) -> None: # noqa: N802
403
- from robot.parsing.model.blocks import SettingSection
404
-
405
393
  if isinstance(node, SettingSection):
406
394
  self.generic_visit(node)
407
395
 
408
- def visit_LibraryImport(self, node: ast.AST) -> None: # noqa: N802
409
- from robot.parsing.lexer.tokens import Token as RobotToken
410
- from robot.parsing.model.statements import LibraryImport as RobotLibraryImport
411
-
412
- n = cast(RobotLibraryImport, node)
413
- name = cast(RobotToken, n.get_token(RobotToken.NAME))
396
+ def visit_LibraryImport(self, node: RobotLibraryImport) -> None: # noqa: N802
397
+ name = node.get_token(Token.NAME)
414
398
 
415
- separator = n.get_token(RobotToken.WITH_NAME)
416
- alias_token = n.get_tokens(RobotToken.NAME)[-1] if separator else None
399
+ separator = node.get_token(Token.WITH_NAME)
400
+ alias_token = node.get_tokens(Token.NAME)[-1] if separator else None
417
401
 
418
- last_data_token = cast(
419
- RobotToken, next(v for v in reversed(n.tokens) if v.type not in RobotToken.NON_DATA_TOKENS)
420
- )
421
- if n.name:
402
+ last_data_token = next(v for v in reversed(node.tokens) if v.type not in Token.NON_DATA_TOKENS)
403
+ if node.name:
422
404
  self._results.append(
423
405
  LibraryImport(
424
- name=n.name,
406
+ name=node.name,
425
407
  name_token=name if name is not None else None,
426
- args=n.args,
427
- alias=n.alias,
408
+ args=node.args,
409
+ alias=node.alias,
428
410
  alias_token=alias_token,
429
411
  line_no=node.lineno,
430
412
  col_offset=node.col_offset,
@@ -442,20 +424,14 @@ class ImportVisitor(Visitor):
442
424
  )
443
425
  )
444
426
 
445
- def visit_ResourceImport(self, node: ast.AST) -> None: # noqa: N802
446
- from robot.parsing.lexer.tokens import Token as RobotToken
447
- from robot.parsing.model.statements import ResourceImport as RobotResourceImport
427
+ def visit_ResourceImport(self, node: RobotResourceImport) -> None: # noqa: N802
428
+ name = node.get_token(Token.NAME)
448
429
 
449
- n = cast(RobotResourceImport, node)
450
- name = cast(RobotToken, n.get_token(RobotToken.NAME))
451
-
452
- last_data_token = cast(
453
- RobotToken, next(v for v in reversed(n.tokens) if v.type not in RobotToken.NON_DATA_TOKENS)
454
- )
455
- if n.name:
430
+ last_data_token = next(v for v in reversed(node.tokens) if v.type not in Token.NON_DATA_TOKENS)
431
+ if node.name:
456
432
  self._results.append(
457
433
  ResourceImport(
458
- name=n.name,
434
+ name=node.name,
459
435
  name_token=name if name is not None else None,
460
436
  line_no=node.lineno,
461
437
  col_offset=node.col_offset,
@@ -473,22 +449,16 @@ class ImportVisitor(Visitor):
473
449
  )
474
450
  )
475
451
 
476
- def visit_VariablesImport(self, node: ast.AST) -> None: # noqa: N802
477
- from robot.parsing.lexer.tokens import Token as RobotToken
478
- from robot.parsing.model.statements import VariablesImport as RobotVariablesImport
452
+ def visit_VariablesImport(self, node: RobotVariablesImport) -> None: # noqa: N802
453
+ name = node.get_token(Token.NAME)
479
454
 
480
- n = cast(RobotVariablesImport, node)
481
- name = cast(RobotToken, n.get_token(RobotToken.NAME))
482
-
483
- last_data_token = cast(
484
- RobotToken, next(v for v in reversed(n.tokens) if v.type not in RobotToken.NON_DATA_TOKENS)
485
- )
486
- if n.name:
455
+ last_data_token = next(v for v in reversed(node.tokens) if v.type not in Token.NON_DATA_TOKENS)
456
+ if node.name:
487
457
  self._results.append(
488
458
  VariablesImport(
489
- name=n.name,
459
+ name=node.name,
490
460
  name_token=name if name is not None else None,
491
- args=n.args,
461
+ args=node.args,
492
462
  line_no=node.lineno,
493
463
  col_offset=node.col_offset,
494
464
  end_line_no=last_data_token.lineno
@@ -926,9 +896,6 @@ class Namespace:
926
896
  position: Optional[Position] = None,
927
897
  skip_commandline_variables: bool = False,
928
898
  ) -> AsyncIterator[Tuple[VariableMatcher, VariableDefinition]]:
929
- from robot.parsing.model.blocks import Keyword, TestCase
930
- from robot.parsing.model.statements import Arguments
931
-
932
899
  yielded: Dict[VariableMatcher, VariableDefinition] = {}
933
900
 
934
901
  test_or_keyword_nodes = list(
@@ -2020,8 +1987,6 @@ class KeywordFinder:
2020
1987
  def _filter_stdlib_runner(
2021
1988
  self, entry1: Tuple[Optional[LibraryEntry], KeywordDoc], entry2: Tuple[Optional[LibraryEntry], KeywordDoc]
2022
1989
  ) -> List[Tuple[Optional[LibraryEntry], KeywordDoc]]:
2023
- from robot.libraries import STDLIBS
2024
-
2025
1990
  stdlibs_without_remote = STDLIBS - {"Remote"}
2026
1991
  if entry1[0] is not None and entry1[0].name in stdlibs_without_remote:
2027
1992
  standard, custom = entry1, entry2
@@ -34,6 +34,7 @@ from ..utils.ast_utils import (
34
34
  get_node_at_position,
35
35
  get_nodes_at_position,
36
36
  get_tokens_at_position,
37
+ iter_nodes,
37
38
  range_from_node,
38
39
  range_from_token,
39
40
  )
@@ -655,7 +656,7 @@ class RobotCodeActionQuickFixesProtocolPart(RobotLanguageServerProtocolPart, Mod
655
656
  ) -> Optional[CodeAction]:
656
657
  from robot.parsing.lexer.tokens import Token
657
658
  from robot.parsing.model.blocks import Keyword
658
- from robot.parsing.model.statements import Arguments, Documentation
659
+ from robot.parsing.model.statements import Arguments, Documentation, KeywordName, Statement
659
660
 
660
661
  if data.range.start.line == data.range.end.line and data.range.start.character <= data.range.end.character:
661
662
  document = await self.parent.documents.get(data.document_uri)
@@ -676,14 +677,16 @@ class RobotCodeActionQuickFixesProtocolPart(RobotLanguageServerProtocolPart, Mod
676
677
  arguments = next((n for n in keyword.body if isinstance(n, Arguments)), None)
677
678
 
678
679
  if arguments is None:
679
- i = 0
680
- first_stmt = keyword.body[i]
681
-
682
- while isinstance(first_stmt, Documentation) and i < len(keyword.body):
683
- i += 1
684
- first_stmt = keyword.body[i]
680
+ first_stmt = next(
681
+ (
682
+ n
683
+ for n in iter_nodes(keyword)
684
+ if isinstance(n, Statement) and not isinstance(n, (KeywordName, Documentation))
685
+ ),
686
+ None,
687
+ )
685
688
 
686
- if i >= len(keyword.body):
689
+ if first_stmt is None:
687
690
  return None
688
691
 
689
692
  spaces = (
@@ -17,6 +17,7 @@ from typing import (
17
17
 
18
18
  from robotcode.core.logging import LoggingDescriptor
19
19
  from robotcode.core.lsp.types import Hover, MarkupContent, MarkupKind, Position, Range
20
+ from robotcode.robot.utils.markdownformatter import MarkDownFormatter
20
21
 
21
22
  from ...common.decorators import language_id
22
23
  from ...common.text_document import TextDocument
@@ -26,7 +27,6 @@ from ..utils.ast_utils import (
26
27
  range_from_node,
27
28
  range_from_token,
28
29
  )
29
- from ..utils.markdownformatter import MarkDownFormatter
30
30
  from .protocol_part import RobotLanguageServerProtocolPart
31
31
 
32
32
  if TYPE_CHECKING:
@@ -1 +0,0 @@
1
- __version__ = "0.64.0"
@@ -1,358 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import functools
4
- import itertools
5
- import re
6
- from abc import ABC, abstractmethod
7
- from typing import Any, Callable, Iterator, List, Optional, Tuple
8
-
9
-
10
- class Formatter(ABC):
11
- _strip_lines = True
12
-
13
- def __init__(self) -> None:
14
- self._lines: List[str] = []
15
-
16
- def handles(self, line: str) -> bool:
17
- return self._handles(line.strip() if self._strip_lines else line)
18
-
19
- @abstractmethod
20
- def _handles(self, line: str) -> bool:
21
- ...
22
-
23
- def add(self, line: str) -> None:
24
- self._lines.append(line.strip() if self._strip_lines else line)
25
-
26
- def end(self) -> str:
27
- result = self.format(self._lines)
28
- self._lines = []
29
- return result
30
-
31
- @abstractmethod
32
- def format(self, lines: List[str]) -> str:
33
- ...
34
-
35
-
36
- class MarkDownFormatter:
37
- def __init__(self) -> None:
38
- self._results: List[str] = []
39
- self._formatters: List[Formatter] = [
40
- TableFormatter(),
41
- PreformattedFormatter(),
42
- ListFormatter(),
43
- HeaderFormatter(),
44
- RulerFormatter(),
45
- ]
46
- self._formatters.append(ParagraphFormatter(self._formatters[:]))
47
- self._current: Optional[Formatter] = None
48
-
49
- def format(self, text: str) -> str:
50
- for line in text.splitlines():
51
- self._process_line(line)
52
- self._end_current()
53
- return "\n".join(self._results)
54
-
55
- def _process_line(self, line: str) -> None:
56
- if not line.strip():
57
- self._end_current()
58
- elif self._current and self._current.handles(line):
59
- self._current.add(line)
60
- else:
61
- self._end_current()
62
- self._current = self._find_formatter(line)
63
- if self._current is not None:
64
- self._current.add(line)
65
-
66
- def _end_current(self) -> None:
67
- if self._current:
68
- self._results.append(self._current.end())
69
- self._current = None
70
-
71
- def _find_formatter(self, line: str) -> Optional[Formatter]:
72
- for formatter in self._formatters:
73
- if formatter.handles(line):
74
- return formatter
75
- return None
76
-
77
-
78
- class SingleLineFormatter(Formatter):
79
- def _handles(self, line: str) -> bool:
80
- return bool(not self._lines and self.match(line))
81
-
82
- @abstractmethod
83
- def match(self, line: str) -> Optional[re.Match[str]]:
84
- ...
85
-
86
- def format(self, lines: List[str]) -> str:
87
- return self.format_line(lines[0])
88
-
89
- @abstractmethod
90
- def format_line(self, line: str) -> str:
91
- ...
92
-
93
-
94
- class HeaderFormatter(SingleLineFormatter):
95
- _regex = re.compile(r"^(={1,5})\s+(\S.*?)\s+\1$")
96
-
97
- def match(self, line: str) -> Optional[re.Match[str]]:
98
- return self._regex.match(line)
99
-
100
- def format_line(self, line: str) -> str:
101
- m = self.match(line)
102
- if m is not None:
103
- level, text = m.groups()
104
-
105
- return "%s %s\n" % ("#" * (len(level) + 1), text)
106
- return ""
107
-
108
-
109
- class LinkFormatter:
110
- _image_exts = (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg")
111
- _link = re.compile(r"\[(.+?\|.*?)\]")
112
- _url = re.compile(
113
- r"""
114
- ((^|\ ) ["'(\[{]*) # begin of line or space and opt. any char "'([{
115
- ([a-z][\w+-.]*://[^\s|]+?) # url
116
- (?=[)\]}"'.,!?:;|]* ($|\ )) # opt. any char )]}"'.,!?:;| and eol or space
117
- """,
118
- re.VERBOSE | re.MULTILINE | re.IGNORECASE,
119
- )
120
-
121
- def format_url(self, text: str) -> str:
122
- return self._format_url(text, format_as_image=False)
123
-
124
- def _format_url(self, text: str, format_as_image: bool = True) -> str:
125
- if "://" not in text:
126
- return text
127
- return self._url.sub(functools.partial(self._replace_url, format_as_image), text)
128
-
129
- def _replace_url(self, format_as_image: bool, match: re.Match[str]) -> str:
130
- pre = match.group(1)
131
- url = match.group(3)
132
- if format_as_image and self._is_image(url):
133
- return pre + self._get_image(url)
134
- return pre + self._get_link(url)
135
-
136
- def _get_image(self, src: str, title: Optional[str] = None) -> str:
137
- return f"![{title or src}]({src})"
138
-
139
- def _get_link(self, href: str, content: Optional[str] = None) -> str:
140
- return f"[{content or href}]({href})"
141
-
142
- def _quot(self, attr: str) -> str:
143
- return attr if '"' not in attr else attr.replace('"', "&quot;")
144
-
145
- def format_link(self, text: str) -> str:
146
- # 2nd, 4th, etc. token contains link, others surrounding content
147
- tokens = self._link.split(text)
148
-
149
- formatters: Iterator[Callable[[str], Any]] = itertools.cycle((self._format_url, self._format_link))
150
- return "".join(f(t) for f, t in zip(formatters, tokens))
151
-
152
- def _format_link(self, text: str) -> str:
153
- link, content = [t.strip() for t in text.split("|", 1)]
154
- if self._is_image(content):
155
- content = self._get_image(content, link)
156
- elif self._is_image(link):
157
- return self._get_image(link, content)
158
- if link.startswith("\\#"):
159
- link = link.lower()
160
- return self._get_link(link, content)
161
-
162
- def remove_link(self, text: str) -> str:
163
- # 2nd, 4th, etc. token contains link, others surrounding content
164
- tokens = self._link.split(text)
165
- if len(tokens) > 1:
166
- formatters: Iterator[Callable[[str], Any]] = itertools.cycle([self._remove_link])
167
- return "".join(f(t) for f, t in zip(formatters, tokens))
168
- return text
169
-
170
- def _remove_link(self, text: str) -> str:
171
- if "|" not in text:
172
- return text
173
-
174
- link, content = [t.strip() for t in text.split("|", 1)]
175
- if self._is_image(content):
176
- return self._get_image(content, link)
177
-
178
- return content
179
-
180
- def _is_image(self, text: str) -> bool:
181
- return text.startswith("data:image/") or text.lower().endswith(self._image_exts)
182
-
183
-
184
- class LineFormatter:
185
- _bold = re.compile(
186
- r"""
187
- ( # prefix (group 1)
188
- (^|\ ) # begin of line or space
189
- ["'(]* _? # optionally any char "'( and optional begin of italic
190
- ) #
191
- \* # start of bold
192
- ([^\ ].*?) # no space and then anything (group 3)
193
- \* # end of bold
194
- (?= # start of postfix (non-capturing group)
195
- _? ["').,!?:;]* # optional end of italic and any char "').,!?:;
196
- ($|\ ) # end of line or space
197
- )
198
- """,
199
- re.VERBOSE,
200
- )
201
- _italic = re.compile(
202
- r"""
203
- ( (^|\ ) ["'(]* ) # begin of line or space and opt. any char "'(
204
- _ # start of italic
205
- ([^\ _].*?) # no space or underline and then anything
206
- _ # end of italic
207
- (?= ["').,!?:;]* ($|\ ) ) # opt. any char "').,!?:; and end of line or space
208
- """,
209
- re.VERBOSE,
210
- )
211
- _code = re.compile(
212
- r"""
213
- ( (^|\ ) ["'(]* ) # same as above with _ changed to ``
214
- ``
215
- ([^\ `].*?)
216
- ``
217
- (?= ["').,!?:;]* ($|\ ) )
218
- """,
219
- re.VERBOSE,
220
- )
221
-
222
- def __init__(self) -> None:
223
- super().__init__()
224
-
225
- self._formatters: List[Tuple[str, Callable[[str], str]]] = [
226
- ("<", self._quote_lower_then),
227
- ("#", self._quote_hash),
228
- ("*", self._format_bold),
229
- ("_", self._format_italic),
230
- ("``", self._format_code),
231
- ("", functools.partial(LinkFormatter().format_link)),
232
- ]
233
-
234
- def format(self, line: str) -> str:
235
- for marker, formatter in self._formatters:
236
- if marker in line:
237
- line = formatter(line)
238
- return line
239
-
240
- def _quote_lower_then(self, line: str) -> str:
241
- return line.replace("<", "\\<")
242
-
243
- def _quote_hash(self, line: str) -> str:
244
- return line.replace("#", "\\#")
245
-
246
- def _format_bold(self, line: str) -> str:
247
- return self._bold.sub("\\1**\\3**", line)
248
-
249
- def _format_italic(self, line: str) -> str:
250
- return self._italic.sub("\\1*\\3*", line)
251
-
252
- def _format_code(self, line: str) -> str:
253
- return self._code.sub("\\1`\\3`", line)
254
-
255
-
256
- class PreformattedFormatter(Formatter):
257
- _format_line = functools.partial(LineFormatter().format)
258
-
259
- def _handles(self, line: str) -> bool:
260
- return line.startswith("| ") or line == "|"
261
-
262
- def format(self, lines: List[str]) -> str:
263
- lines = [LinkFormatter().remove_link(line[2:]) for line in lines]
264
- return "```text\n" + "\n".join(lines) + "\n```\n"
265
-
266
-
267
- class ParagraphFormatter(Formatter):
268
- _format_line = functools.partial(LineFormatter().format)
269
-
270
- def __init__(self, other_formatters: List[Formatter]) -> None:
271
- super().__init__()
272
- self._other_formatters = other_formatters
273
-
274
- def _handles(self, line: str) -> bool:
275
- return not any(other.handles(line) for other in self._other_formatters)
276
-
277
- def format(self, lines: List[str]) -> str:
278
- return self._format_line(" ".join(lines)) + "\n\n"
279
-
280
-
281
- class ListFormatter(Formatter):
282
- _strip_lines = False
283
- _format_item = functools.partial(LineFormatter().format)
284
-
285
- def _handles(self, line: str) -> bool:
286
- return bool(line.strip().startswith("- ") or line.startswith(" ") and self._lines)
287
-
288
- def format(self, lines: List[str]) -> str:
289
- items = ["- %s" % self._format_item(line) for line in self._combine_lines(lines)]
290
- return "\n".join(items) + "\n\n"
291
-
292
- def _combine_lines(self, lines: List[str]) -> Iterator[str]:
293
- current = []
294
- for line in lines:
295
- line = line.strip()
296
- if not line.startswith("- "):
297
- current.append(line)
298
- continue
299
- if current:
300
- yield " ".join(current)
301
- current = [line[2:].strip()]
302
- yield " ".join(current)
303
-
304
-
305
- class RulerFormatter(SingleLineFormatter):
306
- regex = re.compile("^-{3,}$")
307
-
308
- def match(self, line: str) -> Optional[re.Match[str]]:
309
- return self.regex.match(line)
310
-
311
- def format_line(self, line: str) -> str:
312
- return "---"
313
-
314
-
315
- class TableFormatter(Formatter):
316
- _table_line = re.compile(r"^\| (.* |)\|$")
317
- _line_splitter = re.compile(r" \|(?= )")
318
- _format_cell_content = functools.partial(LineFormatter().format)
319
-
320
- def _handles(self, line: str) -> bool:
321
- return self._table_line.match(line) is not None
322
-
323
- def format(self, lines: List[str]) -> str:
324
- return self._format_table([self._split_to_cells(line) for line in lines])
325
-
326
- def _split_to_cells(self, line: str) -> List[str]:
327
- return [cell.strip() for cell in self._line_splitter.split(line[1:-1])]
328
-
329
- def _format_table(self, rows: List[List[str]]) -> str:
330
- table = []
331
-
332
- max_columns = max(len(row) for row in rows)
333
-
334
- try:
335
- header_rows = [list(next(row for row in rows if any(cell for cell in row if cell.startswith("="))))]
336
- except StopIteration:
337
- header_rows = [[]]
338
-
339
- body_rows = [row for row in rows if row not in header_rows]
340
-
341
- for row in header_rows or [[]]:
342
- row += [""] * (max_columns - len(row))
343
- table.append(f'|{"|".join(self._format_cell(cell) for cell in row)}|')
344
-
345
- row_ = [" :--- "] * max_columns
346
- table.append(f'|{"|".join(row_)}|')
347
-
348
- for row in body_rows:
349
- row += [""] * (max_columns - len(row))
350
- table.append(f'|{"|".join(self._format_cell(cell) for cell in row)}|')
351
-
352
- return "\n".join(table) + "\n\n"
353
-
354
- def _format_cell(self, content: str) -> str:
355
- if content.startswith("=") and content.endswith("="):
356
- content = content[1:-1]
357
-
358
- return f" {self._format_cell_content(content).strip()} "