|
|
;;; cider-debug.el --- CIDER interaction with the cider.debug nREPL middleware -*- lexical-binding: t; -*- |
|
|
|
|
|
;; Copyright © 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) |
|
|
(require 'cider-client) |
|
|
(require 'cider-util) |
|
|
(require 'cider-inspector) |
|
|
(require 'cider-browse-ns) |
|
|
(require 'cider-common) |
|
|
(require 'cider-compat) |
|
|
(require 'seq) |
|
|
(require 'spinner) |
|
|
|
|
|
|
|
|
;;; Customization |
|
|
(defgroup cider-debug nil |
|
|
"Presentation and behaviour of the cider debugger." |
|
|
:prefix "cider-debug-" |
|
|
:group 'cider |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(defface cider-debug-code-overlay-face |
|
|
'((((class color) (background light)) :background "grey80") |
|
|
(((class color) (background dark)) :background "grey30")) |
|
|
"Face used to mark code being debugged." |
|
|
:group 'cider-debug |
|
|
:package-version '(cider . "0.9.1")) |
|
|
|
|
|
(defface cider-debug-prompt-face |
|
|
'((t :underline t :inherit font-lock-builtin-face)) |
|
|
"Face used to highlight keys in the debug prompt." |
|
|
:group 'cider-debug |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(defface cider-instrumented-face |
|
|
'((t :box (:color "red" :line-width -1))) |
|
|
"Face used to mark code being debugged." |
|
|
:group 'cider-debug |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(defcustom cider-debug-prompt 'overlay |
|
|
"If and where to show the keys while debugging. |
|
|
If `minibuffer', show it in the minibuffer along with the return value. |
|
|
If `overlay', show it in an overlay above the current function. |
|
|
If t, do both. |
|
|
If nil, don't list available keys at all." |
|
|
:type '(choice (const :tag "Show in minibuffer" minibuffer) |
|
|
(const :tag "Show above function" overlay) |
|
|
(const :tag "Show in both places" t) |
|
|
(const :tag "Don't list keys" nil)) |
|
|
:group 'cider-debug |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(defcustom cider-debug-use-overlays t |
|
|
"Whether to higlight debugging information with overlays. |
|
|
Takes the same possible values as `cider-use-overlays', but only applies to |
|
|
values displayed during debugging sessions. |
|
|
To control the overlay that lists possible keys above the current function, |
|
|
configure `cider-debug-prompt' instead." |
|
|
:type '(choice (const :tag "End of line" t) |
|
|
(const :tag "Bottom of screen" nil) |
|
|
(const :tag "Both" both)) |
|
|
:group 'cider-debug |
|
|
:package-version '(cider . "0.9.1")) |
|
|
|
|
|
(defcustom cider-debug-print-level 10 |
|
|
"print-level for values displayed by the debugger. |
|
|
This variable must be set before starting the repl connection." |
|
|
:type '(choice (const :tag "No limit" nil) |
|
|
(integer :tag "Max depth" 10)) |
|
|
:group 'cider-debug |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(defcustom cider-debug-print-length 10 |
|
|
"print-length for values displayed by the debugger. |
|
|
This variable must be set before starting the repl connection." |
|
|
:type '(choice (const :tag "No limit" nil) |
|
|
(integer :tag "Max depth" 10)) |
|
|
:group 'cider-debug |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
|
|
|
;;; Implementation |
|
|
(defun cider-browse-instrumented-defs () |
|
|
"List all instrumented definitions." |
|
|
(interactive) |
|
|
(if-let ((all (thread-first (cider-nrepl-send-sync-request (list "op" "debug-instrumented-defs")) |
|
|
(nrepl-dict-get "list")))) |
|
|
(with-current-buffer (cider-popup-buffer cider-browse-ns-buffer t) |
|
|
(let ((inhibit-read-only t)) |
|
|
(erase-buffer) |
|
|
(dolist (list all) |
|
|
(let ((ns (car list))) |
|
|
(cider-browse-ns--list (current-buffer) ns |
|
|
(mapcar #'cider-browse-ns--properties (cdr list)) |
|
|
ns 'noerase) |
|
|
(goto-char (point-max)) |
|
|
(insert "\n")))) |
|
|
(goto-char (point-min))) |
|
|
(message "No currently instrumented definitions"))) |
|
|
|
|
|
(defun cider--debug-response-handler (response) |
|
|
"Handle responses from the cider.debug middleware." |
|
|
(nrepl-dbind-response response (status id causes) |
|
|
(when (member "eval-error" status) |
|
|
(cider--render-stacktrace-causes causes)) |
|
|
(when (member "need-debug-input" status) |
|
|
(cider--handle-debug response)) |
|
|
(when (member "done" status) |
|
|
(nrepl--mark-id-completed id)))) |
|
|
|
|
|
(defun cider--debug-init-connection () |
|
|
"Initialize a connection with the cider.debug middleware." |
|
|
(cider-nrepl-send-request |
|
|
(append '("op" "init-debugger") |
|
|
(when cider-debug-print-level |
|
|
(list "print-level" cider-debug-print-level)) |
|
|
(when cider-debug-print-length |
|
|
(list "print-length" cider-debug-print-length))) |
|
|
#'cider--debug-response-handler)) |
|
|
|
|
|
|
|
|
;;; Debugging overlays |
|
|
(defconst cider--fringe-arrow-string |
|
|
#("." 0 1 (display (left-fringe right-triangle))) |
|
|
"Used as an overlay's before-string prop to place a fringe arrow.") |
|
|
|
|
|
(defun cider--debug-display-result-overlay (value) |
|
|
"Place an overlay at point displaying VALUE." |
|
|
(when cider-debug-use-overlays |
|
|
;; This is cosmetic, let's ensure it doesn't break the session no matter what. |
|
|
(ignore-errors |
|
|
;; Result |
|
|
(cider--make-result-overlay (cider-font-lock-as-clojure value) |
|
|
:where (point-marker) |
|
|
:type 'debug-result |
|
|
'before-string cider--fringe-arrow-string) |
|
|
;; Code |
|
|
(cider--make-overlay (save-excursion (clojure-backward-logical-sexp 1) (point)) |
|
|
(point) 'debug-code |
|
|
'face 'cider-debug-code-overlay-face |
|
|
;; Higher priority than `show-paren'. |
|
|
'priority 2000)))) |
|
|
|
|
|
|
|
|
;;; Minor mode |
|
|
(defvar-local cider--debug-mode-commands-alist nil |
|
|
"Alist from keys to debug commands. |
|
|
Autogenerated by `cider--turn-on-debug-mode'.") |
|
|
|
|
|
(defvar-local cider--debug-mode-response nil |
|
|
"Response that triggered current debug session. |
|
|
Set by `cider--turn-on-debug-mode'.") |
|
|
|
|
|
(defcustom cider-debug-display-locals nil |
|
|
"If non-nil, local variables are displayed while debugging. |
|
|
Can be toggled at any time with `\\[cider-debug-toggle-locals]'." |
|
|
:type 'boolean |
|
|
:group 'cider-debug |
|
|
:package-version '(cider . "0.10.0")) |
|
|
|
|
|
(defun cider--debug-format-locals-list (locals) |
|
|
"Return a string description of list LOCALS. |
|
|
Each element of LOCALS should be a list of at least two elements." |
|
|
(if locals |
|
|
(let ((left-col-width |
|
|
;; To right-indent the variable names. |
|
|
(apply #'max (mapcar (lambda (l) (string-width (car l))) locals)))) |
|
|
;; A format string to build a format string. :-P |
|
|
(mapconcat (lambda (l) (format (format " %%%ds: %%s\n" left-col-width) |
|
|
(propertize (car l) 'face 'font-lock-variable-name-face) |
|
|
(cider-font-lock-as-clojure (cadr l)))) |
|
|
locals "")) |
|
|
"")) |
|
|
|
|
|
(defun cider--debug-prompt (command-list) |
|
|
"Return prompt to display for COMMAND-LIST." |
|
|
(concat |
|
|
(mapconcat (lambda (x) (put-text-property 0 1 'face 'cider-debug-prompt-face x) x) |
|
|
;; `eval' is now integrated with things like `C-x C-e' and `C-c M-:' |
|
|
;; so we don't advertise this key to reduce clutter. |
|
|
;; `inspect' would conflict with `inject'. |
|
|
(seq-difference command-list '("eval" "inspect")) " ") |
|
|
"\n")) |
|
|
|
|
|
(defvar-local cider--debug-prompt-overlay nil) |
|
|
|
|
|
(defun cider--debug-mode-redisplay () |
|
|
"Display the input prompt to the user." |
|
|
(nrepl-dbind-response cider--debug-mode-response (debug-value input-type locals) |
|
|
(when (or (eq cider-debug-prompt t) |
|
|
(eq cider-debug-prompt 'overlay)) |
|
|
(if (overlayp cider--debug-prompt-overlay) |
|
|
(overlay-put cider--debug-prompt-overlay |
|
|
'before-string (cider--debug-prompt input-type)) |
|
|
(setq cider--debug-prompt-overlay |
|
|
(cider--make-overlay |
|
|
(max (cider-defun-at-point-start-pos) |
|
|
(window-start)) |
|
|
nil 'debug-prompt |
|
|
'before-string (cider--debug-prompt input-type))))) |
|
|
(let* ((value (concat " " cider-eval-result-prefix |
|
|
(cider-font-lock-as-clojure |
|
|
(or debug-value "#unknown#")))) |
|
|
(to-display |
|
|
(concat (when cider-debug-display-locals |
|
|
(cider--debug-format-locals-list locals)) |
|
|
(when (or (eq cider-debug-prompt t) |
|
|
(eq cider-debug-prompt 'minibuffer)) |
|
|
(cider--debug-prompt input-type)) |
|
|
(when (or (not cider-debug-use-overlays) |
|
|
(eq cider-debug-use-overlays 'both)) |
|
|
value)))) |
|
|
(if (> (string-width to-display) 0) |
|
|
(message "%s" to-display) |
|
|
;; If there's nothing to display in the minibuffer. Just send the value |
|
|
;; to the Messages buffer. |
|
|
(message "%s" value) |
|
|
(message nil))))) |
|
|
|
|
|
(defun cider-debug-toggle-locals () |
|
|
"Toggle display of local variables." |
|
|
(interactive) |
|
|
(setq cider-debug-display-locals (not cider-debug-display-locals)) |
|
|
(cider--debug-mode-redisplay)) |
|
|
|
|
|
(defun cider--debug-lexical-eval (key form &optional callback _point) |
|
|
"Eval FORM in the lexical context of debug session given by KEY. |
|
|
Do nothing if CALLBACK is provided. |
|
|
Designed to be used as `cider-interactive-eval-override' and called instead |
|
|
of `cider-interactive-eval' in debug sessions." |
|
|
;; The debugger uses its own callback, so if the caller is passing a callback |
|
|
;; we return nil and let `cider-interactive-eval' do its thing. |
|
|
(unless callback |
|
|
(cider-debug-mode-send-reply (format "{:response :eval, :code %s}" form) |
|
|
key) |
|
|
t)) |
|
|
|
|
|
(defvar cider--debug-mode-tool-bar-map |
|
|
(let ((tool-bar-map (make-sparse-keymap))) |
|
|
(tool-bar-add-item "right-arrow" #'cider-debug-mode-send-reply :next :label "Next step") |
|
|
(tool-bar-add-item "next-node" #'cider-debug-mode-send-reply :continue :label "Continue non-stop") |
|
|
(tool-bar-add-item "jump-to" #'cider-debug-mode-send-reply :out :label "Out of sexp") |
|
|
(tool-bar-add-item "exit" #'cider-debug-mode-send-reply :quit :label "Quit") |
|
|
tool-bar-map)) |
|
|
|
|
|
(defvar cider--debug-mode-map) |
|
|
|
|
|
(define-minor-mode cider--debug-mode |
|
|
"Mode active during debug sessions. |
|
|
In order to work properly, this mode must be activated by |
|
|
`cider--turn-on-debug-mode'." |
|
|
nil " DEBUG" '() |
|
|
(if cider--debug-mode |
|
|
(if cider--debug-mode-response |
|
|
(nrepl-dbind-response cider--debug-mode-response (input-type) |
|
|
;; A debug session is an ongoing eval, but it's annoying to have the |
|
|
;; spinner spinning while you debug. |
|
|
(when spinner-current (spinner-stop)) |
|
|
(setq-local tool-bar-map cider--debug-mode-tool-bar-map) |
|
|
(add-hook 'kill-buffer-hook #'cider--debug-quit nil 'local) |
|
|
(add-hook 'before-revert-hook #'cider--debug-quit nil 'local) |
|
|
(unless (consp input-type) |
|
|
(error "debug-mode activated on a message not asking for commands: %s" cider--debug-mode-response)) |
|
|
;; Integrate with eval commands. |
|
|
(setq cider-interactive-eval-override |
|
|
(apply-partially #'cider--debug-lexical-eval |
|
|
(nrepl-dict-get cider--debug-mode-response "key"))) |
|
|
;; Set the keymap. |
|
|
(let ((alist (mapcar (lambda (k) (cons (string-to-char k) (concat ":" k))) |
|
|
(seq-difference input-type '("inspect"))))) |
|
|
(setq cider--debug-mode-commands-alist alist) |
|
|
(dolist (it alist) |
|
|
(define-key cider--debug-mode-map (vector (car it)) #'cider-debug-mode-send-reply))) |
|
|
;; Show the prompt. |
|
|
(cider--debug-mode-redisplay) |
|
|
;; If a sync request is ongoing, the user can't act normally to |
|
|
;; provide input, so we enter `recursive-edit'. |
|
|
(when nrepl-ongoing-sync-request |
|
|
(recursive-edit))) |
|
|
(cider--debug-mode -1) |
|
|
(if (called-interactively-p 'any) |
|
|
(user-error (substitute-command-keys "Don't call this mode manually, use `\\[universal-argument] \\[cider-eval-defun-at-point]' instead")) |
|
|
(error "Attempt to activate `cider--debug-mode' without setting `cider--debug-mode-response' first"))) |
|
|
(setq cider-interactive-eval-override nil) |
|
|
(setq cider--debug-mode-commands-alist nil) |
|
|
(setq cider--debug-mode-response nil) |
|
|
;; We wait a moment before clearing overlays and the read-onlyness, so that |
|
|
;; cider-nrepl has a chance to send the next message, and so that the user |
|
|
;; doesn't accidentally hit `n' between two messages (thus editing the code). |
|
|
(when-let ((proc (unless nrepl-ongoing-sync-request |
|
|
(get-buffer-process (cider-current-connection))))) |
|
|
(accept-process-output proc 0.5)) |
|
|
(unless cider--debug-mode |
|
|
(setq buffer-read-only nil) |
|
|
(cider--debug-remove-overlays (current-buffer))) |
|
|
(when nrepl-ongoing-sync-request |
|
|
(ignore-errors (exit-recursive-edit))))) |
|
|
|
|
|
(defun cider--debug-remove-overlays (&optional buffer) |
|
|
"Remove CIDER debug overlays from BUFFER if `cider--debug-mode' is nil." |
|
|
(when (or (not buffer) (buffer-live-p buffer)) |
|
|
(with-current-buffer (or buffer (current-buffer)) |
|
|
(unless cider--debug-mode |
|
|
(kill-local-variable 'tool-bar-map) |
|
|
(remove-overlays nil nil 'cider-type 'debug-result) |
|
|
(remove-overlays nil nil 'cider-type 'debug-code) |
|
|
(setq cider--debug-prompt-overlay nil) |
|
|
(remove-overlays nil nil 'cider-type 'debug-prompt))))) |
|
|
|
|
|
(defun cider--debug-set-prompt (value) |
|
|
"Set `cider-debug-prompt' to VALUE, then redisplay." |
|
|
(setq cider-debug-prompt value) |
|
|
(cider--debug-mode-redisplay)) |
|
|
|
|
|
(easy-menu-define cider-debug-mode-menu cider--debug-mode-map |
|
|
"Menu for CIDER debug mode" |
|
|
`("CIDER DEBUGGER" |
|
|
["Next step" (cider-debug-mode-send-reply ":next") :keys "n"] |
|
|
["Continue non-stop" (cider-debug-mode-send-reply ":continue") :keys "c"] |
|
|
["Move out of sexp" (cider-debug-mode-send-reply ":out") :keys "o"] |
|
|
["Quit" (cider-debug-mode-send-reply ":quit") :keys "q"] |
|
|
"--" |
|
|
["Evaluate in current scope" (cider-debug-mode-send-reply ":eval") :keys "e"] |
|
|
["Inject value" (cider-debug-mode-send-reply ":inject") :keys "i"] |
|
|
["Inspect value" (cider-debug-mode-send-reply ":inspect")] |
|
|
["Inspect local variables" (cider-debug-mode-send-reply ":locals") :keys "l"] |
|
|
"--" |
|
|
("Configure keys prompt" |
|
|
["Don't show keys" (cider--debug-set-prompt nil) :style toggle :selected (eq cider-debug-prompt nil)] |
|
|
["Show in minibuffer" (cider--debug-set-prompt 'minibuffer) :style toggle :selected (eq cider-debug-prompt 'minibuffer)] |
|
|
["Show above function" (cider--debug-set-prompt 'overlay) :style toggle :selected (eq cider-debug-prompt 'overlay)] |
|
|
["Show in both places" (cider--debug-set-prompt t) :style toggle :selected (eq cider-debug-prompt t)] |
|
|
"--" |
|
|
["List locals" cider-debug-toggle-locals :style toggle :selected cider-debug-display-locals]) |
|
|
["Customize" (customize-group 'cider-debug)])) |
|
|
|
|
|
(defun cider-debug-mode-send-reply (command &optional key) |
|
|
"Reply to the message that started current bufer's debugging session. |
|
|
COMMAND is sent as the input option. KEY can be provided to reply to a |
|
|
specific message." |
|
|
(interactive (list |
|
|
(if (symbolp last-command-event) |
|
|
(symbol-name last-command-event) |
|
|
(cdr (assq last-command-event cider--debug-mode-commands-alist))) |
|
|
nil)) |
|
|
(cider-nrepl-send-unhandled-request |
|
|
(list "op" "debug-input" "input" (or command ":quit") |
|
|
"key" (or key (nrepl-dict-get cider--debug-mode-response "key")))) |
|
|
(ignore-errors (cider--debug-mode -1))) |
|
|
|
|
|
(defun cider--debug-quit () |
|
|
"Send a :quit reply to the debugger. Used in hooks." |
|
|
(when cider--debug-mode |
|
|
(cider-debug-mode-send-reply ":quit") |
|
|
(message "Quitting debug session"))) |
|
|
|
|
|
|
|
|
;;; Movement logic |
|
|
(defconst cider--debug-buffer-format "*cider-debug %s*") |
|
|
|
|
|
(defun cider--debug-trim-code (code) |
|
|
(replace-regexp-in-string "\\`#\\(dbg\\|break\\) ?" "" code)) |
|
|
|
|
|
(defun cider--initialize-debug-buffer (code ns id) |
|
|
"Create a new debugging buffer with CODE and namespace NS. |
|
|
ID is the id of the message that instrumented CODE." |
|
|
(let ((buffer-name (format cider--debug-buffer-format id))) |
|
|
(if-let ((buffer (get-buffer buffer-name))) |
|
|
(cider-popup-buffer-display buffer 'select) |
|
|
(with-current-buffer (cider-popup-buffer buffer-name 'select |
|
|
#'clojure-mode 'ancillary) |
|
|
(setq cider-buffer-ns ns) |
|
|
(setq buffer-undo-list nil) |
|
|
(let ((inhibit-read-only t) |
|
|
(buffer-undo-list t)) |
|
|
(erase-buffer) |
|
|
(insert |
|
|
(format "%s" (cider--debug-trim-code code))) |
|
|
(cider--font-lock-ensure) |
|
|
(set-buffer-modified-p nil)))) |
|
|
(switch-to-buffer buffer-name) |
|
|
(goto-char (point-min)))) |
|
|
|
|
|
(defun cider--debug-goto-keyval (key) |
|
|
"Find KEY in current sexp or return nil." |
|
|
(when-let ((limit (ignore-errors (save-excursion (up-list) (point))))) |
|
|
(search-forward-regexp (concat "\\_<" (regexp-quote key) "\\_>") |
|
|
limit 'noerror))) |
|
|
|
|
|
(defun cider--debug-move-point (coordinates) |
|
|
"Place point on POS in FILE, then navigate into the next sexp. |
|
|
COORDINATES is a list of integers that specify how to navigate into the |
|
|
sexp." |
|
|
(condition-case-unless-debug nil |
|
|
;; Navigate through sexps inside the sexp. |
|
|
(let ((in-syntax-quote nil)) |
|
|
(while coordinates |
|
|
(down-list) |
|
|
;; Are we entering a syntax-quote? |
|
|
(when (looking-back "`\\(#{\\|[{[(]\\)" (line-beginning-position)) |
|
|
;; If we are, this affects all nested structures until the next `~', |
|
|
;; so we set this variable for all following steps in the loop. |
|
|
(setq in-syntax-quote t)) |
|
|
(when in-syntax-quote |
|
|
;; A `(. .) is read as (seq (concat (list .) (list .))). This pops |
|
|
;; the `seq', since the real coordinates are inside the `concat'. |
|
|
(pop coordinates) |
|
|
;; Non-list seqs like `[] and `{} are read with |
|
|
;; an extra (apply vector ...), so pop it too. |
|
|
(unless (eq ?\( (char-before)) |
|
|
(pop coordinates))) |
|
|
;; #(...) is read as (fn* ([] ...)), so we patch that here. |
|
|
(when (looking-back "#(" (line-beginning-position)) |
|
|
(pop coordinates)) |
|
|
(if coordinates |
|
|
(let ((next (pop coordinates))) |
|
|
(when in-syntax-quote |
|
|
;; We're inside the `concat' form, but we need to discard the |
|
|
;; actual `concat' symbol from the coordinate. |
|
|
(setq next (1- next))) |
|
|
;; String coordinates are map keys. |
|
|
(if (stringp next) |
|
|
(cider--debug-goto-keyval next) |
|
|
(clojure-forward-logical-sexp next) |
|
|
(when in-syntax-quote |
|
|
(clojure-forward-logical-sexp 1) |
|
|
(forward-sexp -1) |
|
|
;; Here a syntax-quote is ending. |
|
|
(let ((match (when (looking-at "~@?") |
|
|
(match-string 0)))) |
|
|
(when match |
|
|
(setq in-syntax-quote nil)) |
|
|
;; A `~@' is read as the object itself, so we don't pop |
|
|
;; anything. |
|
|
(unless (equal "~@" match) |
|
|
;; Anything else (including a `~') is read as a `list' |
|
|
;; form inside the `concat', so we need to pop the list |
|
|
;; from the coordinates. |
|
|
(pop coordinates)))))) |
|
|
;; If that extra pop was the last coordinate, this represents the |
|
|
;; entire #(...), so we should move back out. |
|
|
(backward-up-list))) |
|
|
;; Place point at the end of instrumented sexp. |
|
|
(clojure-forward-logical-sexp 1)) |
|
|
;; Avoid throwing actual errors, since this happens on every breakpoint. |
|
|
(error (message "Can't find instrumented sexp, did you edit the source?")))) |
|
|
|
|
|
(defun cider--handle-debug (response) |
|
|
"Handle debugging notification. |
|
|
RESPONSE is a message received from the nrepl describing the input |
|
|
needed. It is expected to contain at least \"key\", \"input-type\", and |
|
|
\"prompt\", and possibly other entries depending on the input-type." |
|
|
(nrepl-dbind-response response (debug-value key coor code file point ns original-id |
|
|
input-type prompt inspect) |
|
|
(condition-case-unless-debug e |
|
|
(progn |
|
|
(pcase input-type |
|
|
("expression" (cider-debug-mode-send-reply (cider-read-from-minibuffer |
|
|
(or prompt "Expression: ")) |
|
|
key)) |
|
|
((pred sequencep) |
|
|
(when (or code (and file point)) |
|
|
;; We prefer in-source debugging. |
|
|
(when (and file point) |
|
|
(if-let ((buf (find-buffer-visiting file))) |
|
|
(if-let ((win (get-buffer-window buf))) |
|
|
(select-window win) |
|
|
(pop-to-buffer buf)) |
|
|
(find-file file)) |
|
|
(goto-char point)) |
|
|
;; But we can create a temp buffer if that fails. |
|
|
(unless (or (looking-at-p (regexp-quote code)) |
|
|
(looking-at-p (regexp-quote (cider--debug-trim-code code)))) |
|
|
(cider--initialize-debug-buffer code ns original-id)) |
|
|
(cider--debug-move-point coor)) |
|
|
;; The overlay code relies on window boundaries, but point could have been |
|
|
;; moved outside the window by some other code. Redisplay here to ensure the |
|
|
;; visible window includes point. |
|
|
(redisplay) |
|
|
(cider--debug-remove-overlays) |
|
|
(when cider-debug-use-overlays |
|
|
(cider--debug-display-result-overlay debug-value)) |
|
|
(setq cider--debug-mode-response response) |
|
|
(cider--debug-mode 1))) |
|
|
(when inspect |
|
|
(cider-inspector--value-handler nil inspect) |
|
|
(cider-inspector--done-handler (current-buffer)))) |
|
|
;; If something goes wrong, we send a "quit" or the session hangs. |
|
|
(error (cider-debug-mode-send-reply ":quit" key) |
|
|
(message "Error encountered while handling the debug message: %S" e))))) |
|
|
|
|
|
|
|
|
;;; 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, |
|
|
a number of keys will be prompted to the user." |
|
|
(interactive) |
|
|
(cider-eval-defun-at-point 'debug-it)) |
|
|
|
|
|
(provide 'cider-debug) |
|
|
;;; cider-debug.el ends here
|
|
|
|