robotcode-repl 2.6.2__py3-none-any.whl → 2.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- robotcode/repl/Repl/repl.py +10 -1
- robotcode/repl/__version__.py +1 -1
- robotcode/repl/_debug/controller.py +47 -5
- robotcode/repl/_debug/types.py +6 -3
- robotcode/repl/_indent.py +8 -1
- robotcode/repl/_pt/components.py +2 -1
- robotcode/repl/_pt/doc_viewer.py +457 -84
- robotcode/repl/_pt/lexer.py +4 -1
- robotcode/repl/base_interpreter.py +281 -13
- robotcode/repl/cli.py +77 -14
- robotcode/repl/console_interpreter.py +356 -111
- robotcode/repl/doc_cli.py +655 -0
- robotcode/repl/hooks.py +2 -1
- robotcode/repl/prompt_toolkit_interpreter.py +35 -2
- robotcode/repl/run.py +51 -8
- {robotcode_repl-2.6.2.dist-info → robotcode_repl-2.8.0.dist-info}/METADATA +4 -3
- robotcode_repl-2.8.0.dist-info/RECORD +30 -0
- {robotcode_repl-2.6.2.dist-info → robotcode_repl-2.8.0.dist-info}/WHEEL +1 -1
- robotcode_repl-2.6.2.dist-info/RECORD +0 -29
- {robotcode_repl-2.6.2.dist-info → robotcode_repl-2.8.0.dist-info}/entry_points.txt +0 -0
robotcode/repl/Repl/repl.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
|
|
3
|
+
from robot.errors import ExecutionFailures
|
|
4
|
+
|
|
3
5
|
from ..__version__ import __version__
|
|
6
|
+
from ..base_interpreter import take_active_interpreter
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
class Repl:
|
|
@@ -18,8 +21,14 @@ class Repl:
|
|
|
18
21
|
"""Internal marker keyword that opens the interactive REPL prompt.
|
|
19
22
|
|
|
20
23
|
Called by the synthetic suite ``robotcode repl`` runs; not meant to be
|
|
21
|
-
used directly in your own tests.
|
|
24
|
+
used directly in your own tests. Once the prompt is left, it fails with
|
|
25
|
+
the failures ``robotcode repl`` recorded during the session, so the
|
|
26
|
+
session test reflects them.
|
|
22
27
|
"""
|
|
28
|
+
# The prompt loop ran while this keyword started; its body runs after it.
|
|
29
|
+
interpreter = take_active_interpreter()
|
|
30
|
+
if interpreter is not None and interpreter.failures:
|
|
31
|
+
raise ExecutionFailures(interpreter.failures)
|
|
23
32
|
|
|
24
33
|
def breakpoint(self) -> None:
|
|
25
34
|
"""No-op marker keyword: the RobotCode debugger pauses here when attached.
|
robotcode/repl/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.
|
|
1
|
+
__version__ = "2.8.0"
|
|
@@ -12,16 +12,18 @@ import contextlib
|
|
|
12
12
|
import re
|
|
13
13
|
import reprlib
|
|
14
14
|
import weakref
|
|
15
|
+
from functools import lru_cache
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Mapping, Optional, Set
|
|
17
18
|
|
|
18
19
|
from robot.running.context import EXECUTION_CONTEXTS
|
|
20
|
+
from robot.utils import Matcher
|
|
19
21
|
from robot.variables import evaluate_expression
|
|
20
22
|
|
|
21
23
|
from robotcode.core.utils.path import normalized_path
|
|
22
24
|
from robotcode.robot.utils import RF_VERSION
|
|
23
25
|
|
|
24
|
-
from ..base_interpreter import result_qualified_name
|
|
26
|
+
from ..base_interpreter import result_definition_names, result_qualified_name
|
|
25
27
|
from .types import (
|
|
26
28
|
Breakpoint,
|
|
27
29
|
DebugTerminated,
|
|
@@ -71,6 +73,15 @@ _CATCHING_KEYWORDS = frozenset(
|
|
|
71
73
|
}
|
|
72
74
|
)
|
|
73
75
|
|
|
76
|
+
|
|
77
|
+
@lru_cache(maxsize=None)
|
|
78
|
+
def _keyword_matcher(name: str) -> Matcher:
|
|
79
|
+
"""A keyword breakpoint's name as Robot Framework matches keyword names in
|
|
80
|
+
`--removekeywords name:`: ignoring case, spaces and underscores, with the
|
|
81
|
+
glob patterns `*`, `?` and `[chars]`."""
|
|
82
|
+
return Matcher(name, ignore="_")
|
|
83
|
+
|
|
84
|
+
|
|
74
85
|
# The `Breakpoint` marker keyword from `robotcode.repl.Repl` (see Repl/repl.py):
|
|
75
86
|
# the debugger stops where it runs. Its qualified name is stable across RF 5/6/7.
|
|
76
87
|
_BREAKPOINT_MARKER = "robotcode.repl.Repl.Breakpoint"
|
|
@@ -119,6 +130,8 @@ class DebugController:
|
|
|
119
130
|
self._next_bp_id = 1
|
|
120
131
|
# the breakpoint matched at the current stop (for `.commands` replay)
|
|
121
132
|
self._stopped_breakpoint: Optional[Breakpoint] = None
|
|
133
|
+
# why that breakpoint's condition couldn't be evaluated, shown in the stop line
|
|
134
|
+
self._condition_error = ""
|
|
122
135
|
|
|
123
136
|
# stepping / pause state
|
|
124
137
|
self._stepping: Optional[ResumeAction] = None
|
|
@@ -241,6 +254,15 @@ class DebugController:
|
|
|
241
254
|
"""Pause at the next executed keyword (on-demand pause)."""
|
|
242
255
|
self._pause_requested = True
|
|
243
256
|
|
|
257
|
+
def cancel_pause(self) -> None:
|
|
258
|
+
"""Drop a pause request that hasn't stopped yet."""
|
|
259
|
+
self._pause_requested = False
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def pause_requested(self) -> bool:
|
|
263
|
+
"""Whether a pause request waits for the next executed keyword."""
|
|
264
|
+
return self._pause_requested
|
|
265
|
+
|
|
244
266
|
@property
|
|
245
267
|
def stack(self) -> List[StackFrame]:
|
|
246
268
|
"""Current call stack, outermost frame first."""
|
|
@@ -439,7 +461,7 @@ class DebugController:
|
|
|
439
461
|
|
|
440
462
|
reason = self._stop_reason(frame)
|
|
441
463
|
if reason is not None:
|
|
442
|
-
self._pause(reason, frame)
|
|
464
|
+
self._pause(reason, frame, self._condition_error if reason == StopReason.BREAKPOINT else "")
|
|
443
465
|
|
|
444
466
|
def end_keyword(self, data: "running.Keyword", result: "result.Keyword") -> None:
|
|
445
467
|
if not self._stack:
|
|
@@ -614,6 +636,7 @@ class DebugController:
|
|
|
614
636
|
logpoint emit (log + continue), one-shot removal; records the matched
|
|
615
637
|
breakpoint in `_stopped_breakpoint` for the front-end (`.commands`)."""
|
|
616
638
|
self._stopped_breakpoint = None
|
|
639
|
+
self._condition_error = ""
|
|
617
640
|
if frame.name == _BREAKPOINT_MARKER: # embedded `Breakpoint` keyword
|
|
618
641
|
return True
|
|
619
642
|
bp = self._match_breakpoint(frame)
|
|
@@ -624,8 +647,17 @@ class DebugController:
|
|
|
624
647
|
triggered = bool(self.evaluate_expression(frame, bp.condition))
|
|
625
648
|
except (SystemExit, KeyboardInterrupt):
|
|
626
649
|
raise
|
|
627
|
-
except BaseException:
|
|
628
|
-
|
|
650
|
+
except BaseException as e:
|
|
651
|
+
# A failing condition stops right away, so it gets noticed: past the
|
|
652
|
+
# ignore count and keeping a temporary breakpoint (pdb semantics).
|
|
653
|
+
# A logpoint never stops; it reports the error instead.
|
|
654
|
+
self._condition_error = f"condition error: {str(e) or type(e).__name__}"
|
|
655
|
+
if bp.log_message is not None:
|
|
656
|
+
if self._frontend is not None:
|
|
657
|
+
self._frontend.on_output(self._condition_error)
|
|
658
|
+
return False
|
|
659
|
+
self._stopped_breakpoint = bp
|
|
660
|
+
return True
|
|
629
661
|
if not triggered:
|
|
630
662
|
return False
|
|
631
663
|
bp.hits += 1
|
|
@@ -643,7 +675,7 @@ class DebugController:
|
|
|
643
675
|
for bp in self._breakpoints:
|
|
644
676
|
if not bp.enabled:
|
|
645
677
|
continue
|
|
646
|
-
if bp.kind == "keyword" and bp.name
|
|
678
|
+
if bp.kind == "keyword" and bp.name is not None and self._keyword_matches(bp.name, frame):
|
|
647
679
|
return bp
|
|
648
680
|
if (
|
|
649
681
|
bp.kind == "line"
|
|
@@ -655,6 +687,13 @@ class DebugController:
|
|
|
655
687
|
return bp
|
|
656
688
|
return None
|
|
657
689
|
|
|
690
|
+
@staticmethod
|
|
691
|
+
def _keyword_matches(name: str, frame: StackFrame) -> bool:
|
|
692
|
+
if frame.type not in _KEYWORD_FRAME_TYPES:
|
|
693
|
+
return False
|
|
694
|
+
matcher = _keyword_matcher(name)
|
|
695
|
+
return bool(matcher.match_any((frame.name, frame.short_name, *frame.definition_names)))
|
|
696
|
+
|
|
658
697
|
def _emit_logpoint(self, frame: StackFrame, message: str) -> None:
|
|
659
698
|
try:
|
|
660
699
|
text = self._frame_store(frame).replace_string(message)
|
|
@@ -666,6 +705,8 @@ class DebugController:
|
|
|
666
705
|
self._frontend.on_output(text)
|
|
667
706
|
|
|
668
707
|
def _pause(self, reason: StopReason, frame: StackFrame, description: str = "") -> None:
|
|
708
|
+
# Any stop answers a pending pause request; it must not stop the run once more.
|
|
709
|
+
self._pause_requested = False
|
|
669
710
|
if self._frontend is None:
|
|
670
711
|
# No UI attached: keep running (don't block).
|
|
671
712
|
self._stepping = None
|
|
@@ -736,4 +777,5 @@ class DebugController:
|
|
|
736
777
|
executed=(status != _NOT_RUN),
|
|
737
778
|
context=context_ref,
|
|
738
779
|
variables=variables_ref,
|
|
780
|
+
definition_names=result_definition_names(result),
|
|
739
781
|
)
|
robotcode/repl/_debug/types.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import enum
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Any, List, Optional, Protocol, runtime_checkable
|
|
5
|
+
from typing import Any, List, Optional, Protocol, Tuple, runtime_checkable
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class DebugTerminated(BaseException):
|
|
@@ -53,6 +53,9 @@ class StackFrame:
|
|
|
53
53
|
executed: bool = True
|
|
54
54
|
context: Any = field(default=None, repr=False, compare=False)
|
|
55
55
|
variables: Any = field(default=None, repr=False, compare=False)
|
|
56
|
+
# For a keyword with embedded arguments, its definition name (`Select ${animal}
|
|
57
|
+
# From List`), bare and qualified; empty for other frames.
|
|
58
|
+
definition_names: Tuple[str, ...] = ()
|
|
56
59
|
|
|
57
60
|
@property
|
|
58
61
|
def short_name(self) -> str:
|
|
@@ -66,8 +69,8 @@ class Breakpoint:
|
|
|
66
69
|
|
|
67
70
|
`kind` is ``"line"`` (`source`+`line`) or ``"keyword"`` (`name`). The optional
|
|
68
71
|
attributes drive the hit-time decision: `condition` (stop only if the
|
|
69
|
-
expression is truthy — a *failing* condition stops too,
|
|
70
|
-
noticed), `ignore_count` (skip the next N triggering hits), `log_message`
|
|
72
|
+
expression is truthy — a *failing* condition stops too, past `ignore_count`
|
|
73
|
+
and `temporary`, so the breakage is noticed), `ignore_count` (skip the next N triggering hits), `log_message`
|
|
71
74
|
(logpoint: log + continue, never stop), `temporary` (remove after it stops),
|
|
72
75
|
`commands` (debugger commands replayed at the hit). `id` is a stable number
|
|
73
76
|
for referencing the breakpoint (`.condition 2`, `.delete 2`, …).
|
robotcode/repl/_indent.py
CHANGED
|
@@ -4,7 +4,8 @@ Pure-Python, deterministic, no Robot-runtime dependency. Used by:
|
|
|
4
4
|
|
|
5
5
|
- `ConsoleInterpreter.get_input()` to pre-compute the indent string
|
|
6
6
|
passed via `prefill=` to the next `read_line()` call (so the
|
|
7
|
-
continuation prompt seeds the user's cursor at the right column)
|
|
7
|
+
continuation prompt seeds the user's cursor at the right column),
|
|
8
|
+
and to spot a fresh input that starts with a continuation line.
|
|
8
9
|
- `_pt.components` smart-Enter / Shift-Enter key bindings to decide
|
|
9
10
|
whether the buffer still has an open block and what indent the
|
|
10
11
|
inserted newline should carry.
|
|
@@ -80,3 +81,9 @@ def has_open_block(text: str) -> bool:
|
|
|
80
81
|
block openers than closers — i.e. would not be a complete Robot
|
|
81
82
|
statement if submitted as-is."""
|
|
82
83
|
return _block_depth(text.splitlines()) > 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def starts_with_continuation(text: str) -> bool:
|
|
87
|
+
"""True if the first line of `text` is a continuation line — its first
|
|
88
|
+
cell is Robot's ``...`` marker."""
|
|
89
|
+
return _first_cell(text.split("\n", 1)[0]) == "..."
|
robotcode/repl/_pt/components.py
CHANGED
|
@@ -15,6 +15,7 @@ from prompt_toolkit.buffer import Buffer
|
|
|
15
15
|
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
|
|
16
16
|
from prompt_toolkit.document import Document
|
|
17
17
|
from prompt_toolkit.filters import has_completions
|
|
18
|
+
from prompt_toolkit.formatted_text import StyleAndTextTuples
|
|
18
19
|
from prompt_toolkit.history import FileHistory
|
|
19
20
|
from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
|
|
20
21
|
from prompt_toolkit.styles import Style
|
|
@@ -404,7 +405,7 @@ def _render_signature(name: str, spec: Any, active: int) -> List[Tuple[str, str]
|
|
|
404
405
|
return parts
|
|
405
406
|
|
|
406
407
|
|
|
407
|
-
def _bottom_toolbar() -> Optional[
|
|
408
|
+
def _bottom_toolbar() -> Optional[StyleAndTextTuples]:
|
|
408
409
|
"""Bottom status line — keyword signature when the cursor sits in an
|
|
409
410
|
argument cell, else nothing (the bar is hidden).
|
|
410
411
|
|