branch: elpa/racket-mode
commit c2758f6c0405490aef178e256432be05686a144a
Author: Greg Hendershott <[email protected]>
Commit: Greg Hendershott <[email protected]>

    Update racket-debug-mode
    
    == Front end ==
    
    Remove "EXPERIMENTAL!" caveat from doc strings.
    
    Change racket-debuggable-files from defvar to defcustom.
    
    Highlight ("gently") the entire break expression span, as well as the
    "before" or "after" end where the break occurs. In my hands on testing
    with non-sexp langs like rhombus, I think this helps clarify what's
    going on.
    
    Refactor so that the break expressions set by the user are always
    supplied to the back end. That way e.g. a watchpoint will also work
    when single stepping. IOW only the `racket-debug-go` command will
    ignore them, whereas other commands will honor them.
    
    Rename the "racket-debug-xxx-breakpoint" commands to
    "--break-expression", as the expressions are general (breaks, watches,
    etc.)
    
    Change the motion command names from "next"/"prev" to
    "forward"/"backward", consistent with other Emacs motion commands.
    
    Enhance racket-debug-{forward backward}-point commands to move among
    points in different file buffers.
    
    Improve prose for many doc strings.
    
    Surface minor mode commands in the info/html "reference".
    
    Update some code to use seq (instead of cl-lib or mapcar).
    
    The debugger REPL is too fraught with complications how to wrap any
    arbitrary lang expression in a let-syntax -- especially rhombus with
    its restricted `#%top-interaction`. Drop it. Instead add a command to
    set local variables, which is one of the few practical things the
    debugger REPL could be used for.
    
    == Back end ==
    
    Use struct hierarchy for "next" values (instead of using a pile of
    contracts).
    
    Change the representation of break expressions to use a hash-table
    instead of a list.
---
 doc/generate.el            |  53 ++--
 doc/racket-mode.texi       | 702 +++++++++++++++++++++++++++------------------
 racket-custom.el           |  50 +++-
 racket-debug.el            | 566 +++++++++++++++++++++---------------
 racket-mode.el             |   1 +
 racket/command-server.rkt  |   1 +
 racket/debug-annotator.rkt |  16 +-
 racket/debug.rkt           | 459 +++++++++++++++--------------
 racket/repl.rkt            |   4 +-
 test/racket-tests.el       |  33 ++-
 10 files changed, 1102 insertions(+), 783 deletions(-)

diff --git a/doc/generate.el b/doc/generate.el
index 9cbe95fbde3..e8017bce391 100644
--- a/doc/generate.el
+++ b/doc/generate.el
@@ -61,24 +61,24 @@
     racket-complete-at-point
     "Hash Langs"
     racket-hash-lang-mode
-    (racket-hash-lang-backward ,racket-hash-lang-mode-map)
-    (racket-hash-lang-forward ,racket-hash-lang-mode-map)
-    (racket-hash-lang-up ,racket-hash-lang-mode-map)
-    (racket-hash-lang-down ,racket-hash-lang-mode-map)
+    (racket-hash-lang-backward   ,racket-hash-lang-mode-map)
+    (racket-hash-lang-forward    ,racket-hash-lang-mode-map)
+    (racket-hash-lang-up         ,racket-hash-lang-mode-map)
+    (racket-hash-lang-down       ,racket-hash-lang-mode-map)
     (racket-hash-lang-C-M-q-dwim ,racket-hash-lang-mode-map)
     "Explore"
     racket-xp-mode
-    (racket-xp-describe ,racket-xp-mode-map)
-    (racket-xp-documentation ,racket-xp-mode-map)
-    (racket-xp-next-definition ,racket-xp-mode-map)
-    (racket-xp-previous-definition ,racket-xp-mode-map)
-    (racket-xp-next-use ,racket-xp-mode-map)
-    (racket-xp-previous-use ,racket-xp-mode-map)
-    (racket-xp-next-error ,racket-xp-mode-map)
-    (racket-xp-previous-error ,racket-xp-mode-map)
-    (racket-xp-tail-up ,racket-xp-mode-map)
-    (racket-xp-tail-down ,racket-xp-mode-map)
-    (racket-xp-tail-next-sibling ,racket-xp-mode-map)
+    (racket-xp-describe              ,racket-xp-mode-map)
+    (racket-xp-documentation         ,racket-xp-mode-map)
+    (racket-xp-next-definition       ,racket-xp-mode-map)
+    (racket-xp-previous-definition   ,racket-xp-mode-map)
+    (racket-xp-next-use              ,racket-xp-mode-map)
+    (racket-xp-previous-use          ,racket-xp-mode-map)
+    (racket-xp-next-error            ,racket-xp-mode-map)
+    (racket-xp-previous-error        ,racket-xp-mode-map)
+    (racket-xp-tail-up               ,racket-xp-mode-map)
+    (racket-xp-tail-down             ,racket-xp-mode-map)
+    (racket-xp-tail-next-sibling     ,racket-xp-mode-map)
     (racket-xp-tail-previous-sibling ,racket-xp-mode-map)
     racket-documentation-search
     racket-describe-mode
@@ -96,7 +96,6 @@
     racket-profile-mode
     racket-logger
     racket-logger-mode
-    (racket-debug-mode ,racket-xp-mode-map)
     racket-repl-clear
     racket-repl-clear-leaving-last-prompt
     "Test"
@@ -119,8 +118,23 @@
     list-racket-packages
     racket-package-mode
     describe-racket-package
+    "Debug"
+    racket-debug-mode
+    (racket-debug-step                      ,racket-debug-mode-map)
+    (racket-debug-step-over                 ,racket-debug-mode-map)
+    (racket-debug-step-out                  ,racket-debug-mode-map)
+    (racket-debug-forward-breakable         ,racket-debug-mode-map)
+    (racket-debug-backward-breakable        ,racket-debug-mode-map)
+    (racket-debug-run-to-here               ,racket-debug-mode-map)
+    (racket-debug-set-break-expression      ,racket-debug-mode-map)
+    (racket-debug-clear-break-expression    ,racket-debug-mode-map)
+    (racket-debug-toggle-break-expression   ,racket-debug-mode-map)
+    (racket-debug-forward-break-expression  ,racket-debug-mode-map)
+    (racket-debug-backward-break-expression ,racket-debug-mode-map)
+    (racket-debug-set-local                 ,racket-debug-mode-map)
+    (racket-debug-continue                  ,racket-debug-mode-map)
+    (racket-debug-go                        ,racket-debug-mode-map)
     "Other"
-    racket-debug-toggle-breakpoint
     racket-mode-start-faster
     racket-mode-start-slower)
   "Commands to include in the Reference.")
@@ -415,10 +429,7 @@ sorted shortest key sequences first."
                   (add sym (reverse (cons key prefix-keys)))))))))
       ;; Recursively mutate `alist'.
       (keymap km nil)
-      ;; Navigate `alist' sorted by command.
-      (dolist (v (seq-sort-by (lambda (v) (symbol-name (car v)))
-                              #'string<
-                              alist))
+      (dolist (v alist)
         (let* ((command-str (racket--ref-or-code (car v)))
                (keys-strs (seq-map (lambda (binding)
                                      (format "{{{kbd(%s)}}}"
diff --git a/doc/racket-mode.texi b/doc/racket-mode.texi
index 8e5a25e4b77..a269ed79ceb 100644
--- a/doc/racket-mode.texi
+++ b/doc/racket-mode.texi
@@ -91,6 +91,7 @@ Commands
 * Collections::
 * Macro expand::
 * Packages::
+* Debug::
 * Other::
 
 Edit
@@ -156,7 +157,6 @@ Run
 * racket-profile-mode::
 * racket-logger::
 * racket-logger-mode::
-* racket-debug-mode::
 * racket-repl-clear::
 * racket-repl-clear-leaving-last-prompt::
 
@@ -190,9 +190,26 @@ Packages
 * racket-package-mode::
 * describe-racket-package::
 
+Debug
+
+* racket-debug-mode::
+* racket-debug-step::
+* racket-debug-step-over::
+* racket-debug-step-out::
+* racket-debug-forward-breakable::
+* racket-debug-backward-breakable::
+* racket-debug-run-to-here::
+* racket-debug-set-break-expression::
+* racket-debug-clear-break-expression::
+* racket-debug-toggle-break-expression::
+* racket-debug-forward-break-expression::
+* racket-debug-backward-break-expression::
+* racket-debug-set-local::
+* racket-debug-continue::
+* racket-debug-go::
+
 Other
 
-* racket-debug-toggle-breakpoint::
 * racket-mode-start-faster::
 * racket-mode-start-slower::
 
@@ -938,6 +955,7 @@ You can also view these by using the normal Emacs help 
mechanism:
 * Collections::
 * Macro expand::
 * Packages::
+* Debug::
 * Other::
 @end menu
 
@@ -984,50 +1002,50 @@ See also @ref{racket-hash-lang-mode}.
 @multitable 
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 @item Key
 @tab Binding
-@item @kbd{TAB} 
-@tab @code{indent-for-tab-command}
-@item @kbd{C-M-u} 
-@tab @ref{racket-backward-up-list}
-@item @kbd{C-c C-p} 
-@tab @ref{racket-cycle-paren-shapes}
-@item @kbd{C-c C-s} or @kbd{C-c C-.} 
-@tab @ref{racket-describe-search}
-@item @kbd{C-c C-d} 
-@tab @ref{racket-documentation-search}
+@item @kbd{C-c C-c} or @kbd{C-c C-k} 
+@tab @ref{racket-run-module-at-point}
 @item @kbd{C-c C-z} 
 @tab @code{racket-edit-switch-to-repl}
-@item @kbd{C-c C-e x} 
-@tab @ref{racket-expand-definition}
+@item @kbd{C-c C-t} 
+@tab @ref{racket-test}
+@item @kbd{C-c C-l} 
+@tab @ref{racket-logger}
+@item @kbd{C-c C-o} 
+@tab @ref{racket-profile}
+@item @kbd{C-c C-r} 
+@tab @ref{racket-send-region}
 @item @kbd{C-c C-e f} 
 @tab @ref{racket-expand-file}
+@item @kbd{C-c C-e x} 
+@tab @ref{racket-expand-definition}
 @item @kbd{C-c C-e e} 
 @tab @ref{racket-expand-last-sexp}
 @item @kbd{C-c C-e r} 
 @tab @ref{racket-expand-region}
-@item @kbd{C-c C-f} 
-@tab @ref{racket-fold-all-tests}
-@item @kbd{)} or @kbd{]} or @kbd{@}} 
-@tab @ref{racket-insert-closing}
-@item @kbd{C-M-y} 
-@tab @ref{racket-insert-lambda}
-@item @kbd{C-c C-l} 
-@tab @ref{racket-logger}
 @item @kbd{C-c C-x C-f} 
 @tab @ref{racket-open-require-path}
-@item @kbd{C-c C-o} 
-@tab @ref{racket-profile}
-@item @kbd{C-c C-c} or @kbd{C-c C-k} 
-@tab @ref{racket-run-module-at-point}
+@item @kbd{C-c C-p} 
+@tab @ref{racket-cycle-paren-shapes}
+@item @kbd{C-c C-d} 
+@tab @ref{racket-documentation-search}
+@item @kbd{C-c C-s} or @kbd{C-c C-.} 
+@tab @ref{racket-describe-search}
+@item @kbd{C-c C-f} 
+@tab @ref{racket-fold-all-tests}
+@item @kbd{C-c C-u} 
+@tab @ref{racket-unfold-all-tests}
 @item @kbd{C-M-x} 
 @tab @ref{racket-send-definition}
+@item @kbd{C-M-u} 
+@tab @ref{racket-backward-up-list}
+@item @kbd{C-M-y} 
+@tab @ref{racket-insert-lambda}
 @item @kbd{C-x C-e} 
 @tab @ref{racket-send-last-sexp}
-@item @kbd{C-c C-r} 
-@tab @ref{racket-send-region}
-@item @kbd{C-c C-t} 
-@tab @ref{racket-test}
-@item @kbd{C-c C-u} 
-@tab @ref{racket-unfold-all-tests}
+@item @kbd{TAB} 
+@tab @code{indent-for-tab-command}
+@item @kbd{)} or @kbd{]} or @kbd{@}} 
+@tab @ref{racket-insert-closing}
 @end multitable
 
 
@@ -1586,52 +1604,52 @@ A discussion of the information provided by a Racket 
language:
 @multitable 
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 @item Key
 @tab Binding
-@item @kbd{RET} 
-@tab @code{newline-and-indent}
+@item @kbd{C-c C-c} or @kbd{C-c C-k} 
+@tab @ref{racket-run-module-at-point}
 @item @kbd{C-c C-z} 
 @tab @code{racket-edit-switch-to-repl}
-@item @kbd{C-c C-e x} 
-@tab @ref{racket-expand-definition}
+@item @kbd{C-c C-t} 
+@tab @ref{racket-test}
+@item @kbd{C-c C-l} 
+@tab @ref{racket-logger}
+@item @kbd{C-c C-o} 
+@tab @ref{racket-profile}
+@item @kbd{C-c C-r} 
+@tab @ref{racket-send-region}
 @item @kbd{C-c C-e f} 
 @tab @ref{racket-expand-file}
+@item @kbd{C-c C-e x} 
+@tab @ref{racket-expand-definition}
 @item @kbd{C-c C-e e} 
 @tab @ref{racket-expand-last-sexp}
 @item @kbd{C-c C-e r} 
 @tab @ref{racket-expand-region}
+@item @kbd{C-c C-x C-f} 
+@tab @ref{racket-open-require-path}
 @item @kbd{C-c C-f} 
 @tab @ref{racket-fold-all-tests}
-@item @kbd{C-M-q} 
-@tab @ref{racket-hash-lang-C-M-q-dwim}
+@item @kbd{C-c C-u} 
+@tab @ref{racket-unfold-all-tests}
+@item @kbd{C-M-x} 
+@tab @ref{racket-send-definition}
+@item @kbd{C-M-y} 
+@tab @ref{racket-insert-lambda}
 @item @kbd{C-M-b} 
 @tab @ref{racket-hash-lang-backward}
-@item @kbd{C-M-d} 
-@tab @ref{racket-hash-lang-down}
 @item @kbd{C-M-f} 
 @tab @ref{racket-hash-lang-forward}
-@item @kbd{TAB} 
-@tab @code{racket-hash-lang-indent}
 @item @kbd{C-M-u} 
 @tab @ref{racket-hash-lang-up}
-@item @kbd{C-M-y} 
-@tab @ref{racket-insert-lambda}
-@item @kbd{C-c C-l} 
-@tab @ref{racket-logger}
-@item @kbd{C-c C-x C-f} 
-@tab @ref{racket-open-require-path}
-@item @kbd{C-c C-o} 
-@tab @ref{racket-profile}
-@item @kbd{C-c C-c} or @kbd{C-c C-k} 
-@tab @ref{racket-run-module-at-point}
-@item @kbd{C-M-x} 
-@tab @ref{racket-send-definition}
+@item @kbd{C-M-d} 
+@tab @ref{racket-hash-lang-down}
+@item @kbd{C-M-q} 
+@tab @ref{racket-hash-lang-C-M-q-dwim}
 @item @kbd{C-x C-e} 
 @tab @ref{racket-send-last-sexp}
-@item @kbd{C-c C-r} 
-@tab @ref{racket-send-region}
-@item @kbd{C-c C-t} 
-@tab @ref{racket-test}
-@item @kbd{C-c C-u} 
-@tab @ref{racket-unfold-all-tests}
+@item @kbd{TAB} 
+@tab @code{racket-hash-lang-indent}
+@item @kbd{RET} 
+@tab @code{newline-and-indent}
 @end multitable
 
 
@@ -1882,40 +1900,40 @@ commands directly to whatever keys you prefer.
 @multitable 
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 @item Key
 @tab Binding
-@item @kbd{C-c # N} 
-@tab @code{next-error}
-@item @kbd{C-c # P} 
-@tab @code{previous-error}
-@item @kbd{C-c C-s} 
-@tab @ref{racket-describe-search}
-@item @kbd{C-c # g} 
-@tab @code{racket-xp-annotate}
-@item @kbd{C-c C-.} 
-@tab @ref{racket-xp-describe}
-@item @kbd{C-c C-d} 
-@tab @ref{racket-xp-documentation}
 @item @kbd{C-c # j} 
 @tab @ref{racket-xp-next-definition}
-@item @kbd{C-c # n} 
-@tab @ref{racket-xp-next-use}
 @item @kbd{C-c # k} 
 @tab @ref{racket-xp-previous-definition}
+@item @kbd{C-c # n} 
+@tab @ref{racket-xp-next-use}
 @item @kbd{C-c # p} 
 @tab @ref{racket-xp-previous-use}
+@item @kbd{C-c # ?} 
+@tab @code{xref-find-references}
 @item @kbd{C-c # r} 
 @tab @code{racket-xp-rename}
+@item @kbd{C-c # ^} 
+@tab @ref{racket-xp-tail-up}
 @item @kbd{C-c # v} 
 @tab @ref{racket-xp-tail-down}
 @item @kbd{C-c # >} 
 @tab @ref{racket-xp-tail-next-sibling}
 @item @kbd{C-c # <} 
 @tab @ref{racket-xp-tail-previous-sibling}
-@item @kbd{C-c # ^} 
-@tab @ref{racket-xp-tail-up}
+@item @kbd{C-c # g} 
+@tab @code{racket-xp-annotate}
+@item @kbd{C-c # N} 
+@tab @code{next-error}
+@item @kbd{C-c # P} 
+@tab @code{previous-error}
+@item @kbd{C-c C-.} 
+@tab @ref{racket-xp-describe}
+@item @kbd{C-c C-d} 
+@tab @ref{racket-xp-documentation}
+@item @kbd{C-c C-s} 
+@tab @ref{racket-describe-search}
 @item @kbd{M-.} or @kbd{C-c # .} 
 @tab @code{xref-find-definitions}
-@item @kbd{C-c # ?} 
-@tab @code{xref-find-references}
 @end multitable
 
 
@@ -2158,34 +2176,34 @@ browser program -- are given @ref{racket-ext-link-face}.
 @multitable 
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 @item Key
 @tab Binding
-@item @kbd{<} 
-@tab @code{beginning-of-buffer}
-@item @kbd{>} 
-@tab @code{end-of-buffer}
 @item @kbd{q} 
 @tab @code{quit-window}
+@item @kbd{SPC} 
+@tab @code{scroll-up-command}
+@item @kbd{DEL} or @kbd{S-SPC} 
+@tab @code{scroll-down-command}
+@item @kbd{>} 
+@tab @code{end-of-buffer}
+@item @kbd{<} 
+@tab @code{beginning-of-buffer}
+@item @kbd{g} 
+@tab @code{revert-buffer}
 @item @kbd{l} or @kbd{b} or @kbd{C-c C-b} 
 @tab @code{racket-describe-back}
-@item @kbd{x} 
-@tab @code{racket-describe-browse-external}
 @item @kbd{r} or @kbd{f} or @kbd{C-c C-f} 
 @tab @code{racket-describe-forward}
+@item @kbd{i} or @kbd{C-c C-s} 
+@tab @ref{racket-describe-search}
 @item @kbd{n} 
 @tab @code{racket-describe-nav-next}
 @item @kbd{p} 
 @tab @code{racket-describe-nav-prev}
-@item @kbd{C-^} 
-@tab @code{racket-describe-nav-top}
 @item @kbd{^} 
 @tab @code{racket-describe-nav-up}
-@item @kbd{i} or @kbd{C-c C-s} 
-@tab @ref{racket-describe-search}
-@item @kbd{g} 
-@tab @code{revert-buffer}
-@item @kbd{DEL} or @kbd{S-SPC} 
-@tab @code{scroll-down-command}
-@item @kbd{SPC} 
-@tab @code{scroll-up-command}
+@item @kbd{C-^} 
+@tab @code{racket-describe-nav-top}
+@item @kbd{x} 
+@tab @code{racket-describe-browse-external}
 @end multitable
 
 
@@ -2217,7 +2235,6 @@ Search installed documentation; view using 
@ref{racket-describe-mode}.
 * racket-profile-mode::
 * racket-logger::
 * racket-logger-mode::
-* racket-debug-mode::
 * racket-repl-clear::
 * racket-repl-clear-leaving-last-prompt::
 @end menu
@@ -2238,54 +2255,54 @@ identifier bindings and modules from the REPL's 
namespace.
 @multitable 
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 @item Key
 @tab Binding
-@item @kbd{TAB} 
-@tab @code{indent-for-tab-command}
+@item @kbd{RET} 
+@tab @code{racket-repl-submit}
 @item @kbd{C-j} 
 @tab @code{newline-and-indent}
-@item @kbd{C-M-q} 
-@tab @code{prog-indent-sexp}
+@item @kbd{TAB} 
+@tab @code{indent-for-tab-command}
 @item @kbd{C-M-u} 
 @tab @ref{racket-backward-up-list}
-@item @kbd{C-c C-s} 
-@tab @ref{racket-describe-search}
-@item @kbd{C-c C-e x} 
-@tab @ref{racket-expand-definition}
+@item @kbd{C-M-q} 
+@tab @code{prog-indent-sexp}
+@item @kbd{M-p} 
+@tab @code{racket-repl-previous-input}
+@item @kbd{M-n} 
+@tab @code{racket-repl-next-input}
+@item @kbd{C-M-y} 
+@tab @ref{racket-insert-lambda}
+@item @kbd{C-c C-u} 
+@tab @code{racket-repl-clear-input}
+@item @kbd{C-c C-p} 
+@tab @code{racket-repl-previous-prompt-or-run}
+@item @kbd{C-c C-n} 
+@tab @code{racket-repl-next-prompt-or-run}
+@item @kbd{C-c C-o} 
+@tab @code{racket-repl-delete-output}
 @item @kbd{C-c C-e f} 
 @tab @ref{racket-expand-file}
+@item @kbd{C-c C-e x} 
+@tab @ref{racket-expand-definition}
 @item @kbd{C-c C-e e} 
 @tab @ref{racket-expand-last-sexp}
 @item @kbd{C-c C-e r} 
 @tab @ref{racket-expand-region}
-@item @kbd{)} or @kbd{]} or @kbd{@}} 
-@tab @ref{racket-insert-closing}
-@item @kbd{C-M-y} 
-@tab @ref{racket-insert-lambda}
+@item @kbd{C-c C-d} 
+@tab @ref{racket-repl-documentation}
+@item @kbd{C-c C-.} 
+@tab @ref{racket-repl-describe}
+@item @kbd{C-c C-s} 
+@tab @ref{racket-describe-search}
+@item @kbd{C-c C-z} 
+@tab @code{racket-repl-switch-to-edit}
 @item @kbd{C-c C-l} 
 @tab @ref{racket-logger}
 @item @kbd{C-c C-c} 
 @tab @code{racket-repl-break}
-@item @kbd{C-c C-u} 
-@tab @code{racket-repl-clear-input}
-@item @kbd{C-c C-o} 
-@tab @code{racket-repl-delete-output}
-@item @kbd{C-c C-.} 
-@tab @ref{racket-repl-describe}
-@item @kbd{C-c C-d} 
-@tab @ref{racket-repl-documentation}
 @item @kbd{C-c C-\} 
 @tab @code{racket-repl-exit}
-@item @kbd{M-n} 
-@tab @code{racket-repl-next-input}
-@item @kbd{C-c C-n} 
-@tab @code{racket-repl-next-prompt-or-run}
-@item @kbd{M-p} 
-@tab @code{racket-repl-previous-input}
-@item @kbd{C-c C-p} 
-@tab @code{racket-repl-previous-prompt-or-run}
-@item @kbd{RET} 
-@tab @code{racket-repl-submit}
-@item @kbd{C-c C-z} 
-@tab @code{racket-repl-switch-to-edit}
+@item @kbd{)} or @kbd{]} or @kbd{@}} 
+@tab @ref{racket-insert-closing}
 @end multitable
 
 
@@ -2525,10 +2542,10 @@ Major mode for results of @ref{racket-profile}.
 @tab @code{quit-window}
 @item @kbd{g} 
 @tab @code{racket-profile-refresh}
-@item @kbd{f} 
-@tab @code{racket-profile-show-non-project}
 @item @kbd{z} 
 @tab @code{racket-profile-show-zero}
+@item @kbd{f} 
+@tab @code{racket-profile-show-non-project}
 @item @kbd{.} or @kbd{RET} 
 @tab @code{racket-profile-visit}
 @end multitable
@@ -2564,16 +2581,16 @@ For more information see:
 @multitable {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} 
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 @item Key
 @tab Binding
-@item @kbd{g} 
-@tab @code{racket-logger-clear}
-@item @kbd{n} 
-@tab @code{racket-logger-next-item}
-@item @kbd{p} 
-@tab @code{racket-logger-previous-item}
 @item @kbd{l} 
 @tab @code{racket-logger-topic-level}
 @item @kbd{w} 
 @tab @code{toggle-truncate-lines}
+@item @kbd{n} 
+@tab @code{racket-logger-next-item}
+@item @kbd{p} 
+@tab @code{racket-logger-previous-item}
+@item @kbd{g} 
+@tab @code{racket-logger-clear}
 @end multitable
 
 
@@ -2583,96 +2600,6 @@ In addition to any hooks its parent mode 
@code{special-mode} might have
 run, this mode runs the hook @code{racket-logger-mode-hook}, as the final
 or penultimate step during initialization.
 
-@node racket-debug-mode
-@subsection racket-debug-mode
-
-@kbd{M-x} @code{racket-debug-mode}
-
-Minor mode for debug breaks.
-
-This feature is @strong{@strong{EXPERIMENTAL}}!!! It is likely to have
-significant limitations and bugs. You are welcome to open an
-issue to provide feedback. Please understand that this feature
-might never be improved -- it might even be removed someday if it
-turns out to have too little value and/or too much cost.
-
-How to debug:
-
-@enumerate
-@item
-``Instrument'' code for step debugging.
-
-Use two @kbd{C-u} command prefixes for either
-@ref{racket-run} or @ref{racket-run-module-at-point}.
-
-The file will be instrumented for step debugging before it is
-run. Any imported files are also instrumented if they are in
-the variable @ref{racket-debuggable-files}.
-
-The run will break at the first breakable position.
-
-Tip: After you run to completion and return to a normal
-REPL prompt, the code remains instrumented. You may enter
-expressions that evaluate instrumented code and it will
-break so you can step debug again.
-
-@item
-When a break occurs, the @ref{racket-repl-mode} prompt changes. In
-this debug REPL, local variables are available for you to use
-and even to @code{set!}.
-
-Also, in the @ref{racket-mode} buffer where the break is located,
-@ref{racket-debug-mode} is enabled. This minor mode makes the
-buffer read-only, provides visual feedback -- about the break
-position, local variable values, and result values -- and
-provides shortcut keys:
-@end enumerate
-
-@multitable {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} 
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
-@item Key
-@tab Binding
-@item @kbd{c} 
-@tab @code{racket-debug-continue}
-@item @kbd{g} 
-@tab @code{racket-debug-go}
-@item @kbd{?} 
-@tab @code{racket-debug-help}
-@item @kbd{n} 
-@tab @code{racket-debug-next-breakable}
-@item @kbd{N} 
-@tab @code{racket-debug-next-breakpoint}
-@item @kbd{p} 
-@tab @code{racket-debug-prev-breakable}
-@item @kbd{P} 
-@tab @code{racket-debug-prev-breakpoint}
-@item @kbd{h} 
-@tab @code{racket-debug-run-to-here}
-@item @kbd{SPC} 
-@tab @code{racket-debug-step}
-@item @kbd{u} 
-@tab @code{racket-debug-step-out}
-@item @kbd{o} 
-@tab @code{racket-debug-step-over}
-@item @kbd{!} 
-@tab @ref{racket-debug-toggle-breakpoint}
-@end multitable
-
-
-
-This is a minor mode.  If called interactively, toggle the @code{Racket-Debug
-mode} mode.  If the prefix argument is positive, enable the mode, and if
-it is zero or negative, disable the mode.
-
-If called from Lisp, toggle the mode if ARG is @code{toggle}.  Enable the
-mode if ARG is nil, omitted, or is a positive number.  Disable the mode
-if ARG is a negative number.
-
-To check whether the minor mode is enabled in the current buffer,
-evaluate the variable @ref{racket-debug-mode}.
-
-The mode's hook is called both when the mode is enabled and when it is
-disabled.
-
 @node racket-repl-clear
 @subsection racket-repl-clear
 
@@ -2842,14 +2769,14 @@ Used by the commands @ref{racket-expand-file},
 @multitable 
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
 @item Key
 @tab Binding
+@item @kbd{RET} 
+@tab @code{racket-stepper-step}
 @item @kbd{n} or @kbd{j} 
 @tab @code{racket-stepper-next-item}
 @item @kbd{p} or @kbd{k} 
 @tab @code{racket-stepper-previous-item}
 @item @kbd{g} 
 @tab @code{racket-stepper-refresh}
-@item @kbd{RET} 
-@tab @code{racket-stepper-step}
 @end multitable
 
 
@@ -3013,66 +2940,293 @@ If the package is available from a catalog, additional 
details
 will be shown, assuming you have run the command
 @ref{racket-package-refresh}.
 
-@node Other
-@section Other
+@node Debug
+@section Debug
 
 @menu
-* racket-debug-toggle-breakpoint::
-* racket-mode-start-faster::
-* racket-mode-start-slower::
+* racket-debug-mode::
+* racket-debug-step::
+* racket-debug-step-over::
+* racket-debug-step-out::
+* racket-debug-forward-breakable::
+* racket-debug-backward-breakable::
+* racket-debug-run-to-here::
+* racket-debug-set-break-expression::
+* racket-debug-clear-break-expression::
+* racket-debug-toggle-break-expression::
+* racket-debug-forward-break-expression::
+* racket-debug-backward-break-expression::
+* racket-debug-set-local::
+* racket-debug-continue::
+* racket-debug-go::
 @end menu
 
-@node racket-debug-toggle-breakpoint
-@subsection racket-debug-toggle-breakpoint
-
-@kbd{M-x} @code{racket-debug-toggle-breakpoint}
+@node racket-debug-mode
+@subsection racket-debug-mode
 
-Add or remove a breakpoint.
+@kbd{M-x} @code{racket-debug-mode}
 
-Each breakpoint has a condition and a list of actions.
+Minor mode enabled during step debugging breaks.
 
-The condition is a Racket expression that is evaluated in a
-context where local variables exist. Examples:
+How to step debug:
 
-@itemize
+@enumerate
 @item
-``#t'' means break always.
+``Instrument'' code for step debugging.
+
+Use two @kbd{C-u} command prefixes -- for either
+@ref{racket-run} or @ref{racket-run-module-at-point} -- to instrument
+and run the file with step debugging.
+
+Imported files are also instrumented if they are in the
+variable @ref{racket-debuggable-files}.
+
+Execution will pause at the first breakable position.
 
 @item
-If the code around the breakpoint is something like
-``(for ([n 100]) _)'', then a condition like
-``(zero? (modulo n 10))'' is every 10 times through the
-loop.
-@end itemize
+Whenever a break occurs:
+
+In the @ref{racket-mode} buffer where the break is located,
+@ref{racket-debug-mode} is enabled. This minor mode makes the buffer
+read-only, provides visual feedback -- about the break position,
+local variable values, and result values -- and provides shortcut
+keys:
+@end enumerate
+
+@multitable {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} 
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
+@item Key
+@tab Binding
+@item @kbd{SPC} 
+@tab @ref{racket-debug-step}
+@item @kbd{o} 
+@tab @ref{racket-debug-step-over}
+@item @kbd{u} 
+@tab @ref{racket-debug-step-out}
+@item @kbd{n} 
+@tab @ref{racket-debug-forward-breakable}
+@item @kbd{p} 
+@tab @ref{racket-debug-backward-breakable}
+@item @kbd{h} 
+@tab @ref{racket-debug-run-to-here}
+@item @kbd{!} 
+@tab @ref{racket-debug-toggle-break-expression}
+@item @kbd{N} 
+@tab @ref{racket-debug-forward-break-expression}
+@item @kbd{P} 
+@tab @ref{racket-debug-backward-break-expression}
+@item @kbd{=} 
+@tab @ref{racket-debug-set-local}
+@item @kbd{c} 
+@tab @ref{racket-debug-continue}
+@item @kbd{g} 
+@tab @ref{racket-debug-go}
+@item @kbd{?} 
+@tab @code{racket-debug-help}
+@end multitable
+
+
+
+Tip: After your program runs to completion and returns to a REPL prompt,
+the code remains instrumented. As a result, in the REPL if you enter
+expressions that evaluate instrumented code, you can debug those, too.
+
+This is a minor mode.  If called interactively, toggle the @code{Racket-Debug
+mode} mode.  If the prefix argument is positive, enable the mode, and if
+it is zero or negative, disable the mode.
+
+If called from Lisp, toggle the mode if ARG is @code{toggle}.  Enable the
+mode if ARG is nil, omitted, or is a positive number.  Disable the mode
+if ARG is a negative number.
+
+To check whether the minor mode is enabled in the current buffer,
+evaluate the variable @ref{racket-debug-mode}.
+
+The mode's hook is called both when the mode is enabled and when it is
+disabled.
+
+@node racket-debug-step
+@subsection racket-debug-step
+
+@kbd{SPC} 
+
+Step to next breakable position.
+
+With @kbd{C-u} substitute values.
+
+@node racket-debug-step-over
+@subsection racket-debug-step-over
+
+@kbd{o} 
+
+Step over next expression.
+
+With @kbd{C-u} , substitute values.
+
+@node racket-debug-step-out
+@subsection racket-debug-step-out
+
+@kbd{u} 
+
+Step out.
+
+With @kbd{C-u} , substitute values.
+
+@node racket-debug-forward-breakable
+@subsection racket-debug-forward-breakable
+
+@kbd{n} 
+
+Move to next breakable position in current buffer.
+
+Useful followed by commands like @ref{racket-debug-run-to-here} or
+@ref{racket-debug-set-break-expression}.
+
+@node racket-debug-backward-breakable
+@subsection racket-debug-backward-breakable
+
+@kbd{p} 
 
-Actions is a list of symbols; you may specify one or more. The
-action symbols are:
+Move to previous breakable position in current buffer.
+
+Useful followed by commands like @ref{racket-debug-run-to-here} or
+@ref{racket-debug-set-break-expression}.
+
+@node racket-debug-run-to-here
+@subsection racket-debug-run-to-here
+
+@kbd{h} 
+
+Run to point.
+
+Equivalent to adding a temporary unconditional break at point,
+followed by @ref{racket-debug-continue}.
+
+With @kbd{C-u} , substitute values.
+
+@node racket-debug-set-break-expression
+@subsection racket-debug-set-break-expression
+
+@kbd{M-x} @code{racket-debug-set-break-expression}
+
+Set a break expression at a breakable position.
+
+Break expressions encapsulate a range of traditional debugger features
+like conditional breakpoints and watchpoints.
+
+Each break expression consists of a Racket expression, which will be
+evaluated in a context where local variables exist.
+
+Unless the expression evaluates to Racket false or void, execution will
+break there. In other words, this is a ``conditional breakpoint''.
+
+In addition, the expression may invoke @code{#%dump}, which displays
+information about all locals (and for after-breaks, the result values)
+to both the REPL and the racket-mode-debugger logger topic. In other
+words, this is a ``watchpoint''. Although @ref{racket-debug-mode} already
+shows these values @emph{in situ} when at a break, this may be useful if you
+want a history.
+
+For example, if the code around the point is something like
+@code{(for ([n 100]) ___)}, then:
 
 @itemize
 @item
-``break'' causes a break, enabling @ref{racket-debug-mode}.
+@code{#t} means break always.
 
 @item
-``log'' and ``print'' display information about local
-variables to the logger or REPL output, respectively.
-Although @ref{racket-debug-mode} already shows these values ``in
-situ'' when you reach a break, this may be useful if you want
-a history. Specifying ``log'' or ``print'', but not
-``break'', is equivalent to what many debuggers call a
-watchpoint instead of a breakpoint: Output some information
-and automatically resume.
+@code{(zero? (modulo n 10))} breaks every 10 times through the loop.
+
+@item
+@code{(when (even? n) (#%dump))} dumps watch information every other time
+through the loop, but never breaks.
 @end itemize
 
-Note: Although @ref{racket-debug-mode} provides a convenient
-keybinding, you may invoke this command anytime using M-x.
+The expression may consist of any Racket sub-expressions that evaluate
+without error in that local context.
+
+Each break expression is displayed using the customization variables
+@code{racket-debug-break-expression-string} and
+@code{racket-debug-break-expression-face}.
 
 Note: If you're warned that point isn't known to be a breakable
 position, that might be because it truly isn't, or, just because
-you are not in @ref{racket-debug-mode} and the breakable positions
-aren't yet known. Worst case, if you set a breakpoint someplace
-that is not breakable, it is ignored. With a few exceptions --
-such as close paren positions that are tail calls -- most open
-parens and close parens are breakble positions.
+@ref{racket-debug-mode} is inactive therefore the breakable positions aren
+uknown. Worst case, if you set a break expression someplace that is not
+breakable, it is ignored. With a few exceptions -- such as close paren
+positions that are tail calls -- most open parens and close parens are
+breakble positions in s-expression languages. See the commands
+@ref{racket-debug-forward-breakable} and @ref{racket-debug-backward-breakable}.
+
+@node racket-debug-clear-break-expression
+@subsection racket-debug-clear-break-expression
+
+@kbd{M-x} @code{racket-debug-clear-break-expression}
+
+When a break expression exists at point, clear it and return true.
+
+@node racket-debug-toggle-break-expression
+@subsection racket-debug-toggle-break-expression
+
+@kbd{!} 
+
+Set or clear a break expression.
+
+See @ref{racket-debug-clear-break-expression} or
+@ref{racket-debug-set-break-expression}.
+
+@node racket-debug-forward-break-expression
+@subsection racket-debug-forward-break-expression
+
+@kbd{N} 
+
+Move to next @ref{racket-debug-set-break-expression} location.
+
+@node racket-debug-backward-break-expression
+@subsection racket-debug-backward-break-expression
+
+@kbd{P} 
+
+Move to previous @ref{racket-debug-set-break-expression} location.
+
+@node racket-debug-set-local
+@subsection racket-debug-set-local
+
+@kbd{=} 
+
+Set local variable to new value.
+
+@node racket-debug-continue
+@subsection racket-debug-continue
+
+@kbd{c} 
+
+Continue, utilizing break expressions.
+
+Execution pauses at any position whose break expression evaluates to a
+true, non-void value.
+
+With @kbd{C-u} , substitute values.
+
+@node racket-debug-go
+@subsection racket-debug-go
+
+@kbd{g} 
+
+Go unconditionally, ignoring all break expressions.
+
+Similar to continuing the program normally, without stepping or
+evaluating any break expressions -- although code annotated for
+debugging runs more slowly.
+
+With @kbd{C-u} , substitute values.
+
+@node Other
+@section Other
+
+@menu
+* racket-mode-start-faster::
+* racket-mode-start-slower::
+@end menu
 
 @node racket-mode-start-faster
 @subsection racket-mode-start-faster
@@ -3307,7 +3461,7 @@ This has a visible effect only when there is @emph{not} 
also a
 
 How much documentation to show via @code{eldoc}.
 
-Used by @code{nil} and @code{nil}.
+Used by @code{racket-xp-eldoc-point-or-sexp-head}.
 
 @itemize
 @item
@@ -3769,14 +3923,18 @@ need to use @code{racket-refresh-sexp-comment-faces}.
 @node racket-debuggable-files
 @subsection racket-debuggable-files
 
-Used to tell @ref{racket-run} what files may be instrumented for debugging.
+Used specify what files to instrument for debugging.
 
-This isn't yet a defcustom becuase the debugger status is still
-``experimental''.
+The value must be either:
 
-Must be either a list of file name strings, or, a function that
-takes the name of the file being run and returns a list of file
-names.
+@itemize
+@item
+A list of file name strings.
+
+@item
+A function that, given the name of the file being run, returns
+a list of file name strings.
+@end itemize
 
 Each file name in the list is made absolute using
 @code{expand-file-name} with respect to the file being run and given
diff --git a/racket-custom.el b/racket-custom.el
index b2e0f544947..d07172f17ac 100644
--- a/racket-custom.el
+++ b/racket-custom.el
@@ -637,6 +637,40 @@ ignore POS. Examples: `racket-show-echo-area' and
                                      (natnum :tag "natural number"))
                                (sexp :tag "sexp")))))))
 
+(defcustom racket-debuggable-files 'racket-same-directory-files
+  "Used specify what files to instrument for debugging.
+
+The value must be either:
+
+- A list of file name strings.
+
+- A function that, given the name of the file being run, returns
+  a list of file name strings.
+
+Each file name in the list is made absolute using
+`expand-file-name' with respect to the file being run and given
+to `racket-file-name-front-to-back'."
+  :type '(choice
+          (function :tag "Function")
+          (repeat :tag "Files" file))
+  :risky t)
+
+(defun racket--displayable-string (str fallback)
+  "Return STR if a font seems able to display it, else FALLBACK."
+  (if (seq-every-p (lambda (c)
+                     (pcase (char-displayable-p c)
+                       ((or 'nil 'unicode) nil)
+                       (v v)))
+                   str)
+      str
+    fallback))
+
+(defcustom racket-debug-break-expression-string
+  (racket--displayable-string "●" "!")
+  "String used in overlays for `racket-debug-set-break-expression'."
+  :type 'string
+  :safe #'stringp)
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; racket-faces group
 
@@ -784,21 +818,21 @@ values, including symbols quoted using \"syntax\" or
   "Face for `racket-logger-mode' debug level.")
 
 (defface-racket racket-debug-break-face
-  '((t (:background "red")))
+  '((t (:underline (color: "dark orange" :style line))))
   "Face for `racket-debug-mode' break position.")
 
-(defface-racket racket-debug-breakpoint-face
+(defface-racket racket-debug-break-span-face
+  '((t (:inherit highlight)))
+  "Face for `racket-debug-mode' break spans.")
+
+(defface-racket racket-debug-break-expression-face
   '((t (:foreground "red" :weight bold)))
-  "Face for `racket-debug-mode' breakpoint overlays.")
+  "Face for `racket-debug-set-break-expression' overlays.")
 
 (defface-racket racket-debug-locals-face
-  '((t (:inherit font-lock-constant-face :box (:line-width -1) :slant italic)))
+  '((t (:inherit tooltip :box (:line-width -1) :slant italic)))
   "Face for `racket-debug-mode' local variables.")
 
-(defface-racket racket-debug-result-face
-  '((t (:inherit font-lock-constant-face :box (:line-width -1) :slant italic 
:weight bold)))
-  "Face for `racket-debug-mode' result values.")
-
 (defface-racket racket-doc-link-face
   '((t (:underline (:color "gray" :style line))))
   "Face `racket-describe-mode' uses for links within documentation.
diff --git a/racket-debug.el b/racket-debug.el
index c7b5dac5b38..991e1578730 100644
--- a/racket-debug.el
+++ b/racket-debug.el
@@ -1,6 +1,6 @@
 ;;; racket-debug.el -*- lexical-binding: t; -*-
 
-;; Copyright (c) 2018-2022 by Greg Hendershott.
+;; Copyright (c) 2018-2025 by Greg Hendershott.
 
 ;; Author: Greg Hendershott
 ;; URL: https://github.com/greghendershott/racket-mode
@@ -10,11 +10,12 @@
 (require 'racket-back-end)
 (require 'racket-repl)
 (require 'easymenu)
-(require 'cl-lib)
 (require 'rx)
+(require 'seq)
 
 (defun racket-same-directory-files (file)
   "A suitable value for the variable `racket-debuggable-files'.
+
 Return FILE plus absolute paths for all Racket files in the same
 directory as FILE."
   (cons file
@@ -23,29 +24,16 @@ directory as FILE."
                          (rx "." (or "rkt" "ss" "scm" "scrbl") eos)
                          nil)))
 
-(defvar racket-debuggable-files #'racket-same-directory-files
-  "Used to tell `racket-run' what files may be instrumented for debugging.
-
-This isn't yet a defcustom becuase the debugger status is still
-\"experimental\".
-
-Must be either a list of file name strings, or, a function that
-takes the name of the file being run and returns a list of file
-names.
-
-Each file name in the list is made absolute using
-`expand-file-name' with respect to the file being run and given
-to `racket-file-name-front-to-back'.")
-
 (defun racket--debuggable-files (file-to-run)
   "Do the work described in doc str for variable `racket-debuggable-files'."
-  (mapcar (lambda (file)
-            (racket-file-name-front-to-back
-             (expand-file-name file file-to-run)))
-          (if (functionp racket-debuggable-files)
-              (funcall racket-debuggable-files file-to-run)
-            racket-debuggable-files)))
-
+  (seq-map (lambda (file)
+             (racket-file-name-front-to-back
+              (expand-file-name file file-to-run)))
+           (if (functionp racket-debuggable-files)
+               (funcall racket-debuggable-files file-to-run)
+             racket-debuggable-files)))
+
+(defvar racket--debug-break-span nil)
 (defvar racket--debug-breakable-positions nil)
 (defvar racket--debug-break-locals nil)
 (defvar racket--debug-break-info nil)
@@ -53,38 +41,60 @@ to `racket-file-name-front-to-back'.")
 ;;              (U (list 'before)
 ;;                 (list 'after string-of-racket-write-values))))
 
-(defvar racket--debug-breakpoints nil
-  "A list of overlays for breakpoints the user has set.")
+(defvar racket--debug-break-expression-overlays nil
+  "A list of overlays for break expressions set by the user.
+
+We need this variable because we support debugging across
+multiple source files so these overlays may exist among various
+buffers.")
 
 ;;;###autoload
-(defun racket--debug-on-break (response)
-  (pcase response
-    (`((,src . ,pos) ,breakable-positions ,locals ,vals)
+(defun racket--debug-on-break (info)
+  (pcase info
+    (`((,src ,pos ,beg ,end) ,breakable-positions ,locals ,vals)
      (let ((src (racket-file-name-back-to-front src)))
-       (pcase (find-buffer-visiting src)
-         (`nil (other-window 1) (find-file src))
-         (buf  (pop-to-buffer buf)))
+       (if-let (buf (find-buffer-visiting src))
+           (if (eq major-mode 'racket-repl-mode)
+               (pop-to-buffer buf) ;other window
+             (pop-to-buffer-same-window buf))
+         (if (eq major-mode 'racket-repl-mode)
+             (find-file-other-window src)
+           (find-file src)))
        (goto-char pos)
+       (setq racket--debug-break-span (cons beg end))
        (pcase vals
-         (`(,_id before)          (message "Break before expression"))
-         (`(,_id after (,_ . ,s)) (message "Break after expression: (values %s"
-                                           (substring s 1))))
+         (`(,_id before)
+          (message "Break before expression; press ? for help"))
+         (`(,_id after (,_ . ,s))
+          (message "Break after expression: (values %s; press ? for help"
+                   (substring s 1))))
        (setq racket--debug-breakable-positions
-             (mapcar (lambda (path+positions)
-                       (cons (racket-file-name-back-to-front (car 
path+positions))
-                             (sort (cdr path+positions) #'<)))
-                     breakable-positions))
+             (seq-map (lambda (path+positions)
+                        (cons (racket-file-name-back-to-front (car 
path+positions))
+                              (sort (cdr path+positions) #'<)))
+                      breakable-positions))
        (setq racket--debug-break-locals locals)
        (setq racket--debug-break-info vals)
-       (racket-debug-mode 1)))))
+       (racket-debug-mode 1)))
+    (v (error "racket--debug-on-break: unexpected `info`: %S" v))))
 
-(defun racket--debug-resume (next-break value-prompt-p)
+(defun racket--debug-resume (next-break value-prompt-p &optional extra-exprs)
   (unless racket--debug-break-info (user-error "Not debugging"))
-  (let ((info (if value-prompt-p
-                  (racket--debug-prompt-for-new-values)
-                racket--debug-break-info)))
+  (racket--debug-validate-break-expressions)
+  (let* ((info (if value-prompt-p
+                   (racket--debug-prompt-for-new-values)
+                 racket--debug-break-info))
+         (exprs (seq-map (lambda (o)
+                            (list (with-current-buffer (overlay-buffer o)
+                                    (racket-file-name-front-to-back
+                                     (racket--buffer-file-name)))
+                                  (overlay-start o)
+                                  (or (overlay-get o 'racket-break-expression)
+                                      "#t")))
+                          racket--debug-break-expression-overlays))
+         (exprs (append extra-exprs exprs)))
     (racket--cmd/async (racket--repl-session-id)
-                       `(debug-resume (,next-break ,info))))
+                       `(debug-resume ((,next-break . ,exprs) ,info))))
   (racket-debug-mode -1)
   (setq racket--debug-breakable-positions nil)
   (setq racket--debug-break-locals nil)
@@ -103,69 +113,74 @@ to `racket-file-name-front-to-back'.")
     (v v)))
 
 (defun racket-debug-step (&optional prefix)
-  "Step to next breakable position. With \\[universal-argument] substitute 
values."
+  "Step to next breakable position.
+
+With \\[universal-argument] substitute values."
   (interactive "P")
   (racket--debug-resume 'all prefix))
 
 (defun racket-debug-step-over (&optional prefix)
-  "Step over next expression. With \\[universal-argument], substitute values."
+  "Step over next expression.
+
+With \\[universal-argument], substitute values."
   (interactive "P")
   (racket--debug-resume 'over prefix))
 
 (defun racket-debug-step-out (&optional prefix)
-  "Step out. With \\[universal-argument], substitute values."
+  "Step out.
+
+With \\[universal-argument], substitute values."
   (interactive "P")
   (racket--debug-resume 'out prefix))
 
 (defun racket-debug-continue (&optional prefix)
-  "Continue to next breakpoint. With \\[universal-argument], substitute 
values."
+  "Continue, utilizing break expressions.
+
+Execution pauses at any position whose break expression evaluates to a
+true, non-void value.
+
+With \\[universal-argument], substitute values."
   (interactive "P")
-  (racket--debug-validate-breakpoints)
-  (racket--debug-resume (seq-map (lambda (o)
-                                   (list (with-current-buffer (overlay-buffer 
o)
-                                          (racket-file-name-front-to-back
-                                           (racket--buffer-file-name)))
-                                         (overlay-start o)
-                                         (or (overlay-get o 
'racket-breakpoint-condition)
-                                             "#t")
-                                         (or (overlay-get o 
'racket-breakpoint-actions)
-                                             "(break)")))
-                                 racket--debug-breakpoints)
-                        prefix))
-
-(defun racket--debug-validate-breakpoints ()
-  "Remove invalid overlays from the list."
-  (setq racket--debug-breakpoints
-        (seq-filter (lambda (o)
-                      (if (bufferp (overlay-buffer o))
-                          t
-                        (delete-overlay o)
-                        nil))
-                    racket--debug-breakpoints)))
+  (racket--debug-resume 'some prefix))
+
+(defun racket-debug-run-to-here (&optional prefix)
+  "Run to point.
+
+Equivalent to adding a temporary unconditional break at point,
+followed by `racket-debug-continue'.
+
+With \\[universal-argument], substitute values."
+  (interactive)
+  (let ((extra (list (list (racket-file-name-front-to-back
+                            (racket--buffer-file-name))
+                           (point)
+                           "#t"))))
+    (racket--debug-resume 'some prefix extra)))
 
 (defun racket-debug-go (&optional prefix)
-  "Go, don't break anymore. With \\[universal-argument], substitute values."
+  "Go unconditionally, ignoring all break expressions.
+
+Similar to continuing the program normally, without stepping or
+evaluating any break expressions -- although code annotated for
+debugging runs more slowly.
+
+With \\[universal-argument], substitute values."
   (interactive "P")
   (racket--debug-resume 'none prefix))
 
-(defun racket-debug-run-to-here (&optional prefix)
-  "Resume until point (if possible). With \\[universal-argument], substitute 
values."
-  (interactive)
-  ;; i.e. Act as if the only breakpoint is here.
-  (racket--debug-resume (list (list (racket-file-name-front-to-back
-                                     (racket--buffer-file-name))
-                                    (point)
-                                    "#t"
-                                    "(break)"))
-                        prefix))
-
-(defun racket-debug-next-breakable ()
-  "Move point to next breakable position."
+(defun racket-debug-forward-breakable ()
+  "Move to next breakable position in current buffer.
+
+Useful followed by commands like `racket-debug-run-to-here' or
+`racket-debug-set-break-expression'."
   (interactive)
   (racket--debug-goto-breakable t))
 
-(defun racket-debug-prev-breakable ()
-  "Move point to previous breakable position."
+(defun racket-debug-backward-breakable ()
+  "Move to previous breakable position in current buffer.
+
+Useful followed by commands like `racket-debug-run-to-here' or
+`racket-debug-set-break-expression'."
   (interactive)
   (racket--debug-goto-breakable nil))
 
@@ -174,122 +189,227 @@ to `racket-file-name-front-to-back'.")
     (`(,_src . ,ps)
      (let ((ps   (if forwardp ps (reverse ps)))
            (pred (apply-partially (if forwardp #'< #'>) (point))))
-       (goto-char (or (cl-find-if pred ps) (car ps)))))
+       (goto-char (or (seq-find pred ps) (car ps)))))
     (_ (user-error "No breakable positions in this buffer"))))
 
-(defun racket--debug-breakpoint-overlay-equal (o)
-  (and (equal (overlay-buffer o) (current-buffer))
-       (equal (overlay-start o)  (point))))
+(defun racket-debug-set-local ()
+  "Set local variable to new value."
+  (interactive)
+  (let* ((index 0)
+         (candidates
+          (seq-filter
+           #'identity
+           (seq-map
+            (pcase-lambda (`(,src ,pos ,_span ,sym ,val))
+              (when (equal src (racket-file-name-back-to-front
+                                (racket--buffer-file-name)))
+                (concat
+                 (propertize (format "%s" sym)
+                             'racket-affix (list val (format "%s::%s" src pos))
+                             'racket-sort (prog1 index (setq index (1+ 
index))))
+                 ;; `completing-read' strips props so return this via
+                 ;; appended invisible text
+                 (propertize (format "\t%S" pos)
+                             'display ""))))
+            racket--debug-break-locals)))
+         (affix (racket--make-affix [[8 font-lock-variable-name]
+                                     [8 racket-debug-locals-face]
+                                     0]))
+         (display-sort (lambda (strs)
+                         (seq-sort (lambda (a b)
+                                     (< (get-text-property 0 'racket-sort a)
+                                        (get-text-property 0 'racket-sort b)))
+                                   strs)))
+         (metadata `((category . racket-debug-local)
+                     (affixation-function . ,affix)
+                     (display-sort-function . ,display-sort)))
+         (collection (racket--completion-table candidates metadata))
+         (str (completing-read "Local binding to change: " collection nil t))
+         (pos (progn
+                (string-match (rx bos (+? any) "\t" (group-n 1 (+? any)) eos)
+                              str)
+                (read (match-string 1 str))))
+         (o (seq-find (lambda (o)
+                        (eq (overlay-get o 'name) 'racket-debug-local))
+                      (overlays-at pos))))
+    (unless o
+      (error "No local variable found"))
+    (let ((val (read-from-minibuffer "New value: "
+                                     (overlay-get o 
'racket-debug-local-value))))
+      (when (and val (not (equal val "")))
+        (overlay-put o
+                     'after-string
+                     (propertize val 'face racket-debug-locals-face))
+        (overlay-put o
+                     'racket-debug-local-value
+                     val)
+        (let* ((break-id (car racket--debug-break-info))
+               (source (racket-file-name-front-to-back 
(racket--buffer-file-name)))
+               (pos (overlay-start o))
+               (info (list break-id source pos val)))
+          (racket--cmd/async (racket--repl-session-id)
+                             `(debug-set-local ,info)))))))
 
-(defvar racket-debug-breakpoint-conditions '("#t"))
-(defvar racket-debug-breakpoint-actions '("(break)" "(print)" "(log)"))
-(defun racket-debug-toggle-breakpoint ()
-  "Add or remove a breakpoint.
+(defun racket-debug-disable ()
+  "Disable `racket-debug-mode' and reset related variables."
+  (interactive)
+  (when (racket--cmd-open-p) ;otherwise no need
+    (racket--cmd/async (racket--repl-session-id) `(debug-disable)))
+  (racket-debug-mode -1)
+  (setq racket--debug-breakable-positions nil)
+  (setq racket--debug-break-locals nil)
+  (setq racket--debug-break-info nil))
+
+(add-hook 'racket--repl-before-run-hook #'racket-debug-disable)
+
+;;; break expression overlays
+
+(defun racket--debug-validate-break-expressions ()
+  "Remove invalid overlays from the list."
+  (setq racket--debug-break-expression-overlays
+        (seq-filter (lambda (o)
+                      (if (bufferp (overlay-buffer o))
+                          t
+                        (delete-overlay o)
+                        nil))
+                    racket--debug-break-expression-overlays)))
+
+(defun racket-debug-clear-break-expression ()
+  "When a break expression exists at point, clear it and return true."
+  ;; Note: Actually this defensively deletes multiple overlays at
+  ;; point, in case we somehow mistakenly created more than one there.
+  ;; But that detail is N/A for user doc string.
+  (interactive)
+  (let ((anyp nil)
+        (os (overlays-at (point))))
+    (dolist (o os)
+      (when (eq (overlay-get o 'name) 'racket-break-expression)
+        (delete-overlay o)
+        (setq racket--debug-break-expression-overlays
+              (remove o racket--debug-break-expression-overlays))
+        (setq anyp t)))
+    anyp))
+
+(defun racket-debug-set-break-expression (expression)
+  "Set a break expression at a breakable position.
+
+Break expressions encapsulate a range of traditional debugger features
+like conditional breakpoints and watchpoints.
+
+Each break expression consists of a Racket expression, which will be
+evaluated in a context where local variables exist.
 
-Each breakpoint has a condition and a list of actions.
+Unless the expression evaluates to Racket false or void, execution will
+break there. In other words, this is a \"conditional breakpoint\".
 
-The condition is a Racket expression that is evaluated in a
-context where local variables exist. Examples:
+In addition, the expression may invoke `#%dump`, which displays
+information about all locals (and for after-breaks, the result values)
+to both the REPL and the racket-mode-debugger logger topic. In other
+words, this is a \"watchpoint\". Although `racket-debug-mode' already
+shows these values /in situ/ when at a break, this may be useful if you
+want a history.
 
-  - \"#t\" means break always.
+For example, if the code around the point is something like
+`(for ([n 100]) ___)`, then:
 
-  - If the code around the breakpoint is something like
-     \"(for ([n 100]) _)\", then a condition like
-     \"(zero? (modulo n 10))\" is every 10 times through the
-     loop.
+  - `#t` means break always.
 
-Actions is a list of symbols; you may specify one or more. The
-action symbols are:
+  - `(zero? (modulo n 10))` breaks every 10 times through the loop.
 
-  - \"break\" causes a break, enabling `racket-debug-mode'.
+  - `(when (even? n) (#%dump))` dumps watch information every other time
+    through the loop, but never breaks.
 
-  - \"log\" and \"print\" display information about local
-    variables to the logger or REPL output, respectively.
-    Although `racket-debug-mode' already shows these values \"in
-    situ\" when you reach a break, this may be useful if you want
-    a history. Specifying \"log\" or \"print\", but not
-    \"break\", is equivalent to what many debuggers call a
-    watchpoint instead of a breakpoint: Output some information
-    and automatically resume.
+The expression may consist of any Racket sub-expressions that evaluate
+without error in that local context.
 
-Note: Although `racket-debug-mode' provides a convenient
-keybinding, you may invoke this command anytime using M-x.
+Each break expression is displayed using the customization variables
+`racket-debug-break-expression-string' and
+`racket-debug-break-expression-face'.
 
 Note: If you're warned that point isn't known to be a breakable
 position, that might be because it truly isn't, or, just because
-you are not in `racket-debug-mode' and the breakable positions
-aren't yet known. Worst case, if you set a breakpoint someplace
-that is not breakable, it is ignored. With a few exceptions --
-such as close paren positions that are tail calls -- most open
-parens and close parens are breakble positions."
+`racket-debug-mode' is inactive therefore the breakable positions aren
+uknown. Worst case, if you set a break expression someplace that is not
+breakable, it is ignored. With a few exceptions -- such as close paren
+positions that are tail calls -- most open parens and close parens are
+breakble positions in s-expression languages. See the commands
+`racket-debug-forward-breakable' and `racket-debug-backward-breakable'."
+  (interactive
+   (progn
+     (unless (or (pcase (assoc (racket--buffer-file-name)
+                               racket--debug-breakable-positions)
+                   (`(,_src . ,ps) (memq (point) ps)))
+                 (y-or-n-p "Point not known to be a breakable position; set 
anyway "))
+       (keyboard-quit))
+     (list
+      (read-string "Expression [RET for \"#t\"]: "
+                   nil
+                   'racket-debug-break-expressions
+                   "#t"))))
+  (racket-debug-clear-break-expression)
+  (let ((o (make-overlay (point) (1+ (point)) (current-buffer) t nil)))
+    (overlay-put o 'name 'racket-break-expression)
+    (overlay-put o 'before-string (propertize
+                                   racket-debug-break-expression-string
+                                   'face 'racket-debug-break-expression-face
+                                   'help-echo expression))
+    (overlay-put o 'evaporate t)
+    (overlay-put o 'racket-break-expression expression)
+    (push o racket--debug-break-expression-overlays)))
+
+(defun racket-debug-toggle-break-expression ()
+  "Set or clear a break expression.
+
+See `racket-debug-clear-break-expression' or
+`racket-debug-set-break-expression'."
   (interactive)
-  (if-let (o (seq-find #'racket--debug-breakpoint-overlay-equal
-                        racket--debug-breakpoints))
-      (progn
-        (delete-overlay o)
-        (setq racket--debug-breakpoints
-              (seq-remove #'racket--debug-breakpoint-overlay-equal
-                          racket--debug-breakpoints)))
-    (when (or (pcase (assoc (racket--buffer-file-name) 
racket--debug-breakable-positions)
-                (`(,_src . ,ps) (memq (point) ps)))
-              (y-or-n-p "Point not known to be a breakable position; set 
anyway "))
-      (let* ((condition (read-string "Condition expression [RET for \"#t\"]: "
-                                     nil
-                                     'racket-debug-breakpoint-conditions
-                                     "#t"))
-             (actions   (read-string "Actions list [RET for \"(break)\"]: "
-                                     nil
-                                     'racket-debug-breakpoint-actions
-                                     "(break)"))
-             (o (make-overlay (point) (1+ (point)) (current-buffer) t nil)))
-        (overlay-put o 'name 'racket-debug-breakpoint)
-        (overlay-put o 'before-string (propertize
-                                       "⦿"
-                                       'face 'racket-debug-breakpoint-face))
-        (overlay-put o 'evaporate t)
-        (overlay-put o 'racket-breakpoint-condition condition)
-        (overlay-put o 'racket-breakpoint-actions actions)
-        (push o racket--debug-breakpoints)
-        (setq racket-debug-breakpoint-conditions
-              (seq-uniq racket-debug-breakpoint-conditions))
-        (setq racket-debug-breakpoint-actions
-              (seq-uniq racket-debug-breakpoint-actions))))))
-
-(defun racket-debug-next-breakpoint ()
-  "Move point to the next breakpoint in this buffer."
-  (interactive)
-  (racket--goto-breakpoint 'next))
+  (unless (racket-debug-clear-break-expression)
+    (call-interactively #'racket-debug-set-break-expression)))
 
-(defun racket-debug-prev-breakpoint ()
-  "Move point to the previous breakpoint in this buffer."
+(defun racket-debug-forward-break-expression ()
+  "Move to next `racket-debug-set-break-expression' location."
   (interactive)
-  (racket--goto-breakpoint 'previous))
-
-(defun racket--goto-breakpoint (dir)
-  (if-let (p (seq-find (if (eq dir 'next)
-                           (lambda (pos) (< (point) pos))
-                         (lambda (pos) (< pos (point))))
-                       (sort (seq-map #'overlay-start
-                                      (seq-filter (lambda (o)
-                                                    (equal (overlay-buffer o)
-                                                           (current-buffer)))
-                                                  racket--debug-breakpoints))
-                             (if (eq dir 'next)
-                                 #'<
-                               #'>))))
-      (goto-char p)
-    (user-error (format "No %s breakpoint in this buffer" dir))))
+  (racket--debug-goto-break-expression 'forward))
 
-(defun racket-debug-disable ()
+(defun racket-debug-backward-break-expression ()
+  "Move to previous `racket-debug-set-break-expression' location."
   (interactive)
-  (when (racket--cmd-open-p) ;otherwise no need
-    (racket--cmd/async (racket--repl-session-id) `(debug-disable)))
-  (racket-debug-mode -1)
-  (setq racket--debug-breakable-positions nil)
-  (setq racket--debug-break-locals nil)
-  (setq racket--debug-break-info nil))
-
-(add-hook 'racket--repl-before-run-hook #'racket-debug-disable)
+  (racket--debug-goto-break-expression 'backward))
+
+(defun racket--debug-goto-break-expression (dir)
+  "Move among break expression overlays, in `buffer-list' order."
+  (let ((by-buffer (seq-group-by #'overlay-buffer
+                                 racket--debug-break-expression-overlays)))
+    (pcase (seq-some (lambda (buffer)
+                       (when-let (overlays (cdr (assoc buffer by-buffer)))
+                         (let* ((old-pos (if (equal buffer (current-buffer))
+                                             (point)
+                                           (with-current-buffer buffer
+                                             (if (eq dir 'forward)
+                                                 (point-min)
+                                               (point-max)))))
+                                (new-pos (racket--debug-find-point old-pos
+                                                                   overlays
+                                                                   dir)))
+                           (and new-pos
+                                (cons buffer new-pos)))))
+                     (buffer-list))
+      (`(,buffer . ,pos)
+       (pop-to-buffer buffer)
+       (goto-char pos))
+      (_ (user-error "No break expression found %s" dir)))))
+
+(defun racket--debug-find-point (orig-pos overlays dir)
+  (let ((cmp (if (eq dir 'forward) #'< #'>)))
+    (seq-reduce (lambda (pos ov)
+                  (let ((beg (overlay-start ov)))
+                    (if (and (funcall cmp orig-pos beg)
+                             (or (not pos)
+                                 (funcall cmp beg pos)))
+                        beg
+                      pos)))
+                overlays
+                nil)))
 
 (defun racket-debug-help ()
   (interactive)
@@ -298,77 +418,73 @@ parens and close parens are breakble positions."
 (defvar racket--debug-overlays nil)
 
 (define-minor-mode racket-debug-mode
-  "Minor mode for debug breaks.
+  "Minor mode enabled during step debugging breaks.
 
-This feature is **EXPERIMENTAL**!!! It is likely to have
-significant limitations and bugs. You are welcome to open an
-issue to provide feedback. Please understand that this feature
-might never be improved -- it might even be removed someday if it
-turns out to have too little value and/or too much cost.
-
-How to debug:
+How to step debug:
 
 1. \"Instrument\" code for step debugging.
 
-   Use two \\[universal-argument] command prefixes for either
-   `racket-run' or `racket-run-module-at-point'.
-
-   The file will be instrumented for step debugging before it is
-   run. Any imported files are also instrumented if they are in
-   the variable `racket-debuggable-files'.
+   Use two \\[universal-argument] command prefixes -- for either
+   `racket-run' or `racket-run-module-at-point' -- to instrument
+   and run the file with step debugging.
 
-   The run will break at the first breakable position.
+   Imported files are also instrumented if they are in the
+   variable `racket-debuggable-files'.
 
-   Tip: After you run to completion and return to a normal
-   REPL prompt, the code remains instrumented. You may enter
-   expressions that evaluate instrumented code and it will
-   break so you can step debug again.
+   Execution will pause at the first breakable position.
 
-2. When a break occurs, the `racket-repl-mode' prompt changes. In
-   this debug REPL, local variables are available for you to use
-   and even to `set!`.
+2. Whenever a break occurs:
 
-   Also, in the `racket-mode' buffer where the break is located,
-   `racket-debug-mode' is enabled. This minor mode makes the
-   buffer read-only, provides visual feedback -- about the break
-   position, local variable values, and result values -- and
-   provides shortcut keys:
+   In the `racket-mode' buffer where the break is located,
+   `racket-debug-mode' is enabled. This minor mode makes the buffer
+   read-only, provides visual feedback -- about the break position,
+   local variable values, and result values -- and provides shortcut
+   keys:
 
 \\{racket-debug-mode-map}
-"
+
+Tip: After your program runs to completion and returns to a REPL prompt,
+the code remains instrumented. As a result, in the REPL if you enter
+expressions that evaluate instrumented code, you can debug those, too."
   :lighter " RacketDebug"
   :keymap (racket--easy-keymap-define
            '(("SPC" racket-debug-step)
              ("o"   racket-debug-step-over)
              ("u"   racket-debug-step-out)
+             ("n"   racket-debug-forward-breakable)
+             ("p"   racket-debug-backward-breakable)
+             ("h"   racket-debug-run-to-here)
+             ("!"   racket-debug-toggle-break-expression)
+             ("N"   racket-debug-forward-break-expression)
+             ("P"   racket-debug-backward-break-expression)
+             ("="   racket-debug-set-local)
              ("c"   racket-debug-continue)
              ("g"   racket-debug-go)
-             ("n"   racket-debug-next-breakable)
-             ("p"   racket-debug-prev-breakable)
-             ("N"   racket-debug-next-breakpoint)
-             ("P"   racket-debug-prev-breakpoint)
-             ("!"   racket-debug-toggle-breakpoint)
-             ("h"   racket-debug-run-to-here)
              ("?"   racket-debug-help)))
   (racket--assert-edit-mode (lambda () (setq racket-debug-mode nil)))
   (cond
    (racket-debug-mode
+    (racket--debug-make-overlay
+     (car racket--debug-break-span) (cdr racket--debug-break-span)
+     'name 'racket-debug-break-span
+     'priority 90
+     'face racket-debug-break-span-face)
     (racket--debug-make-overlay
      (point) (1+ (point))
+     'name 'racket-debug-break
+     'priority 99
      'face racket-debug-break-face
-     'priority 99)
+     'after-string
+     (when-let (str (cdr (nth 2 racket--debug-break-info)))
+       (propertize (substring str 1 -1)
+                   'face racket-debug-locals-face)))
     (dolist (local racket--debug-break-locals)
       (pcase-let ((`(,_src ,pos ,span ,_name ,val) local))
         (racket--debug-make-overlay
          pos (+ pos span)
+         'name 'racket-debug-local
+         'racket-debug-local-value val
          'after-string (propertize val 'face racket-debug-locals-face))))
-    (pcase racket--debug-break-info
-      (`(,_id after (,_ . ,str))
-       (let ((eol (line-end-position)))
-         (racket--debug-make-overlay
-          (1- eol) eol
-          'after-string (propertize (concat "⇒ (values " (substring str 1))
-                                    'face racket-debug-result-face)))))
     (read-only-mode 1))
    (t
     (read-only-mode -1)
diff --git a/racket-mode.el b/racket-mode.el
index f5b2472038a..b4f14b646f7 100644
--- a/racket-mode.el
+++ b/racket-mode.el
@@ -27,6 +27,7 @@
 (require 'racket-edit)
 (require 'racket-xp)
 (require 'racket-custom)
+(require 'racket-debug)
 (require 'racket-smart-open)
 (require 'racket-imenu)
 (require 'racket-package)
diff --git a/racket/command-server.rkt b/racket/command-server.rkt
index 57e2da7e163..d974e328415 100644
--- a/racket/command-server.rkt
+++ b/racket/command-server.rkt
@@ -172,6 +172,7 @@
     [`(get-uncovered)                  (get-uncovered file)]
     [`(eval ,v)                        (eval-command v)]
     [`(debug-resume ,v)                (debug-resume v)]
+    [`(debug-set-local ,v)             (debug-set-local v)]
     [`(debug-disable)                  (debug-disable)]
     [`(repl-input ,str)                (repl-input str)]
     [`(repl-submit ,str, echo)         (repl-submit str echo)]
diff --git a/racket/debug-annotator.rkt b/racket/debug-annotator.rkt
index adc0bb2d880..e6412a4857b 100644
--- a/racket/debug-annotator.rkt
+++ b/racket/debug-annotator.rkt
@@ -1,11 +1,12 @@
-;; Copyright (c) 2013-2022 by Greg Hendershott.
+;; Copyright (c) 2013-2025 by Greg Hendershott.
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 
 #lang racket/base
 
 (require (for-syntax racket/base)
-         (only-in racket/base [apply plain-apply]) ;;???
-         (prefix-in kernel: syntax/kerncase))
+         (only-in racket/base [apply plain-apply])
+         (prefix-in kernel: syntax/kerncase)
+         (only-in racket/contract -> any/c))
 
 ;; This is like gui-debugger/annotate except:
 ;;
@@ -26,7 +27,8 @@
 (provide annotate-for-single-stepping
          mark-source
          mark-bindings
-         debug-key)
+         debug-key
+         mark/c)
 
 (define (annotate-for-single-stepping stx break? break-before break-after)
   (define (break-wrap debug-info annotated raw is-tail?)
@@ -389,6 +391,12 @@
 
 (define-struct full-mark-struct (module-name source label bindings values))
 
+;; A gui-debugger/marks "mark" is a thunk that returns a
+;; full-mark-struct -- although, like gui-debugger/marks, we don't
+;; provide that struct. Instead the thunk can be passed to various
+;; accessor functions.
+(define mark/c (-> any/c))
+
 ;; debug-key: this key will be used as a key for the continuation marks.
 (define-struct debug-key-struct ())
 (define debug-key (make-debug-key-struct))
diff --git a/racket/debug.rkt b/racket/debug.rkt
index b6f407155bd..dbc920f617a 100644
--- a/racket/debug.rkt
+++ b/racket/debug.rkt
@@ -1,4 +1,4 @@
-;; Copyright (c) 2013-2022 by Greg Hendershott.
+;; Copyright (c) 2013-2025 by Greg Hendershott.
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 
 #lang racket/base
@@ -8,14 +8,14 @@
          racket/format
          racket/list
          racket/match
-         (only-in racket/path path-only)
+         (only-in racket/path
+                  path-only
+                  file-name-from-path)
          racket/set
          syntax/modread
          "debug-annotator.rkt"
          "elisp.rkt"
-         "interaction.rkt"
          "repl-output.rkt"
-         "repl-session.rkt"
          "util.rkt")
 
 (module+ test
@@ -23,153 +23,96 @@
 
 (provide (rename-out [on-break-channel debug-notify-channel])
          debug-resume
+         debug-set-local
          debug-disable
-         make-debug-eval-handler
-         next-break)
+         debug-enable-step
+         make-debug-eval-handler)
 
 (define-logger racket-mode-debugger)
 
-;; A gui-debugger/marks "mark" is a thunk that returns a
-;; full-mark-struct -- although gui-debugger/marks doesn't provide
-;; that struct. Instead the thunk can be passed to various accessor
-;; functions.
-(define mark/c (-> any/c))
-
-;; A "mark-binding" is a list whose first element is syntax of the
-;; identifier, and whose second element is a get/set! procedure.
-(define get/set!/c (case-> (-> any/c)
-                           (-> any/c void)))
-
 (define breakable-positions/c (hash/c path? (set/c #:cmp 'eq pos/c)))
 (define/contract breakable-positions breakable-positions/c (make-hash))
 (define/contract (breakable-position? src pos)
   (-> path? pos/c boolean?)
   (set-member? (hash-ref breakable-positions src (seteq)) pos))
 
-(define/contract (annotate stx #:source [source (syntax-source stx)])
-  (->* (syntax?) (#:source path?) syntax?)
+(define/contract (annotate stx)
+  (-> syntax? syntax?)
+  (define source (syntax-source stx))
   (repl-output-message (format "Debug annotate ~v" source))
   (define-values (annotated breakables)
-    (annotate-for-single-stepping stx break? break-before break-after))
+    (annotate-for-single-stepping stx pause? pause-before pause-after))
   (hash-update! breakable-positions
                 source
                 (λ (s) (set-union s (list->seteq breakables)))
                 (seteq))
   annotated)
 
-;; These contracts are suitable for "edge" with ELisp.
-(define break-point-elisp/c (list/c path-string? pos/c string? string?))
-(define break-when-elisp/c  (or/c 'all 'none (listof break-point-elisp/c)))
-
-;; These contracts are for actual `next-break` value.
-(define break-point/c (list/c path? pos/c any/c (listof symbol?)))
-(define break-when/c  (or/c 'all 'none (listof break-point/c)))
-
-(define (from-elisp-break-when v)
-  (if (list? v)
-      (map from-elisp-break-point v)
-      v))
-
-(define/contract from-elisp-break-point
-  (-> break-point-elisp/c break-point/c)
-  (match-lambda
-    [(list path-str pos condition actions)
-     (list (string->path path-str)
-           pos
-           (read (open-input-string condition))
-           (read (open-input-string actions)))]))
-
-(define/contract next-break
-  (case-> (-> break-when/c)
-          (-> break-when/c void))
-  (let ([v 'none])
+;; A struct hierarchy related to the "next" variable.
+;;
+;; - The "resume" structs are more general, including "over" and "out"
+;;   types, which must be reduced to "next" structs.
+;;
+;; - The "next" structs are more specific, and represent values
+;;   actually permitted in the `next` variable.
+;;
+;; The `caps` field is conditional action points set by the user,
+;; represented as (hash/c (cons/c path? nat/c) any/c).
+
+(struct Resume (caps))
+(struct Resume:Over Resume ()) ;step over
+(struct Resume:Out  Resume ()) ;step out
+
+(struct Next Resume ())
+(struct Next:All  Next ()) ;single step, and also `caps`
+(struct Next:Some Next ()) ;only `caps`
+(struct Next:None Next ()) ;nothing
+
+(define/contract next
+  (case-> (-> Next?)
+          (-> Next? void))
+  (let ([v (Next:None (hash))])
     (case-lambda [() v]
                  [(v!) (set! v v!)])))
 
 ;; Following are the functions we give `annotate-for-single-stepping`,
 ;; calls to which it "weaves into" the annotated code. When it calls
-;; `break?` and we return true, next it calls either `break-before` or
-;; `break-after`.
-
-(define ((break? src) pos)
-  (match (next-break)
-    ['none        #f]
-    ['all         #t]
-    [(? list? xs) (for/or ([x (in-list xs)])
-                    (match x
-                      [(list (== src) (== pos) _condition _actions) x]
-                      [_                                            #f]))]
-    [_            #f]))
-
-(define/contract (break-before top-mark ccm)
-  (-> mark/c continuation-mark-set? (or/c #f (listof any/c)))
-  (break 'before top-mark ccm #f))
-
-(define/contract (break-after top-mark ccm . vals)
-  (->* (mark/c continuation-mark-set?) #:rest (listof any/c)
-       any)
-  (apply values (break 'after top-mark ccm vals)))
-
-(define/contract (break before/after top-mark ccm vals)
-  (-> (or/c 'before 'after) mark/c continuation-mark-set? (or/c #f (listof 
any/c))
-      (or/c #f (listof any/c)))
+;; `pause?` and we return true, next it calls either `pause-before` or
+;; `pause-after`.
+
+(define ((pause? src) pos)
+  (match (next)
+    [(? Next:None?) #f]
+    [(? Next:All?) #t]
+    [(Next points) (hash-ref points (cons src pos) #f)]))
+
+(define (pause-before top-mark ccm)
+  #;(-> mark/c continuation-mark-set? (or/c #f (listof any/c)))
+  (pause 'before top-mark ccm #f))
+
+(define (pause-after top-mark ccm . vals)
+  #;(->* (mark/c continuation-mark-set?) #:rest (listof any/c)
+         any)
+  (apply values (pause 'after top-mark ccm vals)))
+
+(define (pause before/after top-mark ccm vals)
+  #;(-> (or/c 'before 'after) mark/c continuation-mark-set? (or/c #f (listof 
any/c))
+        (or/c #f (listof any/c)))
   (define stx (mark-source top-mark))
   (define src (syntax-source stx))
+  (define beg (syntax-position stx))
+  (define end (+ (syntax-position stx) (syntax-span stx) -1))
   (define pos (case before/after
-                [(before)    (syntax-position stx)]
-                [(after)  (+ (syntax-position stx) (syntax-span stx) -1)]))
-
-  ;; What to do depends on whether the break is due to a user
-  ;; breakpoint, and if so, what condition and actions it specifies.
-  (define actions
-    (match ((break? src) pos)
-      [(list _src _pos condition actions)
-       (if (or (equal? condition #t) ;short-cut
-               (with-handlers ([values
-                                (λ (e)
-                                  (repl-output-message
-                                   (format "~a\nin debugger condition 
expression:\n  ~v"
-                                           (exn-message e)
-                                           condition))
-                                  #t)]) ;break anyway
-                (eval
-                 (call-with-session-context (current-session-id)
-                                            with-locals
-                                            condition
-                                            (mark-bindings top-mark)))))
-           actions
-           null)]
-      ;; Otherwise, e.g. for a simple step, the default and only
-      ;; action is to break.
-      [_ '(break)]))
-
-  (when (memq 'print actions)
-    (unless (null? (mark-bindings top-mark))
-      (repl-output-message "Debugger watchpoint; locals:")
-      (for* ([binding  (in-list (reverse (mark-bindings top-mark)))]
-             [stx      (in-value (first binding))]
-             [get/set! (in-value (second binding))]
-             #:when (and (syntax-original? stx) (syntax-source stx)))
-        (repl-output-message (format " ~a = ~a" stx (~v (get/set!)))))))
-
-  (when (memq 'log actions)
-    (log-racket-mode-debugger-info
-     "watch ~a ~v~a"
-     before/after
-     stx
-     (for*/fold ([str ""])
-                ([binding  (in-list (reverse (mark-bindings top-mark)))]
-                 [stx      (in-value (first binding))]
-                 [get/set! (in-value (second binding))]
-                 #:when (and (syntax-original? stx) (syntax-source stx)))
-       (string-append str (format "\n ~a = ~a" stx (~v (get/set!)))))))
-
+                [(before) beg]
+                [(after)  end]))
+  (define break?
+    (match (next)
+      [(and (Next points) v)
+       (or (eval-point-expression before/after top-mark vals stx
+                                  (hash-ref points (cons src pos) #f))
+           (Next:All? v))]))
   (cond
-    [(memq 'break actions)
-     ;; Start a debug repl on its own thread, because below we're going to
-     ;; block indefinitely with (channel-get on-resume-channel), waiting for
-     ;; the Emacs front end to issue a debug-resume command.
-     (define repl-thread (thread (repl src pos top-mark)))
+    [break?
      ;; If it is not possible to round-trip serialize/deserialize the
      ;; values, use the original values when stepping (don't attempt to
      ;; substitute user-supplied values).
@@ -203,45 +146,121 @@
                    (get/set!)))))
      (channel-put on-break-channel
                   (list 'debug-break
-                        (cons src pos)
+                        (list src pos beg (add1 end))
                         breakable-positions
                         locals
                         (cons this-break-id
                               (case before/after
                                 [(before) (list 'before)]
                                 [(after)  (list 'after 
(maybe-serialized-vals))]))))
-     ;; Wait for debug-resume command to put to on-resume-channel. If
-     ;; wrong break ID, ignore and wait again.
      (let wait ()
-       (match (channel-get on-resume-channel)
-         [(list (app from-elisp-break-when break-when)
-                (list* (== this-break-id) before/after more))
-          (next-break (calc-next-break break-when before/after top-mark ccm))
-          (begin0
-              ;; The step annotator needs us to return the values to
-              ;; be used when resuming from before or after step --
-              ;; either the original values, or those the user asked
-              ;; to be substituted.
-              (match* [before/after more]
-                [['before (list)]
-                 #f]
-                [['before (list new-vals-str)]
-                 (read-str/default new-vals-str vals)]
-                [['after (list new-vals-pair)]
-                 (match new-vals-pair
-                   [(cons #t  new-vals-str) (read-str/default new-vals-str 
vals)]
-                   [(cons '() _)            vals]) ])
-            (kill-thread repl-thread))]
-         [_ (wait)]))]
+       (sync
+        (handle-evt
+         ;; Wait for any debug-set-local command to put something to
+         ;; on-set-local-channel, then wait again.
+         on-set-local-channel
+         (match-lambda
+           [(list (== this-break-id)
+                  (app string->path source)
+                  pos
+                  new-value-str)
+            (for*/or ([binding (in-list (mark-bindings top-mark))]
+                      [stx (in-value (first binding))]
+                      #:when (and (syntax-original? stx) (syntax-source stx))
+                      #:when (and (equal? source (syntax-source stx))
+                                  (= pos (syntax-position stx)))
+                      [get/set! (in-value (second binding))])
+              (get/set! (read-str/default new-value-str (get/set!)))
+              #t)
+            (wait)]
+           [_ (wait)]))
+        ;; Wait for debug-resume command to put to on-resume-channel.
+        ;; If wrong break ID, ignore and wait again.
+        (handle-evt
+         on-resume-channel
+         (match-lambda
+           [(list (app from-elisp-resume-next new-next)
+                  (list* (== this-break-id) before/after more))
+            (next (calc-next new-next before/after top-mark ccm))
+            (begin0
+                ;; The step annotator needs us to return the values to
+                ;; be used when resuming from before or after step --
+                ;; either the original values, or those the user asked
+                ;; to be substituted.
+                (match* [before/after more]
+                  [['before (list)]
+                   #f]
+                  [['before (list new-vals-str)]
+                   (read-str/default new-vals-str vals)]
+                  [['after (list new-vals-pair)]
+                   (match new-vals-pair
+                     [(cons #t  new-vals-str) (read-str/default new-vals-str 
vals)]
+                     [(cons '() _)            vals]) ]))]
+           [_ (wait)]))))]
     ;; Otherwise, if we didn't break, we simply need to (a) calculate
-    ;; next-break and (b) tell the annotator to use the original
+    ;; the next point and (b) tell the annotator to use the original
     ;; values (no user substitution).
     [else
-     (next-break (calc-next-break (next-break) before/after top-mark ccm))
+     (next (calc-next (next) before/after top-mark ccm))
      (case before/after
        [(before) #f]
        [(after)  vals])]))
 
+;; Note: This works using Racket language expressions, regardless of
+;; the user program #lang.
+(define (eval-point-expression before/after top-mark vals stx expr)
+  (cond
+    [(boolean? expr) expr]
+    [else
+     (define where (format "~a ~a::~a:~a"
+                           before/after
+                           (file-name-from-path (syntax-source stx))
+                           (syntax-position stx)
+                           (syntax-span stx)))
+     (define bindings-outer-to-inner (reverse (mark-bindings top-mark)))
+     (define (#%dump)
+       (let* ([s (string-append "#%dump " where)]
+              [s (for*/fold ([s s])
+                            ([binding  (in-list bindings-outer-to-inner)]
+                             [stx      (in-value (first binding))]
+                             [get/set! (in-value (second binding))]
+                             #:when (and (syntax-original? stx)
+                                         (syntax-source stx)))
+                   (string-append s (format "\n  ~a = ~v"
+                                            (syntax-e stx)
+                                            (get/set!))))]
+              [s (if (eq? before/after 'after)
+                     (string-append s (format "\n => ~s" vals))
+                     s)])
+         (repl-output-message s)
+         (log-racket-mode-debugger-info s)
+         (void)))
+     (define ht
+       (for*/hasheq ([binding  (in-list bindings-outer-to-inner)]
+                     [stx      (in-value (first binding))]
+                     #:when    (and (syntax-original? stx)
+                                    (syntax-source stx))
+                     [sym      (in-value (syntax->datum stx))]
+                     [get/set! (in-value (second binding))])
+         (values sym get/set!)))
+     (define new-expr
+       #`(let-values (#,@(for/list ([(sym get/set!) (in-hash ht)])
+                           #`[(#,(datum->syntax #f sym)) #,(get/set!)])
+                      [(#%dump) #,#%dump])
+           #,expr))
+     (define (on-fail e)
+       (repl-output-message
+        (format "~a\n  in debugger expression: ~s\n  at: ~a"
+                (exn-message e)
+                expr
+                where))
+       #f)
+     (define result (with-handlers ([exn:fail? on-fail])
+                      (eval new-expr)))
+     (cond
+       [(void? result) #f]
+       [else result])]))
+
 (define (serializable? v)
   (with-handlers ([exn:fail:read? (λ _ #f)])
     (equal? v (write/read v))))
@@ -265,9 +284,9 @@
   (with-handlers ([exn:fail:read? (λ _ default)])
     (read (open-input-string str))))
 
-(define/contract (calc-next-break break-when before/after top-mark ccm)
-  (-> (or/c break-when/c 'over 'out) (or/c 'before 'after) mark/c 
continuation-mark-set?
-      break-when/c)
+(define/contract (calc-next next before/after top-mark ccm)
+  (-> Resume? (or/c 'before 'after) mark/c continuation-mark-set?
+      Next?)
   (define (big-step frames)
     (define num-marks (length (debug-marks (current-continuation-marks))))
     (or (for/or ([frame  (in-list frames)]
@@ -279,14 +298,16 @@
                  [right (and left (+ left (syntax-span stx) -1))])
             (and right
                  (breakable-position? src right)
-                 (list (list src right #t '(break))))))
-        'all))
-  (case break-when
-    [(out)  (big-step (debug-marks ccm))]
-    [(over) (case before/after
-              [(before) (big-step (cons top-mark (debug-marks ccm)))]
-              [(after)  'all])]
-    [else break-when])) ;'all, 'none, or user breakpoints
+                 (Next:Some
+                       (cons (list src right #t '(break))
+                             (Resume-caps next))))))
+        (Next:All (Resume-caps next))))
+  (match next
+    [(? Resume:Out?)  (big-step (debug-marks ccm))]
+    [(? Resume:Over?) (case before/after
+                        [(before) (big-step (cons top-mark (debug-marks ccm)))]
+                        [(after)  (Next:All (Resume-caps next))])]
+    [_ next]))
 
 (define break-id/c nat/c)
 (define/contract new-break-id
@@ -297,82 +318,40 @@
   (-> continuation-mark-set? (listof mark/c))
   (continuation-mark-set->list ccm debug-key))
 
-;;; Debug REPL
-
-(define ((repl src pos top-mark))
-  (parameterize ([current-prompt-read (make-prompt-read src pos top-mark)])
-    (read-eval-print-loop)))
-
-(define (make-prompt-read src pos top-mark)
-  (define (racket-mode-debug-prompt-read)
-    (define-values (_base name _dir) (split-path src))
-    (define prompt (format "[~a:~a]" name pos))
-    (define stx (get-interaction prompt))
-    (call-with-session-context (current-session-id)
-                               with-locals stx (mark-bindings top-mark)))
-  racket-mode-debug-prompt-read)
-
-(define (with-locals stx bindings)
-  ;; Before or during module->namespace -- i.e. during a racket-run --
-  ;; current-namespace won't (can't) yet be a namespace with module
-  ;; body bindings. Indeed it might be from make-base-empty-namespace,
-  ;; and not even include racket/base bindings such as #%app. In that
-  ;; case make them available. That way the debug REPL at least can
-  ;; handle expressions involving local bindings.
-  (unless (member '#%app (namespace-mapped-symbols))
-    (log-racket-mode-debug "debug prompt-read namespace-require racket/base")
-    (namespace-require 'racket/base))
-  ;; Note that mark-bindings is ordered from inner to outer scopes --
-  ;; and can include outer variables shadowed by inner ones. So use
-  ;; only the first occurence of each identifier symbol we encounter.
-  ;; e.g. in (let ([x _]) (let ([x _]) ___)) we want only the inner x.
-  (define ht (make-hasheq))
-  (for* ([binding  (in-list bindings)]
-         [sym      (in-value (syntax->datum (first binding)))]
-         #:unless (hash-has-key? ht sym)
-         [get/set! (in-value (second binding))])
-    (hash-set! ht sym get/set!))
-  (syntax-case stx ()
-    ;; I couldn't figure out how to get a set! transformer to work for
-    ;; Typed Racket -- how to annotate or cast a get/set! as (-> Any
-    ;; Void). So instead, just intercept (set! id e) as a datum and
-    ;; effectively (get/set! (eval e debug-repl-ns)) here. In other
-    ;; words treat the stx like a REPL "command". Of course this
-    ;; totally bypasses type-checking, but this is a debugger. YOLO!
-    [(set! id e)
-     (and (module-declared? 'typed/racket/base)
-          (eq? 'set! (syntax->datum #'set!))
-          (identifier? #'id)
-          (hash-has-key? ht (syntax->datum #'id)))
-     (let ([set (hash-ref ht (syntax->datum #'id))]
-           [v   (eval #'e)])
-       (set v)
-       #`(void))]
-    ;; Wrap stx in a let-syntax form with a make-set!-transformer for
-    ;; every local variable in the mark-bindings results.
-    [_
-     (let ([syntax-bindings
-            (for/list ([(sym get/set!) (in-hash ht)])
-              (define id (datum->syntax #f sym))
-              (define xform
-                (make-set!-transformer
-                 (λ (stx)
-                   (syntax-case stx (set!)
-                     [(set! id v) (identifier? #'id) #`(#%plain-app #,get/set! 
v)]
-                     [id          (identifier? #'id) #`'#,(get/set!)]))))
-              #`(#,id #,xform))])
-       #`(let-syntax #,syntax-bindings
-           #,stx))]))
+;;; Command interface
 
+;; These contracts are suitable for "edge" with ELisp.
 
-;;; Command interface
+(define elisp-action-point/c (list/c path-string? pos/c string?))
+(define elisp-resume-next/c (cons/c (or/c 'over 'out 'all 'some 'none)
+                                    (listof elisp-action-point/c)))
+
+(define/contract (from-elisp-resume-next v)
+  (-> elisp-resume-next/c Resume?)
+  (define ctor (case (car v)
+                 [(over) Resume:Over]
+                 [(out)  Resume:Out]
+                 [(all)  Next:All]
+                 [(some) Next:Some]
+                 [(none) Next:None]))
+  (define caps (for/hash ([v (in-list (cdr v))])
+                 (match-define (list path-str pos condition) v)
+                 (values (cons (string->path path-str)
+                               pos)
+                         (with-handlers ([exn:fail?
+                                          (λ (e)
+                                            `(begin
+                                              (println ,(exn-message e))
+                                              #f))])
+                           (read (open-input-string condition))))))
+  (ctor caps))
 
 (define locals/c (listof (list/c path-string? pos/c pos/c symbol? string?)))
 (define break-vals/c (cons/c break-id/c
                              (or/c (list/c 'before)
                                    (list/c 'after (cons/c boolean? string?)))))
 (define on-break/c (list/c 'debug-break
-                           (cons/c path? pos/c)
+                           (list/c path? pos/c pos/c pos/c)
                            breakable-positions/c
                            locals/c
                            break-vals/c))
@@ -382,19 +361,31 @@
                               (or/c (list/c 'before)
                                     (list/c 'before string?)
                                     (list/c 'after (cons/c elisp-bool/c 
string?)))))
-(define on-resume/c (list/c (or/c break-when-elisp/c 'out 'over) 
resume-vals/c))
-(define/contract on-resume-channel (channel/c on-resume/c) (make-channel))
+
+(define on-resume/c (list/c elisp-resume-next/c resume-vals/c))
+(define/contract on-resume-channel
+  (channel/c on-resume/c) (make-channel))
 
 (define/contract (debug-resume resume-info)
   (-> on-resume/c #t)
   (channel-put on-resume-channel resume-info)
   #t)
 
+(define on-set-local/c (list/c break-id/c string? pos/c string?))
+(define/contract on-set-local-channel
+  (channel/c on-set-local/c) (make-channel))
+
+(define/contract (debug-set-local set-local-info)
+  (-> on-set-local/c #t)
+  (channel-put on-set-local-channel set-local-info)
+  #t)
+
 (define (debug-disable)
-  (next-break 'none)
-  (for ([k (in-hash-keys breakable-positions)])
-    (hash-remove! breakable-positions k)))
+  (next (Next:None (hash)))
+  (hash-clear! breakable-positions))
 
+(define (debug-enable-step)
+   (next (Next:All (Resume-caps (next)))))
 
 ;;; Make eval handler to instrument entire files
 
@@ -408,7 +399,7 @@
          (define stx (syntax-or-sexpr->syntax v))
          (define top-stx (expand-syntax-to-top-form stx))
          (cond [(set-member? files (syntax-source stx))
-                (next-break 'all)
+                (next (Next:All (hash)))
                 (parameterize* ([current-eval orig-eval]
                                 [current-load/use-compiled
                                  (let ([orig (current-load/use-compiled)])
diff --git a/racket/repl.rkt b/racket/repl.rkt
index 9361162344b..b3061bcaa5f 100644
--- a/racket/repl.rkt
+++ b/racket/repl.rkt
@@ -10,7 +10,7 @@
          (only-in racket/path path-only file-name-from-path)
          racket/set
          (only-in racket/string string-join)
-         (only-in "debug.rkt" make-debug-eval-handler next-break)
+         (only-in "debug.rkt" make-debug-eval-handler debug-enable-step)
          "elisp.rkt"
          "error.rkt"
          "gui.rkt"
@@ -302,7 +302,7 @@
   (define (racket-mode-prompt-read)
     (define prompt (maybe-module-path->prompt-string maybe-mod))
     (define stx (get-interaction prompt))
-    (next-break 'all) ;let debug-instrumented code break again
+    (debug-enable-step) ;let debug-instrumented code break again
     stx)
   racket-mode-prompt-read)
 
diff --git a/test/racket-tests.el b/test/racket-tests.el
index 46aaeb517b6..5ca6102d942 100644
--- a/test/racket-tests.el
+++ b/test/racket-tests.el
@@ -334,6 +334,15 @@ c.rkt. Visit each file, racket-run, and check as expected."
 
 ;;; Debugger
 
+(defun racket-tests/see-debug-break-overlays ()
+  (racket-tests/call-until-true
+   (lambda ()
+     (let ((names (seq-map (lambda (o)
+                             (overlay-get o 'name))
+                           (overlays-at (point)))))
+       (and (memq 'racket-debug-break      names)
+            (memq 'racket-debug-break-span names))))))
+
 (ert-deftest racket-tests/debugger ()
   (message "racket-tests/debugger")
   (dolist (edit-mode (list #'racket-mode #'racket-hash-lang-mode))
@@ -356,29 +365,19 @@ c.rkt. Visit each file, racket-run, and check as 
expected."
          (racket-tests/should-eventually (get-buffer racket-repl-buffer-name))
          (racket-tests/should-eventually (racket--repl-session-id))
          (racket-tests/should-eventually racket-debug-mode)
-
-         (with-racket-repl-buffer
-           (should (racket-tests/see-back (concat "\n[" name ":42]> ")))) 
;debugger prompt
-         (should (racket-tests/see-char-property (point) 'face
-                                                 racket-debug-break-face))
+         (should (racket-tests/see-debug-break-overlays))
 
          (racket-debug-step)
-         (with-racket-repl-buffer
-           (should (racket-tests/see-back (concat "\n[" name ":33]> "))))
-         (should (racket-tests/see-char-property (point) 'face
-                                                 racket-debug-break-face))
-         (should (racket-tests/see-char-property  (- (point) 3) 'after-string
-                                                  (propertize "41" 'face 
racket-debug-locals-face)))
+         (should (racket-tests/see-debug-break-overlays))
+         (should (racket-tests/see-char-property (- (point) 3) 'after-string
+                                                 (propertize "41" 'face 
racket-debug-locals-face)))
 
          (racket-debug-step)
-         (with-racket-repl-buffer
-           (should (racket-tests/see-back (concat "\n[" name ":47]> "))))
-         (should (racket-tests/see-char-property  (point) 'after-string
-                                                  (propertize "⇒ (values 42)" 
'face racket-debug-result-face)))
+         (should (racket-tests/see-debug-break-overlays))
+         (should (racket-tests/see-char-property (point) 'after-string
+                                                 (propertize "42" 'face 
racket-debug-locals-face)))
 
          (racket-debug-step)              ;no more debug breaks left
-         (with-racket-repl-buffer
-           (should (racket-tests/see-back (concat "\n" name "> "))))
          (should (racket-tests/see-char-property (point) 'after-string
                                                  nil))
          (should-not racket-debug-mode)

Reply via email to