Emacs for TextMate junkies 6

Posted by sjs
on Saturday, June 23

Update #1: What I first posted will take out your < key by mistake (it's available via C-q <), it has since been revised to Do The Right Thing.

Update #2: Thanks to an anonymouse[sic] commenter this code is a little cleaner.

Update #3: I should read the Emacs manual sometime, especially since I have it in dead-tree form. Check out skeleton pairs in the Emacs manual.

Despite my current infatuation with Emacs there are many reasons I started using TextMate, especially little time-savers that are very addictive. I'll talk about one of those features tonight. When you have text selected in TextMate and you hit say the ' (single quote) then TextMate will surround the selected text with single quotes. The same goes for double quotes, parentheses, brackets, and braces. This little trick is one of my favourites so I had to come up with something similar in Emacs. It was easy since a mailing list post has a solution for surrounding the current region with tags, which served as a great starting point.

1
2
3
4
5
6
7
(defun surround-region-with-tag (tag-name beg end)
      (interactive "sTag name: \nr")
      (save-excursion
        (goto-char beg)
        (insert "<" tag-name ">")
        (goto-char (+ end 2 (length tag-name)))
        (insert "</" tag-name ">")))

With a little modification I now have the following in my ~/.emacs file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
;; help out a TextMate junkie

(defun wrap-region (left right beg end)
  "Wrap the region in arbitrary text, LEFT goes to the left and RIGHT goes to the right."
  (interactive)
  (save-excursion
    (goto-char beg)
    (insert left)
    (goto-char (+ end (length left)))
    (insert right)))

(defmacro wrap-region-with-function (left right)
  "Returns a function which, when called, will interactively `wrap-region-or-insert' using LEFT and RIGHT."
  `(lambda () (interactive)
     (wrap-region-or-insert ,left ,right)))

(defun wrap-region-with-tag-or-insert ()
  (interactive)
  (if (and mark-active transient-mark-mode)
      (call-interactively 'wrap-region-with-tag)
    (insert "<")))

(defun wrap-region-with-tag (tag beg end)
  "Wrap the region in the given HTML/XML tag using `wrap-region'. If any
attributes are specified then they are only included in the opening tag."
  (interactive "*sTag (including attributes): \nr")
  (let* ((elems    (split-string tag " "))
         (tag-name (car elems))
         (right    (concat "</" tag-name ">")))
    (if (= 1 (length elems))
        (wrap-region (concat "<" tag-name ">") right beg end)
      (wrap-region (concat "<" tag ">") right beg end))))

(defun wrap-region-or-insert (left right)
  "Wrap the region with `wrap-region' if an active region is marked, otherwise insert LEFT at point."
  (interactive)
  (if (and mark-active transient-mark-mode)
      (wrap-region left right (region-beginning) (region-end))
    (insert left)))

(global-set-key "'"  (wrap-region-with-function "'" "'"))
(global-set-key "\"" (wrap-region-with-function "\"" "\""))
(global-set-key "`"  (wrap-region-with-function "`" "`"))
(global-set-key "("  (wrap-region-with-function "(" ")"))
(global-set-key "["  (wrap-region-with-function "[" "]"))
(global-set-key "{"  (wrap-region-with-function "{" "}"))
(global-set-key "<"  'wrap-region-with-tag-or-insert) ;; I opted not to have a wrap-with-angle-brackets

Download wrap-region.el

That more or less sums up why I like Emacs so much. I wanted that functionality so I implemented it (barely! It was basically done for me), debugged it by immediately evaluating sexps and then trying it out, and then once it worked I reloaded my config and used the wanted feature. That's just awesome, and shows one strength of open source.

Comments

Leave a response

  1. Ed PimanJune 23, 2007 @ 02:36 PM

    I just tried that out using Aquamacs, and it worked like a charm. Thanks for sharing.

  2. anonymouse@anonymouse.comJune 24, 2007 @ 12:03 PM

    Nice. But you can refactor it to avoid all that boilerplate at the end of the file (that you snipped from the blog post).

    (defmacro wrap-region-with-function (left right) "Returns a function which, when called, will interactively wrap-region-or-insert using left and right." `(lambda () (interactive) (wrap-region-or-insert ,left ,right) ))

    When evaluated, this will macroexpand into wrap-region-with-parens-or-insert. Then define keys as follows:

    (global-set-key "(" (wrap-region-with-function "(" ")" ) )

  3. sjsJune 24, 2007 @ 04:44 PM

    Ed: Glad that you like it! :)

    anonymouse: Thanks for that! I wanted to do that but I don't know enough ELisp yet. I mostly need to read up on quasiquote & related stuff, and implement it in my scheme interpreter so I understand it well.

  4. MikeHJune 25, 2007 @ 05:17 AM

    I've been looking for this for a long time, and even tried it myself, but I didn't do it nearly as well as you did.

    One thing that Textmate (and Eclipse, for that matter) does is that if you enter one of the wrapped characters without text selected, it will enter the ending character as well and put the cursor between the two.

    IOW, if you are typing and enter "(" the screen will show "(_)", where _ is the cursor.

    Anyway, I made a small change to wrap-region-or-insert to do just that.

    (defun wrap-region-or-insert (left right) "Wrap the region with `wrap-region' if an active region is marked, otherwise insert LEFT at point." (interactive) (if (and mark-active transient-mark-mode) (wrap-region left right (region-beginning) (region-end)) (insert left) (insert right) (backward-char) ) )

  5. fooJune 25, 2007 @ 08:34 AM

    Check out paredit.el

    http://mumble.net/~campbell/emacs/paredit.el

  6. sjsJune 25, 2007 @ 10:48 AM

    Mike: Thanks for that. It'll take some getting used to without TextMate overwriting ending delimiters, but foo's link to paredit.el seems to hold the cure for that ailment.

    When I have some time to go over paredit.el I'll probably have a nice update for the wrap-region family of functions.

Comment