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.
    Post a Comment