Monday, September 17, 2012

emacs lisp: removing a lamba hook

disclaimer: I know almost nothing about emacs lisp, so please forgive any mistakes or incorrect assumptions.

I've been using Clojure for over 4 years at this point, but it's generally been on projects that are mostly Java with a few small components in Clojure. Given that context my teammates preferred that we stick with IntelliJ; however, things have recently changed and the emacs journey has begun.

Taking emacs from 'factory settings' to 'impressive' for me was as easy as getting started with emacs-live.
Emacs Live can be summarized as:
  • a nice structured approach to organising your Emacs config
  • modular in that functionality is organised by discrete packs
  • Indeed, Emacs Live does structure your config nicely; however, in it's own words: Emacs Live is also an opinionated set of defaults. I like all of the defaults... except one: rainbow-delimiters

    I get that people like rainbow-delimeters, and I would never try to convince you not to use them - they're just not for me, and I needed to find out how to get rid of them.

    Emacs Live adds rainbow-delimiters in two different ways. The following code shows how Emacs Live uses add-hook to enable rainbow delimiters for scheme, emacs-lisp, & lisp.
    (dolist (x '(scheme emacs-lisp lisp))
      (add-hook 
        (intern (concat (symbol-name x) "-mode-hook"))
        'rainbow-delimiters-mode))
    The code required to remove rainbow-delimiters from scheme, emacs-lisp, & lisp is very straightforward, and can be found below.
    (dolist (x '(scheme emacs-lisp lisp))
      (remove-hook 
        (intern (concat (symbol-name x) "-mode-hook"))
        'rainbow-delimiters-mode))
    As you can see, replacing add-hook with remove-hook will remove the hook that Emacs Live added for me. Since Emacs Live loads my personal settings last, my remove should successfully work every time. It's a best practice that you create hooks that can be run in any order - and this change is obviously order specific; however, I can't think of a way to follow the hook best practice without hacking the emacs-live checkout. Therefore, it seems like this solution is the most pragmatic.

    The next snippet of code is how Emacs Live adds rainbow delimiters (& a few other things) to clojure-mode.
    (add-hook 'clojure-mode-hook
              (lambda ()
                (enable-paredit-mode)
                (rainbow-delimiters-mode)
                (add-to-list 'ac-sources 'ac-source-yasnippet)
                (setq buffer-save-without-query t)))
    The previous hook was easy to remove, since I knew exactly what function I needed to remove. This hook is more of a problem, since it's anonymous. Additionally, this function is defined within Emacs Live code, so simply changing it to a named function isn't an option (since I don't want to modify the emacs-live checkout).

    Finding a solution wasn't very hard. The first thing I did was try to get a list of the functions that will be fired by the hook. This is as simple as printing, as the following code shows.
    (print clojure-mode-hook)
    I put that simple print statement in my .emacs-live.el, restarted emacs, went to the *Messages* buffer, and found the following line printed out.
    ((lambda nil 
             (enable-paredit-mode)
             (rainbow-delimiters-mode)
             (add-to-list (quote ac-sources) (quote ac-source-yasnippet)) 
             (setq buffer-save-without-query t)))
    As you can see, clojure-mode-hook has a list of the functions that have been added via add-hook. With this information, I added the following code, which first removes the existing hook and then adds a new anonymous function with everything previously specified - sans rainbow-delimiters.
    (remove-hook 'clojure-mode-hook (first clojure-mode-hook))
    
    (add-hook 'clojure-mode-hook
              (lambda ()
                (enable-paredit-mode)
                (add-to-list 'ac-sources 'ac-source-yasnippet)
                (setq buffer-save-without-query t)))
    The above snippet removes my unwanted lambda hook and adds a new hook with everything I do want.

    Is this fragile? You bet. If things change in Emacs Live, I'll need to mirror those changes in my .emacs-live.el - which is why it's recommended that you don't rely on ordering when adding and removing hooks. However, given the situation, this seems like a pragmatic solution.

    Feedback definitely welcome.

    4 comments:

    1. I too use Emacs Live. Such an awesome set of defaults.

      My approach to this issue was pretty lazy: just set each individual parens color to the 'regular' one.

      I worked a lot in my theme in general, as Overtone's is a bit too flashy - I wanted to keep the dark feel though. Fancy checking it out?

      https://github.com/vemv/emacs-live (includes screenshot) (-> packs/user/user-pack/lib/vemv.theme.el)

      ReplyDelete
    2. Anonymous6:20 PM

      I found that just disabling the colour live pack did the trick for me (and saved my aching retinas). I did that by adding the following to my ./live-packs/me-pack/init.el to override which packs it loads. This should be compatible with future live updates and I think is in line with the way you're meant to use the packs.

      (live-load-config-file "bindings.el")
      (live-use-packs '(live/foundation-pack
      live/clojure-pack
      live/lang-pack
      live/power-pack))

      ReplyDelete
    3. Ola Bini9:02 PM

      Another alternative would be to override rainbow-delimiter-mode and make it a no op.

      ReplyDelete
    4. good stuff, thanks for the comments.

      ReplyDelete

    Note: Only a member of this blog may post a comment.