robotcode-repl 2.7.0__tar.gz → 2.8.0.dev548__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 (31) hide show
  1. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/.gitignore +4 -1
  2. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/PKG-INFO +4 -3
  3. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/pyproject.toml +2 -1
  4. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/Repl/repl.py +10 -1
  5. robotcode_repl-2.8.0.dev548/src/robotcode/repl/__version__.py +1 -0
  6. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_debug/controller.py +47 -5
  7. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_debug/types.py +6 -3
  8. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_indent.py +8 -1
  9. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_pt/components.py +2 -1
  10. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_pt/doc_viewer.py +457 -84
  11. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_pt/lexer.py +4 -1
  12. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/base_interpreter.py +281 -13
  13. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/cli.py +67 -13
  14. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/console_interpreter.py +356 -111
  15. robotcode_repl-2.8.0.dev548/src/robotcode/repl/doc_cli.py +651 -0
  16. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/hooks.py +2 -1
  17. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/prompt_toolkit_interpreter.py +35 -2
  18. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/run.py +51 -8
  19. robotcode_repl-2.7.0/src/robotcode/repl/__version__.py +0 -1
  20. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/README.md +0 -0
  21. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/Repl/__init__.py +0 -0
  22. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/__init__.py +0 -0
  23. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_backends.py +0 -0
  24. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_debug/__init__.py +0 -0
  25. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_keyword_lookup.py +0 -0
  26. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_progress.py +0 -0
  27. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_pt/__init__.py +0 -0
  28. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_pt/completion.py +0 -0
  29. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_pt/history.py +0 -0
  30. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/_session_export.py +0 -0
  31. {robotcode_repl-2.7.0 → robotcode_repl-2.8.0.dev548}/src/robotcode/repl/py.typed +0 -0
@@ -279,6 +279,9 @@ dist
279
279
  # vuepress build output
280
280
  .vuepress/dist
281
281
 
282
+ # Astro (documentation site)
283
+ docs/.astro/
284
+
282
285
  # Serverless directories
283
286
  .serverless/
284
287
 
@@ -339,7 +342,7 @@ bundled/libs
339
342
  # .agents
340
343
  .agents/
341
344
  skills-lock.json
342
- .claude
345
+ #.claude
343
346
 
344
347
  # sarif files
345
348
  /**/*.sarif.json
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: robotcode-repl
3
- Version: 2.7.0
3
+ Version: 2.8.0-dev.548
4
4
  Summary: RobotCode REPL for Robot Framework
5
5
  Project-URL: Homepage, https://robotcode.io
6
6
  Project-URL: Donate, https://opencollective.com/robotcode
@@ -20,13 +20,14 @@ Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
23
24
  Classifier: Programming Language :: Python :: Implementation :: CPython
24
25
  Classifier: Programming Language :: Python :: Implementation :: PyPy
25
26
  Classifier: Topic :: Utilities
26
27
  Classifier: Typing :: Typed
27
28
  Requires-Python: >=3.10
28
29
  Requires-Dist: prompt-toolkit!=3.0.52
29
- Requires-Dist: robotcode-runner==2.7.0
30
+ Requires-Dist: robotcode-runner==2.8.0-dev.548
30
31
  Description-Content-Type: text/markdown
31
32
 
32
33
  # robotcode-repl
@@ -18,6 +18,7 @@ classifiers = [
18
18
  "Programming Language :: Python :: 3.12",
19
19
  "Programming Language :: Python :: 3.13",
20
20
  "Programming Language :: Python :: 3.14",
21
+ "Programming Language :: Python :: 3.15",
21
22
  "Programming Language :: Python :: Implementation :: CPython",
22
23
  "Programming Language :: Python :: Implementation :: PyPy",
23
24
  "Operating System :: OS Independent",
@@ -28,7 +29,7 @@ classifiers = [
28
29
  ]
29
30
  dynamic = ["version"]
30
31
  dependencies = [
31
- "robotcode-runner==2.7.0",
32
+ "robotcode-runner==2.8.0-dev.548",
32
33
  "prompt_toolkit!=3.0.52"
33
34
  ]
34
35
 
@@ -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.
@@ -0,0 +1 @@
1
+ __version__ = "2.8.0-dev.548"
@@ -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
- triggered = True # a failing condition stops, so it gets noticed
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 in (frame.short_name, frame.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
  )
@@ -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, so the breakage is
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`, …).
@@ -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]) == "..."
@@ -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[List[Tuple[str, str]]]:
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