Skip to content

Commit a0f7996

Browse files
committed
Generate the debugger menus from a single command catalog
The menu-bar menu and the transient menu each spelled out the full list of debugger commands by hand, so they had already drifted: the menu-bar menu advertised the wrong key for inject and was missing step-in, stacktrace and trace. Introduce `cider--debug-commands' as the one place that lists the commands (key, function, label, group) and generate both menus from it, so they can't fall out of sync again.
1 parent 1a1ca2d commit a0f7996

3 files changed

Lines changed: 97 additions & 58 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646
### Bugs fixed
4747

48+
- 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.
4849
- Fix `cider-log-kill-appender`'s confirmation message, which had the appender and framework names swapped.
4950
- [#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)).
5051
- [#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: 73 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -394,31 +394,60 @@ In order to work properly, this mode must be activated by
394394
(setq cider-debug-prompt value)
395395
(cider--debug-mode-redisplay))
396396

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

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

496-
(transient-define-prefix cider-debug-menu ()
497-
"Transient menu for the CIDER debugger.
525+
(eval-and-compile
526+
(defun cider--debug-transient-column (title group)
527+
"Build a transient column vector titled TITLE for GROUP.
528+
The suffixes are derived from `cider--debug-commands'."
529+
(apply #'vector title
530+
(mapcar (pcase-lambda (`(,key ,fn ,desc ,_group))
531+
(list (string key) desc fn))
532+
(cider--debug-commands-in-group group)))))
533+
534+
(defmacro cider--debug-define-menu ()
535+
"Define the `cider-debug-menu' transient from `cider--debug-commands'."
536+
`(transient-define-prefix cider-debug-menu ()
537+
"Transient menu for the CIDER debugger.
498538
It groups and labels the debugger's single-key commands, so they can be
499539
discovered without memorizing the key prompt. The sub-keys match the
500540
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)])
541+
[,(cider--debug-transient-column "Stepping" 'stepping)
542+
,(cider--debug-transient-column "Values" 'values)
543+
,(cider--debug-transient-column "Session" 'session)]
544+
;; Uppercase `here', preserving the H binding's forced variant.
545+
[:hide (lambda () t)
546+
("H" "Continue to point (forced)" cider-debug-move-here)]))
547+
548+
(cider--debug-define-menu)
523549

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

test/cider-debug-tests.el

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -262,14 +262,26 @@
262262
(cider-debug-force-out)
263263
(expect 'cider-debug-mode-send-reply :to-have-been-called-with ":out" nil t))
264264

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))))
265+
(it "expose every catalog command as a callable command"
266+
;; Every entry in `cider--debug-commands' feeds the menus, so its
267+
;; function must be an actual command.
268+
(pcase-dolist (`(,_key ,fn ,_desc ,_group) cider--debug-commands)
269+
(expect (commandp fn) :to-be-truthy))))
270+
271+
(describe "cider--debug-commands"
272+
(it "covers every command in `cider-debug-prompt-commands'"
273+
;; The catalog drives the menus; the prompt commands drive the overlay
274+
;; and the session keymap. Every prompt command must therefore have a
275+
;; catalog entry, so a command can never appear in one surface but not
276+
;; the others.
277+
(let ((catalog-keys (mapcar #'car cider--debug-commands)))
278+
(dolist (spec cider-debug-prompt-commands)
279+
(expect (memq (car spec) catalog-keys) :to-be-truthy))))
280+
281+
(it "groups every entry under a known group"
282+
(pcase-dolist (`(,_key ,_fn ,_desc ,group) cider--debug-commands)
283+
(expect (memq group '(stepping values session)) :to-be-truthy)))
284+
285+
(it "binds each key to a distinct command"
286+
(let ((keys (mapcar #'car cider--debug-commands)))
287+
(expect (length keys) :to-equal (length (seq-uniq keys))))))

0 commit comments

Comments
 (0)