As I mentioned before, after starting out the new job, in a new city, with a new language, and a new computer, I started playing with GNU Emacs editor. I kind of staled with Vim, knew how to do stuff, but I wasn’t feeling very pleased by Vimscript. On the other hand I was reading good things about Emacs (many of them in Steve Yegge blog and his Drunken Rants) and always had some curiosity about Elisp.

At first I was using it just for playing at home, and kept using Vim at work, since my project was running at high speed and I was a little bit insecure — specially because I wanted to use a version from CVS that supported fancy Truetype fonts in the X11 environment. After some time, I got confident enough to use it full-time, bootstrapped my .emacs file with Barbieri’s .emacs plus minor modifications — funny thing is, for some reason I kept using Vim to edit my .emacs file for a few weeks.

Inside Emacs’ M-x info library, there is a nice document, also available on the web, Programming in Emacs Lisp. Nice because it explains Elisp but also how Emacs works at the same time — strongly recommended for those who are starting. Another good resource is Yegge’s Emergency Lisp, which is a good practical summary of the language.

During this process I kept talking to my friends about the new editor (we were all Vim users) and trying to push Emacs into them, too. It worked for at least one of them. He tried a bit, but missed some features from the editor he used to work, Textmate. We both immediatly concluded: hey, Emacs should be able to do everything that Textmate did, and more.

So he found out about yasnippet that allowed the creation of mini-templates that are triggered by short strings and then using TAB (yes, the guy has two blogs, don’t ask me why). I ended up trying to solve another problem, which is to automatically insert the closing parenthesis when you insert the opening (abstract this to use with other interesting pairs of characters). It was just using the existing skeleton-pair feature available in Emacs, with a few twists.

Show me the code

I’ll comment on my solution, but you can also grab the raw file here. By the way, I’ve used M-x htmlize-buffer to create colorized pieces of code.

;; Paren experiment
(setq skeleton-pair t)
(defvar my-skeleton-pair-alist
  '((?\) . ?\()
    (?\] . ?\[)
    (?} . ?{)
    (?" . ?")))

First we needed to activate skeleton-pair. It’s part of skeleton-mode. It allows you to type ( and get () with the point (also known as cursor) positioned inside the parens. Also allows you to selected a region and type (, and the region will be inside the parens. In this block we also create a helper association list, that we’ll be using later.

(defun my-skeleton-pair-end (arg)
  "Skip the char if it is an ending, otherwise insert it."
  (interactive "*p")
  (let ((char last-command-char))
    (if (and (assq char my-skeleton-pair-alist)
             (eq char (following-char)))
        (forward-char)
      (self-insert-command (prefix-numeric-value arg)))))

First real thing is here. We defined a function that accepts one argument. The interactive tells us how we fill the arguments when the function is called via some key or key combination interactively.

At this moment it’s nice to know that everything you type in Emacs will end up triggering some function. Even simple characters. When you type a, Emacs will look in a table and see what function it should call. Normal letters like a are bound to the function self-insert-command.

Back to the function: it’s body is very simple, if the character we typed (given by the global last-command-char) is one closing char present in our association list and the next character is the same, we just forward-char, otherwise, we add the character as usual. The idea is to bind this function to the closing chars like ) and }. That’s what we do next:

(dolist (pair my-skeleton-pair-alist)
  (global-set-key (char-to-string (first pair))
                  'my-skeleton-pair-end)
  ;; If the char for begin and end is the same,
  ;; use the original skeleton
  (global-set-key (char-to-string (rest pair))
                  'skeleton-pair-insert-maybe))

So when you type ( the function skeleton-pair-insert-maybe takes care, when you type ) our function will take care of the ending. This gives the first feature over pure skeleton-pair: when you type ( and then ) you’ll end up with the right thing () instead of ()) that the plain skeleton pair would give you.

After testing a bit, and showing to others, we realize: if I press ( and then backspace I would still have the ) hanging around. Solutions? Well, let’s rebind Backspace to be smart enough about that, right? Well, that’s what I did at first: do my thing and call the original bind for backspace backward-delete-char-untabify. It worked…

…except when some language mode rebinds backspace, like python-mode because it uses backspace also to walk thru the possible indentations in the code and we would break their binding. And guess what? Lately I was coding mostly in Python. After some research, a solution came up:

(defadvice backward-delete-char-untabify
  (before my-skeleton-backspace activate)
  "When deleting the beginning of a pair, and the ending is next char, delete it too."
  (let ((pair (assq (following-char) my-skeleton-pair-alist)))
    (and pair
         (eq (preceding-char) (rest pair))
         (delete-char 1))))

Short explanation: we say that this piece of code will run before backward-delete-char-untabify runs. It does what we need: if we are calling backspace to delete an opening paren and the closing paren is right after, we delete the closing one. After we run, the normal function will run and delete the opening one. Now python-mode or any other mode can rebind backspace as they want, everytime they use the original function, our code will run.

And that’s how we got the second feature (smart backspace). And that’s it. Actually after that, experimenting with other modes, like the cc-mode (default for C/C++ files), I discovered that the opening paren (as other opening chars) are binded to c-electric-paren to do indentation as you type.

I didn’t found an easy way to workaround this like in backspace, because the original function for ( is self-insert-char but making an advice for this function would make our code run lots and lots of times unnecessarily. The other solution would be to create advices for the c-electric-* functions. But since I’m not using this smart parens when using C, I didn’t investigate further, so if you discover another clean way to do this, please tell me.

Conclusion

I hope this post could teach you something about Emacs. The code I present here is not very important, but the concepts around it are, for example: the binding of keys to functions in Emacs, or how to improve functions by using defadvice. Just knowing these concepts is already useful: later when you need something similar, you’ll know where to look. Even better, when solving a problem, you’ll have new ideas on how to solve it.

I’m still using this code, not sure for how long. I can say it’s useful when I’m coding Python — with a few exception cases. In other modes — like when typing normal text — the smart parens are not very useful for me, and in erc (one way to use IRC inside Emacs) they are even annoying, but that’s just me. If you try it, comments are welcome. Also are welcome new ideas on how to deal with these problems.

About these ads