Using The Emacs save-restriction Special Form

Emacs has the ability to “narrow” a buffer to a region thereby making the parts of the buffer outside the region invisible. The invisible part of the buffer is completely inaccessible: you can’t move into it, you can’t change it, and, of course, you can’t see it. The table below shows the commands to narrow and widen (cancel the narrowing) buffers1.

Key Sequence Function Action
Ctrl+x n n narrow-to-region Narrow to region between point and mark
Ctrl+x n w widen Widen the buffer
Ctrl+x n p narrow-to-page Narrow to current page
Ctrl+x n d narrow-to-defun Narrow to current function

These functions are sometimes useful during interactive editing. For example, you might want to narrow to the function you’re working on so you won’t be distracted by the other code. I find, however, that they’re mostly useful in Emacs Lisp code. That brings me to the point of this post.

Xah Lee has an excellent—and, if you’re an Elisp programmer, important—post on using narrowing to avoid changing the boundaries of a region while you work in it. If you haven’t read his post, go do that now before continuing with this one.

Lee’s post is an excellent example of using narrowing in Elisp code but what I really want to talk about is the special form save-restriction. This form merely saves the current narrowing, if any, executes its body, and then restores the saved narrowing. The template is

(save-restriction
  (narrow-to-region beg end)
  (do stuff)
  (do-more stuff))

so that you can temporarily narrow to some region without affecting any narrowing already in effect. You might wonder why you would want to narrow in Elisp. Lee gives an excellent example of its usefulness but I most often use it as a sort of “set it and forget it” device when I’m writing code that can operate on a region or the entire buffer.

For example, consider the following function that counts lines in a Scheme source code file:

(defun jcs-count-scheme-lines ()
  "Count non-comment, non-blank lines in a Scheme program"
  (interactive)
  (with-region-or-buffer (b e)
    (let ((cnt 0))
      (save-excursion
        (save-restriction
          (narrow-to-region b e)
          (goto-char (point-min))
          (while (not (eobp))
            (unless (looking-at "^\\s-*$\\|\\s-*;")
              (setq cnt (1+ cnt)))
            (forward-line 1))
          (message "%d code lines" cnt))))))

The with-region-or-buffer macro binds the beginning and end of the current region if there is one or the beginning and end of buffer if there isn’t to its first and second arguments. I wrote about it here and here. I narrow to the region defined by b and e so that I don’t have to worry about whether I’m working in the whole buffer or a smaller region. In effect, I set the area I’m working in and then just pretend I’m working in the whole buffer so that I don’t have to write a lot of code worrying about whether I’m acting on a buffer or smaller region.

This is a really handy technique that can save a lot of code and effort. I use it all the time because I try to make most of my Elisp functions work on either regions or the entire buffer.

The code above illustrates another point. The save-excursion special form is similar to save-restriction except that it saves and restores the point, mark, state of mark activation, and current buffer. When you use them together, as I did here, you should put the save-excursion in the outermost position.

Footnotes:

1 Narrowing is disabled by default. When you first use it, Emacs will ask you if you want to enable it temporarily or permanently. You can enable it yourself by putting (put 'narrow-to-region 'disabled nil) in your .emacs or init.el file.

This entry was posted in Programming and tagged , . Bookmark the permalink.