On Wed 25 Sep 2013 at 01:47:40AM -0700, Matt Mower wrote:
> The 1st edition Joy Of Clojure has a break macro that gives you this
> functionality with some clever bits and pieces wrapped around a call
> to clojure.main/repl. You can then insert a (break) call at any point
> and get a repl with the local bindings available.
…
> Can anyone offer any advice or guidance? And, most especially, a
> warning if this is going to be completely impossible so I can stop
> wasting my time?
This is possible and potentially awesome, however it will be tricky to
implement.
First, allow me to share my debugging setup:
;;
;; In ~/.lein/profiles.clj
;; From
https://github.com/guns/haus/blob/1c7912c7119636fa18ac0f0cf7ff2f2463dbdddb/etc/%25lein/profiles.clj#L55-L83
;;
{:user {:init-ns user
:init
(do
(require 'clojure.pprint
'clojure.tools.trace)
(defmacro p [& xs]
`(do (clojure.pprint/pprint
(zipmap '~(reverse xs) [~@(reverse xs)]))
~(last xs)))
(defmacro dump-locals []
`(clojure.pprint/pprint
~(into {} (map (fn [l] [`'~l l]) (reverse (keys &env))))))
(defmacro trace
([expr] `(trace *ns* ~expr))
([nspace expr]
`(try (clojure.tools.trace/trace-ns ~nspace)
~expr
(finally (clojure.tools.trace/untrace-ns ~nspace))))))}}
These three macros allow me to inspect local values, dump local
variables, trace function calls, and are globally available from the
user namespace. The output of the pprint calls are available through the
:Last command¹.
I find this to be an acceptable alternative to a full blown debugger,
_especially_ given the minimal amounts of mutable state in Clojure
programs.
Having the ability to step through functions like gdb would be very
nice, but it does not sound like the debug REPL you are describing has
this ability either.
Even if you wired in a debugger that offers (next), (step), and
(continue), vim has historically had a very bad time of integrating
debuggers because of its synchronicity (see vim-clewn and Pyclewn). I
have learned to love gdb in a tmux pane, having given up any notions of
debugging C from within vim.
So personally, I would not bother to try to drive a debug REPL via
fireplace. This is far from impossible, but will require using
fireplace's private s:eval() function and handling the nREPL response
map yourself. This is not worth the work if all you want to do is
inspect local bindings; the dump-locals macro above will do that just
fine.
That said, if this debug REPL is awesome and you don't mind using the
console REPL, here are some tips:
* Create a wrapper around fireplace#eval() that allows an early return:
function! DebugEval(expr)
return fireplace#eval(
\ '(binding [user/*eval-promise* (promise)]'
\ ' (future (deliver user/*eval-promise* (do ' . a:expr . ')))'
\ ' @user/*eval-promise*)'
\ )
endfunction
Then have (break) deliver an early response to return control to vim:
(ns user)
(def ^:dynamic *eval-promise* nil)
(defmacro break []
(deliver *eval-promise* "Debug REPL started")
…)
Now when (user/break) is called, fireplace will receive an early
response while the original eval continues in a future.
After delivering *eval-promise*, (break) should rebind *in* and *out*
to the running console's Reader and Writer before launching its REPL.
If you are successful, the `lein repl` console should have a REPL at
the breakpoint, while fireplace continues as normal.
* Use a Tmux/GNU Screen plugin to send text from vim to the console REPL
to avoid manual copy and paste.
There are many variations on this, but these plugins essentially
enable context-free copy/paste to an arbitrary interactive process.
This is normally inferior to fireplace's <Plug>FireplacePrint mapping,
but in this case it is advantageous since fireplace does not expose a
raw eval() function.
Finally, if you really do desire tighter integration of Vim + a Clojure
debugger, it's possible to build one atop fireplace, AND make it work
well².
Aside from the private s:eval() function³, Tim Pope did a great job
making fireplace modular and hackable. In particular, the fireplace#.*
functions are simple to understand, and may effectively be used as a
vim<->nREPL library.
Good luck, and tell us about your successes!
guns
¹ :Last buffers comment out stdout and are immutable. I find it more
useful to treat :Last buffers like the old VimClojure result buffers,
where stdout (from pprint) is syntax highlighted and the contents are
mutable and evalable. The attached patch will make this so.
² But please don't try to emulate an interactive prompt! Vim isn't
Emacs, and will never be (unfortunately). Just (eval) forms without
the use of a prompt, like fireplace.
³ I suspect tpope made this private to avoid receiving even more bug
reports from novice users. IIRC there is a Github issue filed by Chas
Emerick to make this public.