-
-
Notifications
You must be signed in to change notification settings - Fork 645
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add cider-debug.el: Interactive debugger similar to edebug
Add cider-debug-defun-at-point to the menu-bar. Bind cider-debug-defun-at-point to cider-eval-defun-at-point with a prefix. This needs debug.clj at cider-nrepl to work. Invoke M-x cider-debug-defun-at-point to see it in action.
- Loading branch information
Showing
6 changed files
with
206 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -724,7 +724,8 @@ Keyboard shortcut | Description | |
<kbd>C-c M-p</kbd> | Load the form preceding point in the REPL buffer. | ||
<kbd>C-c C-p</kbd> | Evaluate the form preceding point and pretty-print the result in a popup buffer. | ||
<kbd>C-c C-f</kbd> | Evaluate the top level form under point and pretty-print the result in a popup buffer. | ||
<kbd>C-M-x</kbd> <kbd>C-c C-c</kbd> | Evaluate the top level form under point and display the result in the echo area. If invoked with a prefix argument, insert the result into the current buffer. | ||
<kbd>C-M-x</kbd> <kbd>C-c C-c</kbd> | Evaluate the top level form under point and display the result in the echo area. | ||
<kbd>C-u C-M-x</kbd> <kbd>C-u C-c C-c</kbd> | Debug the top level form under point and walk through its evaluation | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
Malabarba
Author
Member
|
||
<kbd>C-c C-r</kbd> | Evaluate the region and display the result in the echo area. | ||
<kbd>C-c C-b</kbd> | Interrupt any pending evaluations. | ||
<kbd>C-c C-m</kbd> | Invoke `macroexpand-1` on the form at point and display the result in a macroexpansion buffer. If invoked with a prefix argument, `macroexpand` is used instead of `macroexpand-1`. | ||
|
@@ -844,6 +845,19 @@ Keyboard shortcut | Description | |
<kbd>d</kbd> | toggle display of duplicate frames | ||
<kbd>a</kbd> | toggle display of all frames | ||
|
||
### cider-debug | ||
<!-- Technically this is not a mode (yet), but let's not burden the user with that knowledge. --> | ||
|
||
cider-debug (invoked with <kbd>C-u C-M-x</kbd>) tries to be consistent with | ||
Edebug. So it makes available the following bindings while stepping through | ||
code. | ||
|
||
Keyboard shortcut | Description | ||
--------------------------------|------------------------------- | ||
<kbd>n</kbd> | Next step | ||
<kbd>c</kbd> | Continue without stopping | ||
<kbd>i</kbd> | Inject a value into running code | ||
|
||
### Managing multiple sessions | ||
|
||
You can connect to multiple nREPL servers using <kbd>M-x | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
;;; cider-debug.el --- CIDER interaction with clj-debugger -*- lexical-binding: t; -*- | ||
|
||
;; Copyright (C) 2015 Artur Malabarba | ||
|
||
;; Author: Artur Malabarba <bruce.connor.am@gmail.com> | ||
|
||
;; This program is free software; you can redistribute it and/or modify | ||
;; it under the terms of the GNU General Public License as published by | ||
;; the Free Software Foundation, either version 3 of the License, or | ||
;; (at your option) any later version. | ||
|
||
;; This program is distributed in the hope that it will be useful, | ||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
;; GNU General Public License for more details. | ||
|
||
;; You should have received a copy of the GNU General Public License | ||
;; along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
;;; Commentary: | ||
|
||
;; Instrument code with `cider-debug-defun-at-point', and when the code is | ||
;; executed cider-debug will kick in. See this function's doc for more | ||
;; information. | ||
|
||
;;; Code: | ||
|
||
(require 'nrepl-client) | ||
(require 'cider-interaction) | ||
|
||
(defvar cider--current-debug-value nil | ||
"Last value received from the debugger. | ||
Is printed by `cider--debug-read-command' while stepping through | ||
code.") | ||
|
||
(defconst cider--instrument-format | ||
(concat "(cider.nrepl.middleware.debug/instrument-and-eval" | ||
;; filename and point are passed in a map. Eventually, this should be | ||
;; part of the message (which the nrepl sees as a map anyway). | ||
" {:filename %S :point %S} '%s)") | ||
"Format to instrument an expression given a file and a coordinate.") | ||
|
||
|
||
;;; Implementation | ||
(defun cider--debug-init-connection () | ||
"Initialize a connection with clj-debugger." | ||
(nrepl-send-request | ||
'("op" "init-debugger") | ||
(let ((connection-buffer (nrepl-current-connection-buffer))) | ||
(lambda (response) | ||
(nrepl-dbind-response response (value coor filename point status id) | ||
(if (not (member "done" status)) | ||
(cider--handle-debug value coor filename point connection-buffer) | ||
(puthash id (gethash id nrepl-pending-requests) | ||
nrepl-completed-requests) | ||
(remhash id nrepl-pending-requests))))))) | ||
|
||
(defun cider--forward-sexp (n) | ||
"Move forward N logical sexps. | ||
This will skip over sexps that don't represent objects, such as ^{}." | ||
(while (> n 0) | ||
;; Non-logical sexps. | ||
(while (progn (forward-sexp 1) | ||
(forward-sexp -1) | ||
(looking-at-p "\\^")) | ||
(forward-sexp 1)) | ||
;; The actual sexp | ||
(forward-sexp 1) | ||
(setq n (1- n)))) | ||
|
||
(defun cider--handle-debug (value coordinates file point connection-buffer) | ||
"Handle debugging notification. | ||
VALUE is saved in `cider--current-debug-value' to be printed | ||
while waiting for user input. | ||
COORDINATES, FILE and POINT are used to place point at the instrumented sexp. | ||
CONNECTION-BUFFER is the nrepl buffer." | ||
;; Be ready to prompt the user when debugger.core/break is | ||
;; triggers a need-input request. | ||
(nrepl-push-input-handler #'cider--need-debug-input connection-buffer) | ||
|
||
;; Navigate to the instrumented sexp, wherever we might be. | ||
(find-file file) | ||
;; Position of the sexp. | ||
(goto-char point) | ||
(condition-case nil | ||
;; Make sure it is a list. | ||
(let ((coordinates (append coordinates nil))) | ||
;; Navigate through sexps inside the sexp. | ||
(while coordinates | ||
(down-list) | ||
(cider--forward-sexp (pop coordinates))) | ||
;; Place point at the end of instrumented sexp. | ||
(cider--forward-sexp 1)) | ||
;; Avoid throwing actual errors, since this happens on every breakpoint. | ||
(error (message "Can't find instrumented sexp, did you edit the source?"))) | ||
;; Prepare to notify the user. | ||
(setq cider--current-debug-value value)) | ||
|
||
(defun cider--debug-read-command () | ||
"Receive input from the user representing a command to do." | ||
(let ((cider-interactive-eval-result-prefix | ||
"(n)ext (c)ontinue (i)nject => ")) | ||
(cider--display-interactive-eval-result | ||
cider--current-debug-value)) | ||
(let ((input | ||
(cl-case (read-char) | ||
;; These keys were chosen to match edebug rather than clj-debugger. | ||
(?n "(c)") | ||
(?c "(q)") | ||
;; Inject | ||
(?i (condition-case nil | ||
(concat (read-from-minibuffer "Expression to inject (non-nil): ") | ||
"\n(c)") | ||
(quit nil)))))) | ||
(if (and input (not (string= "" input))) | ||
(progn (setq cider--current-debug-value nil) | ||
input) | ||
(cider--debug-read-command)))) | ||
|
||
(defun cider--need-debug-input (buffer) | ||
"Handle an need-input request from BUFFER." | ||
(with-current-buffer buffer | ||
(nrepl-request:stdin | ||
;; For now we immediately try to read-char. Ideally, this will | ||
;; be done in a minor-mode (like edebug does) so that the user | ||
;; isn't blocked from doing anything else. | ||
(concat (cider--debug-read-command) "\n") | ||
(cider-stdin-handler buffer)))) | ||
|
||
|
||
;;; User commands | ||
;;;###autoload | ||
(defun cider-debug-defun-at-point () | ||
"Instrument the top-level expression at point. | ||
If it is a defn, dispatch the instrumented definition. Otherwise, | ||
immediately evaluate the instrumented expression. | ||
While debugged code is being evaluated, the user is taken through the | ||
source code and displayed the value of various expressions. At each step, | ||
the following keys are available: | ||
n: Next step | ||
c: Continue without stopping | ||
i: Inject a value at this point" | ||
(interactive) | ||
(cider--debug-init-connection) | ||
(let* ((expression (cider-defun-at-point)) | ||
(eval-buffer (current-buffer)) | ||
(position (cider-defun-at-point-start-pos)) | ||
(prefix | ||
(if (string-match "\\`(defn-? " expression) | ||
"Instrumented => " "=> ")) | ||
(instrumented (format cider--instrument-format | ||
(buffer-file-name) | ||
position | ||
expression))) | ||
;; Once the code has been instrumented, it can be sent as a | ||
;; regular evaluation. Any debug messages will be received by the | ||
;; callback specified in `cider--debug-init-connection'. | ||
(cider-interactive-source-tracking-eval | ||
instrumented position | ||
(nrepl-make-response-handler (current-buffer) | ||
(lambda (_buffer value) | ||
(let ((cider-interactive-eval-result-prefix prefix)) | ||
(cider--display-interactive-eval-result value))) | ||
;; Below is the default for `cider-interactive-source-tracking-eval'. | ||
(lambda (_buffer out) | ||
(cider-emit-interactive-eval-output out)) | ||
(lambda (_buffer err) | ||
(cider-emit-interactive-eval-err-output err) | ||
(cider-handle-compilation-errors err eval-buffer)) | ||
'())))) | ||
|
||
(provide 'cider-debug) | ||
;;; cider-debug.el ends here |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
But C-u C-M-x was already bound to something very useful, how to invoke the original behavior?