Skip to content

Commit f15b610

Browse files
committed
Merge pull request #1085 from Malabarba/rewrite-cider-debug
Rewrite cider-debug and document new debugging keys
2 parents 9ebcba2 + 16e1b77 commit f15b610

File tree

3 files changed

+57
-94
lines changed

3 files changed

+57
-94
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,10 @@ Keyboard shortcut | Description
867867
--------------------------------|-------------------------------
868868
<kbd>n</kbd> | Next step
869869
<kbd>c</kbd> | Continue without stopping
870+
<kbd>o</kbd> | Move out of the current sexp (like `up-list`)
870871
<kbd>i</kbd> | Inject a value into running code
872+
<kbd>e</kbd> | Eval code in current context
873+
<kbd>q</kbd> | Quit execution
871874

872875
### Managing multiple sessions
873876

cider-debug.el

Lines changed: 52 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@
2828
(require 'nrepl-client)
2929
(require 'cider-interaction)
3030

31-
(defvar cider--current-debug-value nil
32-
"Last value received from the debugger.
33-
Is printed by `cider--debug-read-command' while stepping through
34-
code.")
35-
3631
(defconst cider--instrument-format
3732
(concat "(cider.nrepl.middleware.debug/instrument-and-eval"
3833
;; filename and point are passed in a map. Eventually, this should be
@@ -46,14 +41,13 @@ code.")
4641
"Initialize a connection with clj-debugger."
4742
(nrepl-send-request
4843
'("op" "init-debugger")
49-
(let ((connection-buffer (nrepl-current-connection-buffer)))
50-
(lambda (response)
51-
(nrepl-dbind-response response (debug-value coor filename point status id)
52-
(if (not (member "done" status))
53-
(cider--handle-debug debug-value coor filename point connection-buffer)
54-
(puthash id (gethash id nrepl-pending-requests)
55-
nrepl-completed-requests)
56-
(remhash id nrepl-pending-requests)))))))
44+
(lambda (response)
45+
(nrepl-dbind-response response (status id)
46+
(if (not (member "done" status))
47+
(cider--handle-debug response)
48+
(puthash id (gethash id nrepl-pending-requests)
49+
nrepl-completed-requests)
50+
(remhash id nrepl-pending-requests))))))
5751

5852
(defun cider--forward-sexp (n)
5953
"Move forward N logical sexps.
@@ -68,64 +62,61 @@ This will skip over sexps that don't represent objects, such as ^{}."
6862
(forward-sexp 1)
6963
(setq n (1- n))))
7064

71-
(defun cider--handle-debug (value coordinates file point connection-buffer)
72-
"Handle debugging notification.
73-
VALUE is saved in `cider--current-debug-value' to be printed
74-
while waiting for user input.
75-
COORDINATES, FILE and POINT are used to place point at the instrumented sexp.
76-
CONNECTION-BUFFER is the nrepl buffer."
77-
;; Be ready to prompt the user when debugger.core/break is
78-
;; triggers a need-input request.
79-
(nrepl-push-input-handler #'cider--need-debug-input connection-buffer)
80-
65+
(defun cider--debug-move-point (file pos coordinates)
66+
"Place point on POS in FILE, then navigate into the next sexp.
67+
COORDINATES is a list of integers that specify how to navigate into the
68+
sexp."
8169
;; Navigate to the instrumented sexp, wherever we might be.
8270
(find-file file)
8371
;; Position of the sexp.
84-
(goto-char point)
72+
(goto-char pos)
8573
(condition-case nil
8674
;; Make sure it is a list.
87-
(let ((coordinates (append coordinates nil)))
88-
;; Navigate through sexps inside the sexp.
75+
;; Navigate through sexps inside the sexp.
76+
(progn
8977
(while coordinates
9078
(down-list)
9179
(cider--forward-sexp (pop coordinates)))
9280
;; Place point at the end of instrumented sexp.
9381
(cider--forward-sexp 1))
9482
;; Avoid throwing actual errors, since this happens on every breakpoint.
95-
(error (message "Can't find instrumented sexp, did you edit the source?")))
96-
;; Prepare to notify the user.
97-
(setq cider--current-debug-value value))
98-
99-
(defun cider--debug-read-command ()
100-
"Receive input from the user representing a command to do."
101-
(let ((cider-interactive-eval-result-prefix
102-
"(n)ext (c)ontinue (i)nject => "))
103-
(cider--display-interactive-eval-result
104-
(or cider--current-debug-value "#unknown#")))
105-
(let ((input
106-
(cl-case (read-char)
107-
;; These keys were chosen to match edebug rather than clj-debugger.
108-
(?n "(c)")
109-
(?c "(q)")
110-
;; Inject
111-
(?i (condition-case nil
112-
(concat (read-from-minibuffer "Expression to inject (non-nil): ")
113-
"\n(c)")
114-
(quit nil))))))
115-
(if (and input (not (string= "" input)))
116-
(progn (setq cider--current-debug-value nil)
117-
input)
118-
(cider--debug-read-command))))
119-
120-
(defun cider--need-debug-input (buffer)
121-
"Handle an need-input request from BUFFER."
122-
(with-current-buffer buffer
123-
(nrepl-request:stdin
124-
;; For now we immediately try to read-char. Ideally, this will
125-
;; be done in a minor-mode (like edebug does) so that the user
126-
;; isn't blocked from doing anything else.
127-
(concat (cider--debug-read-command) "\n")
128-
(cider-stdin-handler buffer))))
83+
(error (message "Can't find instrumented sexp, did you edit the source?"))))
84+
85+
(defun cider--handle-debug (response)
86+
"Handle debugging notification.
87+
RESPONSE is a message received form the nrepl describing the input
88+
needed. It is expected to contain at least \"key\", \"input-type\", and
89+
\"prompt\", and possibly other entries depending on the input-type."
90+
(nrepl-dbind-response response (debug-value key coor filename point input-type prompt)
91+
(let ((input))
92+
(unwind-protect
93+
(setq input
94+
(pcase input-type
95+
("expression" (cider-read-from-minibuffer
96+
(or prompt "Expression: ")))
97+
((pred sequencep)
98+
(when (and filename point)
99+
(cider--debug-move-point filename point coor))
100+
(cider--debug-read-command input-type debug-value prompt))))
101+
;; No matter what, we want to send this request or the session will stay
102+
;; hanged.
103+
(nrepl-send-request
104+
(list "op" "debug-input" "key" key
105+
;; If the user somehow managed to trigger an error or not input
106+
;; anything send :quit to avoid getting an exception.
107+
"input" (or input ":quit"))
108+
#'ignore)))))
109+
110+
(defun cider--debug-read-command (command-list value prompt)
111+
"Receive input from the user representing a command to do.
112+
VALUE is displayed to the user as the output of last evaluated sexp."
113+
(let ((cider-interactive-eval-result-prefix (concat prompt "\n => ")))
114+
(cider--display-interactive-eval-result (or value "#unknown#")))
115+
(let ((alist `((?\C-\[ . ":quit") (?\C-g . ":quit")
116+
,@(mapcar (lambda (k) (cons (string-to-char k) (concat ":" k)))
117+
command-list))))
118+
(or (cdr (assq (read-char) alist))
119+
(cider--debug-read-command command-list value))))
129120

130121

131122
;;; User commands
@@ -137,10 +128,7 @@ immediately evaluate the instrumented expression.
137128
138129
While debugged code is being evaluated, the user is taken through the
139130
source code and displayed the value of various expressions. At each step,
140-
the following keys are available:
141-
n: Next step
142-
c: Continue without stopping
143-
i: Inject a value at this point"
131+
a number of keys will be prompted to the user."
144132
(interactive)
145133
(cider--debug-init-connection)
146134
(let* ((expression (cider-defun-at-point))

nrepl-client.el

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -815,34 +815,6 @@ for functionality like pretty-printing won't clobber the values of *1, *2, etc."
815815
(defvar nrepl-err-handler 'cider-default-err-handler
816816
"Evaluation error handler.")
817817

818-
(defvar-local nrepl--input-handler-queue (make-queue)
819-
"A queue of handlers (functions) for incoming \"need-input\" messages.
820-
Functions are passed the connection buffer as the only argument and should
821-
send a string to stdin on that connection. See `cider-need-input' for an
822-
example.
823-
824-
This variable is designed as a queue, so new elements should be added to
825-
the bottom using `nrepl-push-input-handler', and they are removed from the
826-
top when used. Whenever the variable is nil, `cider-need-input' is used.")
827-
828-
(defun nrepl-push-input-handler (function buffer)
829-
"Add FUNCTION to input handlers queue on BUFFER's connection.
830-
FUNCTION is added to the bottom of `nrepl--input-handler-queue'."
831-
(with-current-buffer buffer
832-
(with-current-buffer (nrepl-current-connection-buffer)
833-
(queue-enqueue nrepl--input-handler-queue function))))
834-
835-
(defun nrepl--handle-input (buffer)
836-
"Handle need-input message in BUFFER.
837-
Check whether there's a handler waiting to be used in
838-
`nrepl--input-handler-queue', otherwise default to `cider-need-input'."
839-
(let ((handler
840-
(with-current-buffer buffer
841-
(with-current-buffer (nrepl-current-connection-buffer)
842-
(or (queue-dequeue nrepl--input-handler-queue)
843-
#'cider-need-input)))))
844-
(funcall handler buffer)))
845-
846818
(defun nrepl-make-response-handler (buffer value-handler stdout-handler
847819
stderr-handler done-handler
848820
&optional eval-error-handler)
@@ -887,7 +859,7 @@ server responses."
887859
(when (member "namespace-not-found" status)
888860
(message "Namespace not found."))
889861
(when (member "need-input" status)
890-
(nrepl--handle-input buffer))
862+
(cider-need-input buffer))
891863
(when (member "done" status)
892864
(puthash id (gethash id nrepl-pending-requests) nrepl-completed-requests)
893865
(remhash id nrepl-pending-requests)
@@ -962,7 +934,7 @@ of the same \"op\" that came along."
962934
;; If we get a need-input message then the repl probably isn't going
963935
;; anywhere, and we'll just timeout. So we forward it to the user.
964936
(if (member "need-input" status)
965-
(progn (nrepl--handle-input (current-buffer))
937+
(progn (cider-need-input (current-buffer))
966938
;; If the used took a few seconds to respond, we might
967939
;; unnecessarily timeout, so let's reset the timer.
968940
(setq time0 (current-time)))

0 commit comments

Comments
 (0)