Announcing delim-kill.el

A few weeks ago, I had the chance to attend a few courses on Perl and one on giving technical presentations, held by Perl guru and uber-geek Damien Conway. They were fantastic—just like the ones last year—so I should really write a post of its own about them. Strongly recommended.

But for now, this is about one particular thing he said about editing: you need to have a shortcut in your editor that cuts or copies the text between two given characters around cursor, or point in Emacs lingo. This lets you do many frequent editing tasks easily. Say you’re going through a source file and want to move {}-delimited blocks around, or copy the field you’re on in a CSV file, or the current section in a Markdown file (from # to the next #, roughly).

Damian showed this off in Vim. I knew right away that I wanted it in Emacs. There are of course specialized functions for doing things with certain groups of delimiters, such as the ubiquitous sexp-based functions in Emacs, described extensively on the EmacsWiki. But these rely on pre-configured groups of delimiters, and to kill the text, you have to do one extra operation. I liked the idea of having a single function, and thus key binding, to do all of it. And most importantly, it seemed an interesting challenge for my still-developing Emacs and Lisp skills, so I didn’t even do a lot of research before I dug right in and wrote delim-kill.el.

delim-kill.el contains a single convenience function for editing structured data: delim-kill. Given two characters FROM and TO, delim-kill kills the text between the first occurrence of FROM before point and the first occurrence of TO after point. FROM and TO may be identical.

If FROM and TO are not identical, the function preserves the balance between the two characters: For each FROM that is encountered while looking for TO, one additional TO is required; and vice versa. For example, in “{ foo X{bar} baz }”, with X being point and “{” and “}” as delimiters, the text “{ foo {bar} baz }” will be killed, not “{ foo {bar}”.

I had fun writing this. It turned out to be a bit harder than I expected, mainly due to all the corner cases like point being on one of the delimiters—like most programming, really. I wrote plenty of unit tests to be sure to handle these cases. It’s very little code now, but when I first had a working version, it was twice as big. Once I had it working, I could see lots of symmetry between the different cases that I could extract into shared code.

Also, I finally learned the condition-case error handling of Emacs. And being able to pass functions as arguments is just great, coming from my mostly-Java day job.

Try it out if it sounds useful to you, and let me know what you think.

Slightly edited 2010-05-16.

About these ads

Tags: , , , ,

9 Responses to “Announcing delim-kill.el”

  1. Paul Rodriguez Says:

    This is handy.

    I presume you meant the last parameter to interactive in delim-kill to be P, not p. As it is an interactive called to delim-kill will never call kill-region, only kill-ring-save.

  2. ScoBe Says:

    I thought I must have something pretty messed up in my .emacs, since delim-kill doesn’t work. But Paul’s suggestion puts all to right. Thanks for this.

  3. thomas11 Says:

    Thanks Paul! Fixed.

  4. dff Says:

    Thanks for writing this. This was a problem I only subconsciously knew I had.

  5. gL Says:

    Thank you for providing this handy tool. Works like a charm!

    To my mind there may be two approvements:
    – handling composite delimiters
    in most of the time from-char “(” will be complemented by to-char “)”
    – possibility to keep both delimiters
    often only the content between the delimiters should be killed; delimiters
    themselves should be retained.

    The handling of composite delimiters could be managed by intercepting the to-char variable. If equals ?\r (empty, not specified) then look up possible composite char and assign the value to to-char

    Maybe a prefix command to toggle between killing/keeping delimiters

  6. thomas11 Says:

    Thanks for your suggestions, gL!

    I don’t think handling complementary delimiters is useful enough. I want to keep delim-kill simple and free of configuration, but more importantly, you wouldn’t really gain much. Most of the typical complementary delimiters like () and {} are right next to each other. You’re already holding shift for the first one. So from there it’s one more keystroke that’s hardly noticeable. Many other typical delimiters are their own counterparts, like comma and space. For those you just hammer the key twice.

    The possibility of keeping the delimiters when killing is something I’ve been thinking about. I’m already using the prefix argument to toggle between killing and copying, though. Maybe it would be more useful to always kill, because you can re-insert the text with a simple C-y. That would free the prefix arg.

  7. gL Says:

    I agree. Keep things simple! And indeed there is no (keyboard) efficiency gain by handling complementary delimiters.

    Your suggested approach may be worth considering

    Best regards

  8. Vadim Grinshpun Says:

    This could be pretty useful. However, the function cannot currently deal with quoting levels (i.e., delimiters that are quoted do not need to match, most likely).

    E.g., for the following line, delim-kill on braces will get confused by the quoted open brace:
    if ( $x ) { %h = ( brace => “{” ); } else { print “whatever”; }

    (it’s a contrived example, but makes the point).
    Perhaps this is outside of the scope of what this function aims to do, but handling this would certainly make it more useful.

  9. thomas11 Says:

    Hi Vadim, thanks for your comment and sorry for the late reply.

    I see your point, and will think about implementing this. I hope it doesn’t get too complicated—there’s also the backslash-escape to consider, for instance. And for Perl, we would want to ignore anything inside a // regular expression…

Comments are closed.


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: