<-- home

Wrapping Emacs Functions to Configure CIDER

I do Clojure development in Emacs using CIDER. A nice way to get a CIDER repl started in a clojure project is to run ~cider-jack-in~. CIDER will drops you in a default repl that is configured for your environment, but I've found that it's sometimes useful to pass additional parameters when I am are starting a repl. I'd like to be prompted after ~cider-jack-in~ for additional options that will be passed to the ~clj~ process, for instance to enable certain classpath aliases like ~:test~.

Emacs supports wrapping existing functions with a feature called Advice.

Here was my first attempt at wrapping a function:

(defun cider-prompt-for-jack-in-options ()
  (interactive)
  (message "Fake Prompt"))

(advice-add 'cider-jack-in-global-options :before #'cider-prompt-for-jack-in-options)

We can test that our advice is actually applied to the function by running ~C-h f cider-jack-in-global-function~ and checking for the ~:before :advice~ section in the doc output.

#+BEGIN_SRC cider-jack-in-global-options is a compiled Lisp function in cider.el.

(cider-jack-in-global-options PROJECT-TYPE)

:before advice: cider-prompt-for-jack-in-options #+END_SRC

I tested to make sure that the ~Fake Prompt~ did indeed print when I ran ~cider-jack-in~.

Here's my first stab at taking options from the minibuffer with a default value.

(defun cider-prompt-for-jack-in-options ()
  (interactive)
  (message "Options are %s" (read-string "Enter global-options: " "-A:ok")))

It has an initial value of ~-A:ok~ in the minibuffer, and then allows you to edit that to whatever you want.

But my goal is to take the current value returned by ~cider-jack-in-global-options~ and allow the user to modify it, so I'll edit my ~advice-add~ function to use an ~:around:~ wrapper instead of a ~:before~ wrapper.

(defun cider-prompt-for-jack-in-options (orig-fn project-type)
  (interactive)
  (let ((res (funcall orig-fn project-type)))
    (read-string "Enter global-options: " res)))

(advice-add 'cider-jack-in-global-options :around #'cider-prompt-for-jack-in-options)

Now my function takes the original function as an argument along with the parameter passed to that argument. Then we can call the original function ourselves with ~funcall~, which we have to use because elisp is a Lisp-2.

The result of the cider-jack-in-global-options is set as the initial value of the ~read-string~, allowing you the user to change the value, or quickly hit the enter key and be on your way.

Footnote

I learned while writing this that there are multiple overlapping ways to advise a function in Emacs. There is a macro ~defadvice~ which according to this page in the Emacs manual is "old" and "largely obselete". Associated obselete functions from that era include ~ad-activate~ and ~ad-deactivate~ which turn on and off all advice for a function. And there is ~ad-enable-advice~ and ~ad-disable-advice~ which will enable or disable a particular piece of advice. I belive all of those functions should be considered deprecated.

- Adam Frey, August 2019

<-- home