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.
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/PKG-INFO +3 -3
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/pyproject.toml +2 -2
- robotcode_language_server-0.65.1/src/robotcode/language_server/__version__.py +1 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/code_lens.py +1 -1
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/library_doc.py +14 -7
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/namespace.py +86 -121
- {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
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/hover.py +1 -1
- robotcode_language_server-0.64.0/src/robotcode/language_server/__version__.py +0 -1
- robotcode_language_server-0.64.0/src/robotcode/language_server/robotframework/utils/markdownformatter.py +0 -358
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/.gitignore +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/LICENSE.txt +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/README.md +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/__init__.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/cli.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/__init__.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/decorators.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/has_extend_capabilities.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/__init__.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/code_action.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/commands.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/completion.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/declaration.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/definition.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/diagnostics.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/document_highlight.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/document_symbols.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/documents.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/folding_range.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/formatting.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/hover.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/implementation.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/inlay_hint.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/inline_value.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/linked_editing_ranges.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/protocol_part.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/references.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/rename.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/selection_range.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/semantic_tokens.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/signature_help.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/window.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/parts/workspace.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/protocol.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/server.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/common/text_document.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/hooks.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/py.typed +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/__init__.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/configuration.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/__init__.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/analyzer.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/entities.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/errors.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/imports_manager.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/diagnostics/model_helper.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/languages.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/__init__.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/code_action_documentation.py +0 -0
- {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
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/code_action_refactor.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/codelens.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/completion.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/debugging_utils.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/diagnostics.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/document_highlight.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/document_symbols.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/documents_cache.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/folding_range.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/formatting.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/goto.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/inlay_hint.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/inline_value.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/protocol_part.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/references.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/rename.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/robocop_diagnostics.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/robot_workspace.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/selection_range.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/semantic_tokens.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/parts/signature_help.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/protocol.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/server.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/__init__.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/_variables.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/ast_utils.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/async_ast.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/match.py +0 -0
- {robotcode_language_server-0.64.0 → robotcode_language_server-0.65.1}/src/robotcode/language_server/robotframework/utils/robot_path.py +0 -0
- {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.
|
|
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.
|
|
29
|
-
Requires-Dist: robotcode==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
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.65.1"
|
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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(
|
|
139
|
+
has_value = bool(node.value)
|
|
138
140
|
value = tuple(
|
|
139
|
-
s.replace("${CURDIR}", str(Path(self.source).parent).replace("\\", "\\\\")) for s in
|
|
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=
|
|
146
|
+
name=node.name,
|
|
145
147
|
name_token=strip_variable_token(
|
|
146
|
-
|
|
148
|
+
Token(name_token.type, name, name_token.lineno, name_token.col_offset, name_token.error)
|
|
147
149
|
),
|
|
148
|
-
line_no=
|
|
149
|
-
col_offset=
|
|
150
|
-
end_line_no=
|
|
151
|
-
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:
|
|
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
|
-
|
|
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 ==
|
|
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
|
|
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 ==
|
|
232
|
+
if v.type == Token.VARIABLE
|
|
238
233
|
),
|
|
239
234
|
None,
|
|
240
235
|
)
|
|
241
236
|
|
|
242
|
-
def visit_Arguments(self, node:
|
|
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
|
-
|
|
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:
|
|
282
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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:
|
|
343
|
-
|
|
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(
|
|
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:
|
|
376
|
-
|
|
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:
|
|
409
|
-
|
|
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 =
|
|
416
|
-
alias_token =
|
|
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 =
|
|
419
|
-
|
|
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=
|
|
406
|
+
name=node.name,
|
|
425
407
|
name_token=name if name is not None else None,
|
|
426
|
-
args=
|
|
427
|
-
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:
|
|
446
|
-
|
|
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
|
-
|
|
450
|
-
|
|
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=
|
|
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:
|
|
477
|
-
|
|
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
|
-
|
|
481
|
-
|
|
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=
|
|
459
|
+
name=node.name,
|
|
490
460
|
name_token=name if name is not None else None,
|
|
491
|
-
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
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
|
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""
|
|
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('"', """)
|
|
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()} "
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|