Close last XML tag (Emacs)
I'm using Emacs for almost 10 years, but still I never wrote too much about it. Partly, the reason is that I'm not an Emacs Guru (although I'm using it for 10 years, imagine that). Emacs is so functional in itself that you don't have to become a “guru” to like it (by “guru” here I mean someone who can bring substantial improvements by writing Lisp code, such as writing a major mode).
Solving a frequent problem
For example, if I type the following:
var html = ; html.push("<table>"); if (foo.rating) html.push("<tr><td>Rating:</td><td>", foo.rating, "</td></tr>"); if (foo.user) html.push("<tr><td>User:</td><td>", foo.user, "</td></tr>"); html.push("</table>");
Let's count. I have 7 close tags in the above sample. Let's say you need 2 seconds to type a closing tag (the average is even worse). Then I've spent like 14 seconds only to close those damn XML tags.
I've done this so many times that I naturally learned to live with it. If you're working with Notepad, then there's no way you can think of helpers—you have to live with it.
However I'm not with Notepad. When I edit XML files, I use NXHTML mode, which is a masterpiece, and saves me a lot of time from closing tags and validating my work. So why shouldn't I be able to have at least a minor benefit when using other modes, such as JS?
Turns out it's easy. It would have been a million times easier if I knew Lisp, but I don't know Lisp so I needed one whole hour to write the following function. I have to use the manual at every line I type. That's a problem, but I hope I'll improve that. Yet, this function will save me many minutes a day, so it worthed spending the efforts.
Here's how I solved the “closing tags problem”:
;; define the function (defun msh-close-tag () "Close the previously defined XML tag" (interactive) (let ((tag nil)) (save-excursion (do ((skip 1)) ((= 0 skip)) (re-search-backward "</?\\([a-zA-Z0-9_-]+\\)") (cond ((looking-at "</") (setq skip (+ skip 1))) ((setq skip (- skip 1))))) (when (looking-at "<\\([a-zA-Z0-9_-]+\\)") (setq tag (match-string 1)))) (insert (concat "</" tag ">")))) ;; and bind it to some key, so we can be fast (define-key global-map [(control c) (/)] 'msh-close-tag)
Put this in your ~/.emacs and whenever you need to close a tag, you just press “C-c /”. It will look for the closest opened tag and it'll close it. It also works when you have, say, “<foo><bar></bar>|” (where “|” is the caret position)—at this point it'll enter “</foo>”.
It's not bullet-proof—it just counts closing tags and discards as many open tags, before finding the one that you need to close. This means, if you have: “<foo><bar></foo>|” and press “C-c /”, it will still enter “</foo>” although your XML is obviously invalid. I decided not to care about this case because my XML is always valid, period. ;-)
That's the first Emacs article that I publish, and I hope to write a lot more in the future. It's really the best text Editor there is and, to quote Paul Graham again, all other things being equal, it's a mistake not to use the best tool.
Update (April 2009) — I wrote since a better version of this function, pasted below.
(defun msh-close-tag () "Close the previously defined XML tag" (interactive) (let ((tag nil) (quote nil)) (save-excursion (do ((skip 1)) ((= 0 skip)) (re-search-backward "</?[a-zA-Z0-9_-]+") (cond ((looking-at "</") (setq skip (+ skip 1))) ((not (looking-at "<[a-zA-Z0-9_-]+[^>]*?/>")) (setq skip (- skip 1))))) (when (looking-at "<\\([a-zA-Z0-9_-]+\\)") (setq tag (match-string 1))) (if (eq (get-text-property (point) 'face) 'font-lock-string-face) (setq quote t))) (when tag (setq quote (and quote (not (eq (get-text-property (- (point) 1) 'face) 'font-lock-string-face)))) (if quote (insert "\"")) (insert "</" tag ">") (if quote (insert "\"")))))
- If the XML tag was defined in a string context and msh-close-tag is not called in a string context, then quotes are added automatically (this is based on font locking actually). It turns out to work best generally.
- It will properly skip "<quiclky />" closed tags now.