Skip to content

Commit

Permalink
Merge pull request #222 from Malabarba/more-debugger
Browse files Browse the repository at this point in the history
More debugger
  • Loading branch information
bbatsov committed Jul 1, 2015
2 parents 80d3297 + d6eefa0 commit 0edf8e2
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 18 deletions.
51 changes: 39 additions & 12 deletions src/cider/nrepl/middleware/debug.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
[clojure.tools.nrepl.misc :refer [response-for]]
[clojure.tools.nrepl.middleware.interruptible-eval :refer [*msg*]]
[cider.nrepl.middleware.util.instrument :as ins]
[cider.nrepl.middleware.inspect :refer [swap-inspector!]]
[cider.nrepl.middleware.util.cljs :as cljs]
[cider.nrepl.middleware.util.inspect :as inspect]
[clojure.walk :as walk])
(:import [clojure.lang Compiler$LocalBinding]))

Expand Down Expand Up @@ -150,8 +152,17 @@
"Read and eval an expression from the client.
extras is a map to be added to the message, and prompt is added into
the :prompt key."
[prompt extras]
(eval-with-locals (read-debug extras :expression prompt)))
([prompt extras] (read-debug-eval-expression prompt extras nil))
([prompt extras code]
(eval-with-locals (or code (read-debug extras :expression prompt)))))

(declare read-debug-command)

(defn inspect-then-read-command
"Inspect `value` and send it as part of a new `read-debug-command`."
[extras value]
(let [i (pr-str (:rendered (swap-inspector! *msg* inspect/start value)))]
(read-debug-command value (assoc extras :inspect i))))

(defn read-debug-command
"Read and take action on a debugging command.
Expand All @@ -160,22 +171,34 @@
next: Return value.
continue: Skip breakpoints for the remainder of this eval session.
out: Skip breakpoints in the current sexp.
inspect: Evaluate an expression and inspect it.
locals: Inspect local variables.
inject: Evaluate an expression and return it.
eval: Evaluate an expression, display result, and prompt again.
quit: Abort current eval session."
quit: Abort current eval session.
Response received can be any one of these values. It can also be a
map whose :response entry is one of these values, which can thus be
used to provide aditional parameters. For instance, if this map has
a :code entry, its value is used for operations such as :eval, which
would otherwise interactively prompt for an expression."
[value extras]
(let [commands (if (cljs/grab-cljs-env *msg*)
[:next :continue :out :inject :eval :quit]
[:next :continue :out :inject :eval :quit])
prompt (apply str (map #(let [[f & r] (name %)]
(apply str " (" f ")" r))
commands))]
(case (read-debug extras commands prompt)
[:next :continue :out :inspect :locals :inject :eval :quit]
[:next :continue :out :inspect :locals :inject :eval :quit])
response-raw (read-debug extras commands nil)
{:keys [code response]} (if (map? response-raw) response-raw
{:response response-raw})
extras (dissoc extras :inspect)]
(case response
:next value
:continue (do (skip-breaks! true) value)
:out (do (skip-breaks! (butlast (:coor extras))) value)
:inject (read-debug-eval-expression "Expression to inject: " extras)
:eval (let [return (read-debug-eval-expression "Expression to evaluate: " extras)]
:locals (inspect-then-read-command extras *locals*)
:inspect (->> (read-debug-eval-expression "Inspect value: " extras code)
(inspect-then-read-command extras))
:inject (read-debug-eval-expression "Expression to inject: " extras code)
:eval (let [return (read-debug-eval-expression "Expression to evaluate: " extras code)]
(read-debug-command value (assoc extras :debug-value (pr-str return))))
:quit (abort!))))

Expand Down Expand Up @@ -254,7 +277,11 @@
"eval" (h (maybe-debug msg))
"debug-input" (when-let [pro (@promises (:key msg))]
(swap! promises dissoc (:key msg))
(deliver pro (read-string input))
(try (deliver pro (read-string input))
(catch Exception e
(when-not (realized? pro)
(deliver pro :quit))
(throw e)))
(transport/send (:transport msg)
(response-for msg :status :done)))
"init-debugger" (initialize msg)
Expand Down
22 changes: 19 additions & 3 deletions src/cider/nrepl/middleware/util/instrument.clj
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,18 @@
(println "[DBG]" (not (not cider-breakfunction)) cider-coor form))
(if (and cider-breakfunction (seq cider-coor))
(list cider-breakfunction form cider-coor)
form))))
;; If the form is a list and has no metadata, maybe it was
;; destroyed by a macro. Try guessing the coor by looking at
;; the first element. This fixes `->`, for instance.
(if (listy? form)
(let [{:keys [cider-coor cider-breakfunction]} (meta (first form))
coor (if (= (last cider-coor) 0)
(pop cider-coor)
cider-coor)]
(if (and cider-breakfunction (seq cider-coor))
(list cider-breakfunction form coor)
form))
form)))))

(defn- contains-recur?
"Return true if form is not a `loop` and a `recur` is found in it."
Expand Down Expand Up @@ -230,8 +241,13 @@
(let [map-inner (fn [forms]
(map-indexed #(walk-indexed (conj coor %1) f %2)
forms))
;; Order of maps and sets is unpredictable, unfortunately.
result (cond (map? form) form
;; Maps are unordered, but we can try to use the keys (and
;; they're important enough that we're willing to risk
;; getting the position wrong).
result (cond (map? form) (into {} (map (fn [[k v]]
[k (walk-indexed (conj coor (pr-str k)) f v)])
form))
;; Order of sets is unpredictable, unfortunately.
(set? form) form
;; Borrowed from clojure.walk/walk
(list? form) (apply list (map-inner form))
Expand Down
41 changes: 41 additions & 0 deletions test/clj/cider/nrepl/middleware/debug_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,44 @@
(with-locals
(is (= '(("x" "1"))
(#'d/locals-for-message d/*locals*))))))

(deftest eval-expression-with-code
(with-locals
(is (= (#'d/read-debug-eval-expression
"Unused prompt" {:some "random", 'meaningless :map} '(inc 1))
2)))
(let [x 10]
(with-locals
(is (= (#'d/read-debug-eval-expression
"Unused prompt" {:some "random", 'meaningless :map} '(inc x))
11)))))

(deftest inspect-then-read-command
(binding [*msg* {:session (atom {})}]
(with-redefs [d/read-debug-command vector]
(let [[v m] (#'d/inspect-then-read-command {} 10)]
(is (= v 10))
(is (string? (:inspect m)))))))

(deftest debug-reader
(is (empty? (remove #(:cider-breakfunction (meta %))
(d/debug-reader '[a b c]))))
(is (:cider-breakfunction (meta (d/debug-reader '[a b c]))))
(is (= (count (remove #(:cider-breakfunction (meta %))
(d/debug-reader '[a :b 10])))
2)))

(deftest breakpoint-reader
(is (:cider-breakfunction (meta (d/breakpoint-reader '[a b c]))))
(is (= '[a :b 10 "ok"]
(remove #(:cider-breakfunction (meta %)) (d/breakpoint-reader '[a :b 10 "ok"]))))
;; Just don't error
(is (map d/breakpoint-reader '[a :b 10 "ok"])))

(deftest reader-macros
(binding [*data-readers* {'dbg d/debug-reader}]
;; Reader macro variants
(is (empty? (remove #(:cider-breakfunction (meta %)) (read-string "#dbg [a b c]"))))
(is (:cider-breakfunction (meta (read-string "#dbg [a b c]"))))
(is (= (count (remove #(:cider-breakfunction (meta %)) (read-string "#dbg [a :b 10]")))
2))))
6 changes: 3 additions & 3 deletions test/clj/cider/nrepl/middleware/util/instrument_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@
(with-redefs [d/breakpoint-reader
(fn [x] (t/with-meta-safe x {:cider-breakfunction #'bp}))]
(reset! bp-tracker #{})
(clojure.pprint/pprint (walk/macroexpand-all (#'t/instrument-tagged-code (#'d/debug-reader form))))
;; Replace #'bp with 'bp for easier comparison.
(walk/macroexpand-all (#'t/instrument-tagged-code (#'d/debug-reader form)))
;; Replace #'bp with 'bp for easier print and comparison.
(walk/postwalk #(if (= % #'bp) 'bp %) @bp-tracker)))

(deftest instrument-clauses
(are [exp res] (= (breakpoint-tester exp) res)
(are [exp res] (clojure.set/subset? res (breakpoint-tester exp))
'(cond-> value
v2 form
v3 (boogie oogie form))
Expand Down

0 comments on commit 0edf8e2

Please sign in to comment.