Breakpoints in fireplace

116 views
Skip to first unread message

Matt Mower

unread,
Sep 25, 2013, 4:47:40 AM9/25/13
to vimcl...@googlegroups.com
Hi folks.

I realise I may not be doing things entirely the "clojure way" but something i really miss from Ruby is Pry and the ability to drop into a repl at any point and explore the local bindings.

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.

As long as you execute this code in the repl itself. The problem comes when you're in Vim using vim.fireplace to execute the code. Then you hang Vim.

I'm assuming this is because the nrepl process that is evaluating the code is hung waiting for input which will never arrive since, well I have no idea what it's stdin is wired up to.

But when you run lein repl you also get a lovely console repl that shares the same environment and has working I/O it seems like it would be ideal if there was some way to (temporarily) wire up the nrepl session (that you talk to over the network from vim.fireplace) to the I/O streams that the console repl session is using. If you see what I mean.

I'm pretty much a newbie at Clojure and at Vim really so my reading of the nrepl source is definitely giving me the impression this task is somewhat above my paygrade but it is something I'd find useful so I'm continuing to hunt & peck and hoping to get lucky.

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?

Kind regards,

Matt

guns

unread,
Sep 25, 2013, 11:23:00 PM9/25/13
to vimcl...@googlegroups.com, Matt Mower
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.
historical-buffers.patch

Matt Mower

unread,
Sep 26, 2013, 4:59:17 AM9/26/13
to vimcl...@googlegroups.com, Matt Mower
Hi guns.


On Thursday, 26 September 2013 04:23:00 UTC+1, guns wrote:

This is possible and potentially awesome, however it will be tricky to
implement.

First, allow me to share my debugging setup:


Thanks for that. Rob Ashton put me onto spyscope with it's #spy/p which is also useful for tracing. The problem with all of these though is that you get a dump, while the program is running, of potentially a huge gob of stuff and no real way to *explore* it.
 
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.

No, what I am really shooting for is what the Ruby "Pry" utility gives you. A way to halt the program at a specific point and explore the local state at that point.

Even if you wired in a debugger that offers (next), (step), and
(continue), vim has historically had a very bad time of integrating

Just to clarify I am not looking to be able to do anything from within Vim outside of what vim.fireplace does now. I am quite happy for the REPL to be in the console.

  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.

Okay this seems a promising approach and I will explore this. Thank you for the leg up.
 
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².

At this point my ambitions are, I hope, reasonable & limited. To be able to eval code in Vim and - if there is a #break - in it to have execution of the code interrupted by dropping into a console based REPL session where I can explore the local state with full REPL power.
 
Good luck, and tell us about your successes!


If I can make it work I will!

Thanks again.

Matt

Dave Ray

unread,
Sep 26, 2013, 11:59:39 AM9/26/13
to vimcl...@googlegroups.com, Matt Mower
There's also redl [1] which provides a break macro that suspends a program so you can inspect the current state before continuing. I haven't actually used it though. vim-redl provides some level of vim integration [2].

Dave

[1] https://github.com/dgrnbrg/redl
[2] https://github.com/dgrnbrg/vim-redl



--
You received this message because you are subscribed to the Google Groups "vimclojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vimclojure+...@googlegroups.com.
To post to this group, send email to vimcl...@googlegroups.com.
Visit this group at http://groups.google.com/group/vimclojure.
For more options, visit https://groups.google.com/groups/opt_out.

Matt Mower

unread,
Sep 26, 2013, 12:42:52 PM9/26/13
to vimcl...@googlegroups.com, Matt Mower
On Thursday, 26 September 2013 16:59:39 UTC+1, daveray wrote:
There's also redl [1] which provides a break macro that suspends a program so you can inspect the current state before continuing. I haven't actually used it though. vim-redl provides some level of vim integration [2].


Dave that's brilliant, thank you. Being able to use the redl.core/break function from a ReplHere is exactly what I needed.

Kind regards,

Matt

David Greenberg

unread,
Sep 26, 2013, 1:17:54 PM9/26/13
to vimcl...@googlegroups.com, Matt Mower
I'd also like to mention that you need to have fireplace installed to use vim-redl :)

Let me know if you run into issues--I'll be tackling the ones in the bugtracker in a couple weeks.

guns

unread,
Sep 26, 2013, 4:27:23 PM9/26/13
to vimcl...@googlegroups.com, dsg123...@gmail.com
On Thu 26 Sep 2013 at 10:17:54AM -0700, David Greenberg wrote:

> I'd also like to mention that you need to have fireplace installed to
> use vim-redl :)
>
> Let me know if you run into issues--I'll be tackling the ones in the
> bugtracker in a couple weeks.

I see have been overlooking your work to my own detriment, David. It
appears that my response to Matt essentially outlined both spyscope and
vim-redl¹! Except, of course, you refined these ideas and executed them
brilliantly.

Even the in-buffer REPL (which appears to be an extraction of the
VimClojure REPL) seems to work well, with a minimal amount of magic and
a tasteful restraint in buffer-local mappings.

I'll be sure to pay more attention in the future.

guns

¹ I did not realize that you had extracted vim-redl as a fireplace
plugin. This removes the primary reason I had ignored vim-redl in the
past ― kudos for making this happen.

David Greenberg

unread,
Sep 26, 2013, 10:15:33 PM9/26/13
to guns, vimcl...@googlegroups.com, David Greenberg
Thanks! I really appreciate the compliments :)

Wolodja Wentland

unread,
Sep 27, 2013, 6:52:20 AM9/27/13
to vimcl...@googlegroups.com
On Thu, Sep 26, 2013 at 22:15 -0400, David Greenberg wrote:
> Thanks! I really appreciate the compliments :)

They are well deserved and I like to thank you and all others who work on
Clojure plugins for vim. Splendid work guys and much appreciated!

You might not hear from your users often, but that doesn't mean that we do not
appreciate your hard work.
--
Wolodja <bab...@gmail.com>

4096R/CAF14EFC
081C B7CD FF04 2BA9 94EA 36B2 8B7F 7D30 CAF1 4EFC
signature.asc

David Greenberg

unread,
Sep 28, 2013, 9:42:40 AM9/28/13
to vimcl...@googlegroups.com, Matt Mower
I'd also like to point out #spy/t is a much more powerful tool for tracing--it includes the form, the context (part of the stacktrace), and can be configured to include a timestamp or additional context.

Most of the debugging libraries I write for Clojure are specifically to improve my Vim workflow.
Reply all
Reply to author
Forward
0 new messages