Skip to content

Commit 5dceb6c

Browse files
authored
Merge pull request #4076 from clojure-emacs/debugger-cleanup
Clean up the debugger menus and fix the force step-out key
2 parents 1a1ca2d + e64ec85 commit 5dceb6c

3 files changed

Lines changed: 139 additions & 61 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545

4646
### Bugs fixed
4747

48+
- Fix the debugger's `O` (force step-out) key, which aborted the session with an error because it sent an invalid `:force-out` command instead of a forced `:out`.
49+
- Fix the debugger's menu-bar menu: it advertised the wrong key for "Inject value" (`i` instead of `j`) and was missing entries for stepping in, showing the stacktrace, and tracing.
4850
- Fix `cider-log-kill-appender`'s confirmation message, which had the appender and framework names swapped.
4951
- [#4066](https://github.com/clojure-emacs/cider/pull/4066): Jump to the actual source when clicking a stack frame for a top-level anonymous function (the `deftest` shape), instead of landing in `clojure.core/fn` ([#3157](https://github.com/clojure-emacs/cider/issues/3157)).
5052
- [#4058](https://github.com/clojure-emacs/cider/pull/4058): Signal a helpful error when `cider-javadoc` gets an unresolvable (non-absolute) Javadoc URL from the middleware, instead of silently doing nothing ([#2969](https://github.com/clojure-emacs/cider/issues/2969)).

lisp/cider-debug.el

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,10 @@ Can be toggled at any time with `\\[cider-debug-toggle-locals]'."
186186
(?n "next" "next")
187187
(?i "in" "in")
188188
(?o "out" "out")
189-
(?O "force-out" nil)
189+
;; Force-out is not a distinct middleware command: it's `out' sent with a
190+
;; `force?' flag, which `cider-debug-mode-send-reply' adds because the key
191+
;; is uppercase.
192+
(?O "out" nil)
190193
(?h "here" "here")
191194
(?e "eval" "eval")
192195
(?p "inspect" "inspect")
@@ -394,31 +397,60 @@ In order to work properly, this mode must be activated by
394397
(setq cider-debug-prompt value)
395398
(cider--debug-mode-redisplay))
396399

400+
;; The command catalog is the single source of truth for both the menu-bar
401+
;; menu (`cider-debug-mode-menu') and the transient menu (`cider-debug-menu'),
402+
;; so the two can no longer drift apart. It is wrapped in `eval-and-compile'
403+
;; because the transient prefix is generated from it at macroexpansion time.
404+
(eval-and-compile
405+
(defconst cider--debug-commands
406+
'((?n cider-debug-next "Next step" stepping)
407+
(?i cider-debug-in "Step in" stepping)
408+
(?o cider-debug-out "Step out" stepping)
409+
(?O cider-debug-force-out "Step out (skip breakpoints)" stepping)
410+
(?h cider-debug-move-here "Continue to point" stepping)
411+
(?c cider-debug-continue "Continue" stepping)
412+
(?C cider-debug-continue-all "Continue non-stop" stepping)
413+
(?e cider-debug-eval "Eval in debug scope" values)
414+
(?j cider-debug-inject "Inject value" values)
415+
(?p cider-debug-inspect "Inspect current value" values)
416+
(?P cider-debug-inspect-expr "Inspect expression" values)
417+
(?l cider-debug-locals "Inspect locals" values)
418+
(?L cider-debug-toggle-locals "Toggle locals display" values)
419+
(?s cider-debug-stacktrace "Show stacktrace" session)
420+
(?t cider-debug-trace "Trace (continue, printing values)" session)
421+
(?q cider-debug-quit "Quit session" session))
422+
"Catalog of the debugger's user-facing commands.
423+
Each entry is (KEY FUNCTION DESCRIPTION GROUP), where KEY is the character
424+
that invokes FUNCTION during a session, DESCRIPTION is the label shown in
425+
the menus, and GROUP is one of `stepping', `values', or `session'.")
426+
427+
(defun cider--debug-commands-in-group (group)
428+
"Return the `cider--debug-commands' entries belonging to GROUP."
429+
(seq-filter (pcase-lambda (`(,_key ,_fn ,_desc ,g)) (eq g group))
430+
cider--debug-commands)))
431+
432+
(defun cider--debug-menu-items (group)
433+
"Return `easy-menu' items for the debugger commands in GROUP."
434+
(mapcar (pcase-lambda (`(,key ,fn ,desc ,_group))
435+
(vector desc fn :keys (string key)))
436+
(cider--debug-commands-in-group group)))
437+
397438
(easy-menu-define cider-debug-mode-menu cider--debug-mode-map
398439
"Menu for CIDER debug mode."
399-
`("CIDER Debugger"
400-
["Next step" (cider-debug-mode-send-reply ":next") :keys "n"]
401-
["Continue" (cider-debug-mode-send-reply ":continue") :keys "c"]
402-
["Continue non-stop" (cider-debug-mode-send-reply ":continue-all") :keys "C"]
403-
["Move out of sexp" (cider-debug-mode-send-reply ":out") :keys "o"]
404-
["Forced move out of sexp" (cider-debug-mode-send-reply ":out" nil t) :keys "O"]
405-
["Move to current position" (cider-debug-mode-send-reply ":here") :keys "h"]
406-
["Quit" (cider-debug-mode-send-reply ":quit") :keys "q"]
407-
"--"
408-
["Evaluate in current scope" (cider-debug-mode-send-reply ":eval") :keys "e"]
409-
["Inject value" (cider-debug-mode-send-reply ":inject") :keys "i"]
410-
["Inspect current value" (cider-debug-mode-send-reply ":inspect") :keys "p"]
411-
["Inspect expression" (cider-debug-mode-send-reply ":inspect-prompt") :keys "P"]
412-
["Inspect local variables" (cider-debug-mode-send-reply ":locals") :keys "l"]
413-
"--"
414-
("Configure keys prompt"
415-
["Don't show keys" (cider--debug-set-prompt nil) :style toggle :selected (eq cider-debug-prompt nil)]
416-
["Show in minibuffer" (cider--debug-set-prompt 'minibuffer) :style toggle :selected (eq cider-debug-prompt 'minibuffer)]
417-
["Show above function" (cider--debug-set-prompt 'overlay) :style toggle :selected (eq cider-debug-prompt 'overlay)]
418-
["Show in both places" (cider--debug-set-prompt t) :style toggle :selected (eq cider-debug-prompt t)]
419-
"--"
420-
["List locals" cider-debug-toggle-locals :style toggle :selected cider-debug-display-locals])
421-
["Customize" (customize-group 'cider-debug)]))
440+
(append
441+
'("CIDER Debugger")
442+
(cider--debug-menu-items 'stepping)
443+
'("--")
444+
(cider--debug-menu-items 'values)
445+
'("--")
446+
(cider--debug-menu-items 'session)
447+
'("--")
448+
'(("Configure keys prompt"
449+
["Don't show keys" (cider--debug-set-prompt nil) :style toggle :selected (eq cider-debug-prompt nil)]
450+
["Show in minibuffer" (cider--debug-set-prompt 'minibuffer) :style toggle :selected (eq cider-debug-prompt 'minibuffer)]
451+
["Show above function" (cider--debug-set-prompt 'overlay) :style toggle :selected (eq cider-debug-prompt 'overlay)]
452+
["Show in both places" (cider--debug-set-prompt t) :style toggle :selected (eq cider-debug-prompt t)])
453+
["Customize" (customize-group 'cider-debug)])))
422454

423455
;; Named commands for the debugger replies, so they can be invoked outside
424456
;; the single-key session bindings (M-x, the transient menu below, etc.).
@@ -493,33 +525,30 @@ In order to work properly, this mode must be activated by
493525
(interactive)
494526
(cider-debug-mode-send-reply ":quit"))
495527

496-
(transient-define-prefix cider-debug-menu ()
497-
"Transient menu for the CIDER debugger.
528+
(eval-and-compile
529+
(defun cider--debug-transient-column (title group)
530+
"Build a transient column vector titled TITLE for GROUP.
531+
The suffixes are derived from `cider--debug-commands'."
532+
(apply #'vector title
533+
(mapcar (pcase-lambda (`(,key ,fn ,desc ,_group))
534+
(list (string key) desc fn))
535+
(cider--debug-commands-in-group group)))))
536+
537+
(defmacro cider--debug-define-menu ()
538+
"Define the `cider-debug-menu' transient from `cider--debug-commands'."
539+
`(transient-define-prefix cider-debug-menu ()
540+
"Transient menu for the CIDER debugger.
498541
It groups and labels the debugger's single-key commands, so they can be
499542
discovered without memorizing the key prompt. The sub-keys match the
500543
session bindings, so this menu is purely additive."
501-
[["Stepping"
502-
("n" "Next step" cider-debug-next)
503-
("i" "Step in" cider-debug-in)
504-
("o" "Step out" cider-debug-out)
505-
("O" "Step out (skip breakpoints)" cider-debug-force-out)
506-
("h" "Continue to point" cider-debug-move-here)
507-
("c" "Continue" cider-debug-continue)
508-
("C" "Continue non-stop" cider-debug-continue-all)]
509-
["Values"
510-
("e" "Eval in debug scope" cider-debug-eval)
511-
("j" "Inject value" cider-debug-inject)
512-
("p" "Inspect current value" cider-debug-inspect)
513-
("P" "Inspect expression" cider-debug-inspect-expr)
514-
("l" "Inspect locals" cider-debug-locals)
515-
("L" "Toggle locals display" cider-debug-toggle-locals)]
516-
["Session"
517-
("s" "Show stacktrace" cider-debug-stacktrace)
518-
("t" "Trace (continue, printing values)" cider-debug-trace)
519-
("q" "Quit session" cider-debug-quit)]]
520-
;; Uppercase `here', preserving the H binding's forced variant.
521-
[:hide (lambda () t)
522-
("H" "Continue to point (forced)" cider-debug-move-here)])
544+
[,(cider--debug-transient-column "Stepping" 'stepping)
545+
,(cider--debug-transient-column "Values" 'values)
546+
,(cider--debug-transient-column "Session" 'session)]
547+
;; Uppercase `here', preserving the H binding's forced variant.
548+
[:hide (lambda () t)
549+
("H" "Continue to point (forced)" cider-debug-move-here)]))
550+
551+
(cider--debug-define-menu)
523552

524553
(defun cider--uppercase-command-p ()
525554
"Return non-nil if the last command was uppercase letter."

test/cider-debug-tests.el

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,29 @@
179179
(cider--debug-move-point '(3 2 1))
180180
(expect (thing-at-point 'symbol) :to-equal "x"))))
181181

182+
(describe "cider--debug-find-coordinates-for-point"
183+
;; `cider--debug-find-coordinates-for-point' is the inverse of
184+
;; `cider--debug-move-point', so feeding its result back into the latter
185+
;; must land on the same spot.
186+
(it "round-trips with cider--debug-move-point"
187+
;; Each form is paired with coordinates known to be valid for it.
188+
(dolist (case '(("(defn a [] (let [x 1] (inc x)) {:a 1, :b 2})" (3 2 1) (3 1 1) (2))
189+
("(defn f [y] (+ (* y 2) (- y 3)))" (3 2 1) (3 1 1))
190+
("(let [x (atom 1)] @x)" (2 1))
191+
("(do @(do (atom {})))" (1 1 1))))
192+
(let ((form (car case)))
193+
(dolist (coord (cdr case))
194+
(with-temp-buffer
195+
(clojure-mode)
196+
(save-excursion (insert form))
197+
(cider--debug-move-point coord)
198+
(let ((target (point)))
199+
(goto-char (point-min))
200+
(let ((found (cider--debug-find-coordinates-for-point target)))
201+
(goto-char (point-min))
202+
(cider--debug-move-point found)
203+
(expect (point) :to-equal target)))))))))
204+
182205
(describe "cider--debug-remember-origin"
183206
(before-each
184207
(setq cider--debug-origin-marker nil
@@ -260,16 +283,40 @@
260283

261284
(it "send a forced :out for `cider-debug-force-out'"
262285
(cider-debug-force-out)
263-
(expect 'cider-debug-mode-send-reply :to-have-been-called-with ":out" nil t))
264-
265-
(it "cover every command char of `cider-debug-prompt-commands'"
266-
;; `here' is handled by `cider-debug-move-here'; the rest map to named
267-
;; commands, so the transient menu can offer the full command set.
268-
(dolist (cmd '(cider-debug-continue cider-debug-continue-all
269-
cider-debug-next cider-debug-in cider-debug-out
270-
cider-debug-force-out cider-debug-eval
271-
cider-debug-inspect cider-debug-inspect-expr
272-
cider-debug-locals cider-debug-inject
273-
cider-debug-stacktrace cider-debug-trace
274-
cider-debug-quit))
275-
(expect (commandp cmd) :to-be-truthy))))
286+
(expect 'cider-debug-mode-send-reply :to-have-been-called-with ":out" nil t)))
287+
288+
(describe "cider-debug-mode-send-reply"
289+
(it "sends a forced :out for the uppercase O key, not an invalid :force-out"
290+
;; The middleware has no `:force-out' command; force-out is `:out' with a
291+
;; `force?' flag, keyed off the uppercase letter.
292+
(spy-on 'cider-nrepl-send-unhandled-request)
293+
(let ((last-command-event ?O)
294+
(cider--debug-mode-response (nrepl-dict "key" "the-key")))
295+
(call-interactively 'cider-debug-mode-send-reply)
296+
(let ((request (car (spy-calls-args-for 'cider-nrepl-send-unhandled-request 0))))
297+
(expect (cadr (member "input" request)) :to-equal "{:response :out :force? true}")
298+
(expect (cadr (member "key" request)) :to-equal "the-key"))))
299+
300+
(it "expose every catalog command as a callable command"
301+
;; Every entry in `cider--debug-commands' feeds the menus, so its
302+
;; function must be an actual command.
303+
(pcase-dolist (`(,_key ,fn ,_desc ,_group) cider--debug-commands)
304+
(expect (commandp fn) :to-be-truthy))))
305+
306+
(describe "cider--debug-commands"
307+
(it "covers every command in `cider-debug-prompt-commands'"
308+
;; The catalog drives the menus; the prompt commands drive the overlay
309+
;; and the session keymap. Every prompt command must therefore have a
310+
;; catalog entry, so a command can never appear in one surface but not
311+
;; the others.
312+
(let ((catalog-keys (mapcar #'car cider--debug-commands)))
313+
(dolist (spec cider-debug-prompt-commands)
314+
(expect (memq (car spec) catalog-keys) :to-be-truthy))))
315+
316+
(it "groups every entry under a known group"
317+
(pcase-dolist (`(,_key ,_fn ,_desc ,group) cider--debug-commands)
318+
(expect (memq group '(stepping values session)) :to-be-truthy)))
319+
320+
(it "binds each key to a distinct command"
321+
(let ((keys (mapcar #'car cider--debug-commands)))
322+
(expect (length keys) :to-equal (length (seq-uniq keys))))))

0 commit comments

Comments
 (0)