Chapter 2. Simple New Commands

In this chapter we’ll develop several very small Lisp functions and commands, introducing a wealth of concepts that will serve us when we tackle larger tasks in the chapters to follow.

Traversing Windows

When I started using Emacs, I was dissatisfied with the keybinding C-x o, other-window. It moves the cursor from one Emacs window into the next. If I wanted to move the cursor to the previous window instead, I had to invoke other-window with -1 as an argument by typing C-u - 1 C-x o, which is cumbersome. Just as cumbersome was pressing C-x o repeatedly until I cycled through all the windows and came back around to what had been the “previous” one.

What I really wanted was one keybinding meaning “next window” and a different keybinding meaning “previous window.” I knew I could do this by writing some new Emacs Lisp code and binding my new functions to new keybindings. First I had to choose those keybindings. “Next” and “previous” naturally suggested C-n and C-p, but those keys are bound to next-line and previous-line and I didn’t want to change them. The next best option was to use some prefix key, followed by C-n and C-p. Emacs already uses C-x as a prefix for many two-keystroke commands (such as C-x o itself), so I chose C-x C-n for “next window” and C-x C-p for “previous window.”

I used the Help subcommand describe-key[8] to see whether C-x C-n and C-x C-p were already bound to other commands. I learned that C-x C-n was the keybinding for set-goal-column, and C-x C-p was the keybinding for mark-page. Binding them to commands for “next window” and “previous window” would override their default bindings. But since those aren’t commands I use very often, I didn’t mind losing the keybindings for them. I can always execute them using M-x.

Once I’d decided to use C-x C-n for “next window,” I had to bind some command to it that would cause “next window” to happen. I wanted a “next window” function that would move the cursor into the next window by default—just like C-x o, which invokes other-window. So creating the keybinding for C-x C-n was a simple matter of putting

(global-set-key "C-xC-n" 'other-window)

into my .emacs. Defining a command to bind to C-x C-p was trickier. There was no existing Emacs command meaning “move the cursor to the previous window.” Time to define one!

Defining other-window-backward

Knowing that other-window can move the cursor to the previous window when given an argument of -1, we can define a new command, other-window-backward, as follows:

(defun other-window-backward ()
  "Select the previous window."
  (interactive)
  (other-window -1))

Let’s look at the parts of this function definition.

  1. A Lisp function definition begins with defun.

  2. Next comes the name of the function being defined; in this case, I’ve chosen other-window-backward.

  3. Next comes the function’s parameter list.[9] This function has no parameters, so we specify an empty parameter list.

  4. The string "Select the previous window." is the new function’s documentation string, or docstring. Any Lisp function definition may have a docstring. Emacs displays the docstring when showing online help about the function, as with the commands describe-function (M-? f) or apropos.

  5. The next line of the function definition, (interactive), is special. It distinguishes this function as an interactive command.

    A command, in Emacs, is a Lisp function that can be invoked interactively, which means it can be invoked from a keybinding or by typing M-x command-name. Not all Lisp functions are commands, but all commands are Lisp functions.

    Any Lisp function, including interactive commands, can be invoked from within other Lisp code using the (function arg…) syntax.

    A function is turned into an interactive command by using the special (interactive) expression at the beginning of the function definition (after the optional docstring). More about this “interactive declaration” below.

  6. Following the name of the function, the parameter list, the docstring, and the interactive declaration is the body of the function, which is simply a sequence of Lisp expressions. This function’s body is the sole expression (other-window -1), which invokes the function other-window with an argument of -1.

Evaluating the defun expression defines the function. It’s now possible to call it in Lisp programs by writing (other-window-backward); to invoke it by typing M-x other-window-backward RET; even to get help on it by typing M-? f other-window-backward RET.[10] Now all that’s needed is the keybinding:

(global-set-key "C-xC-p" 'other-window-backward)

Parameterizing other-window-backward

This keybinding does what we need, but we can improve on it a bit. When using C-x o (or, now, C-x C-n) to invoke other-window, you can specify a numeric prefix argument n to change its behavior. If n is given, other-window skips forward that many windows. For instance, C-u 2 C-x C-n means “move to the second window following this one.” As we’ve seen, n may be negative to skip backward some number of windows. It would be natural to give other-window-backward the ability to skip backward some number of windows when a prefix argument n is given—skipping forward if n is negative. As it is, other-window-backward can only move backward one window at a time.

In order to change it, we must parameterize the function to take one argument: the number of windows to skip. Here’s how we do that:

(defun other-window-backward (n)
  "Select Nth previous window."
  (interactive "p")
  (other-window (- n)))

We’ve given our function a single parameter named n. We’ve also changed the interactive declaration to (interactive "p"), and we’ve changed the argument we pass to other-window from -1 to (- n). Let’s look at these changes, starting with the interactive declaration.

An interactive command is, as we have observed, a kind of Lisp function. That means that the command may take arguments. Passing arguments to a function from Lisp is easy; they simply get written down in the function call, as in (other-window -1). But what if the function is invoked as an interactive command? Where do the arguments come from then? Answering this question is the purpose of the interactive declaration.

The argument to interactive describes how to obtain arguments for the command that contains it. When the command takes no arguments, then interactive has no arguments, as in our first draft of other-window-backward. When the command does take arguments, then interactive takes one argument: a string of code letters, one code letter per argument being described. The code letter p used in this example means, “if there is a prefix argument, interpret it as a number, and if there is no prefix argument, interpret that as the number 1.”[11] The parameter n receives the result of this interpretation when the command is invoked. So if the user invokes other-window-backward by typing C-u 7 C-x C-p, n will be 7. If the user simply types C-x C-p, n will be 1. Meanwhile, other-window-backward can also be called non-interactively from other Lisp code in the normal way: (other-window-backward 4), for example.

The new version of other-window-backward calls other-window with the argument (- n). This computes the negative of n by passing it to the function -. (Note the space between the - and the n.) The function - normally performs subtraction—for instance, (- 5 2) yields 3—but when given only one argument, it negates it.

In the default case, where n is 1, (- n) is -1 and the call to other-window becomes (other-window -1)—precisely as in the first version of this function. If the user specifies a numeric prefix argument—C-u 3 C-x C-p, say—then we call (other-window -3), moving three windows backward, which is exactly what we want.

It’s important to understand the difference between the two expressions (- n) and -1. The first is a function call. There must be a space between the function name and the argument. The second expression is an integer constant. There may not be a space between the minus sign and the 1. It is certainly possible to write (- 1) (though there’s no reason to incur the cost of a function call when you can alternatively write -1). It is not possible to write -n, because n is not a constant.

Making the Argument Optional

There’s one more improvement we can make to other-window-backward, and that’s to make the argument n optional when invoked from Lisp code, just as giving a prefix argument is optional when invoking other-window-backward interactively. It should be possible to pass zero arguments (like this: (other-window-backward)) and get the default behavior (as if calling this: (other-window-backward 1)). Here’s how that’s done:

(defun other-window-backward (&optional n)
  "Select Nth previous window."
  (interactive "p")
  (if n
      (other-window (- n))   ;if n is non-nil
    (other-window -1)))      ;if n is nil

The keyword &optional appearing in a parameter list means that all subsequent parameters are optional. The function may be called with or without a value for an optional parameter. If no value is given, the optional parameter gets the special value nil.

The symbol nil is special in Lisp for three reasons:

  • It designates falsehood. In the Lisp structures that test a true/false condition—if, cond, while, and, or, and not—a value of nil means “false” and any other value means “true.” Thus, in the expression

    (if n
        (other-window (- n))
      (other-window -1))

    (which is Lisp’s version of an if-then-else statement), first n is evaluated. If the value of n is true (non-nil), then

    (other-window (- n))

    is evaluated, otherwise

    (other-window -1)

    is evaluated.

    There is another symbol, t, that designates truth, but it is less important than nil. See below.

  • It is indistinguishable from the empty list. Inside the Lisp interpreter, the symbol nil and the empty list () are the same object. If you call listp, which tests whether its argument is a list, on the symbol nil, you’ll get the result t, which means truth. Likewise, if you call symbolp, which tests whether its argument is a symbol, on the empty list, you’ll get t again. However, if you call symbolp on any other list, or listp on any other symbol, you’ll get nil—falsehood.

  • It is its own value. When you evaluate the symbol nil, the result is nil. For this reason, unlike other symbols, nil doesn’t need to be quoted when you want its name instead of its value, because its name is the same as its value. So you can write

    (setq x nil)      ;assign nil to variable x

    instead of writing

    (setq x 'nil)

    although both will work. For the same reason, you should never ever assign a new value to nil,[12] even though it looks like a valid variable name.

Another function of nil is to distinguish between proper and improper lists. This use is discussed in Chapter 6.

There is a symbol, t, for designating truth. Like nil, t is its own value and doesn’t need to be quoted. Unlike nil, t isn’t mysteriously the same object as something else. And also unlike nil, which is the only way to denote falsehood, all other Lisp values denote truth just like t does. However, t is useful when all you mean is truth (as in the result of symbolp) and you don’t want to choose some arbitrary other Lisp value, like 17 or "plugh", to stand for truth.

Condensing the Code

As mentioned before, the expression

(if n                     ;if this...
    (other-window (- n))  ;...then this
  (other-window -1))      ;...else this

is the Lisp version of an if-then-else statement. The first argument to if is a test. It is evaluated to see whether it yields truth (any expression except nil) or falsehood (nil). If it’s truth, the second argument—the “then” clause—is evaluated. If it’s falsehood, the third argument—the “else” clause (which is optional)—is evaluated. The result of if is always the result of the last thing it evaluates. See Appendix B, for a summary of if and of Lisp’s other flow-control functions, such as cond and while.

In this case, we can make the if expression more concise by factoring out the common subexpressions. Observe that other-window is called in both branches (the “then” and the “else” clauses) of the if. The only thing that varies, depending on n, is the argument that gets passed to other-window. We can therefore rewrite this expression as:

(other-window (if n (- n) -1))

In general,

(if test
    (a  b)
  (a  c))

can be shortened to (a (if test b c)).

We can factor out common subexpressions again by observing that in both branches of the if, we’re looking for the negative of something—either the negative of n or the negative of 1. So

(if n (- n) -1)

can become (- (if n n 1)).

Logical Expressions

An old Lisp programmers’ trick can now be used to make this expression even more concise:

(if n n 1) ≡ (or n 1)

The function or works like the logical “or” in most languages: if all of its arguments are false, it returns falsehood, otherwise it returns truth. But Lisp’s or has an extra bit of usefulness: it evaluates each of its arguments in order until it finds one that’s non-nil, then it returns that value. If it doesn’t find one, it returns nil. So the return value from or isn’t merely false or true, it’s false or the first true value in the list. This means that generally speaking,

(if a  a  b)

can be replaced by

(or a  b)

In fact, it often should be written that way because if a is true, then (if a a b) will evaluate it twice whereas (or a b) won’t. (On the other hand, if you specifically want a evaluated twice, then of course you should use if.) In fact,

(if a  a                 ;if a is true, return a
  (if b  b               ;else if b is true, return b
    ...
      (if y  y  z)))     ;else if y is true, return y, else z

(which might look artificial here but is actually a pretty common pattern in actual programs) can be changed to the following form.

(or a  b ... y  z)

subject to the warning about evaluating expressions multiple times.

Similarly,

(if a
  (if b
    ...
      (if y  z)))

(note that none of the ifs in this example has an “else” clause) can also be written as

(and a  b ... y  z)

because and works by evaluating each of its arguments in order until it finds one that’s nil. If it finds one, it returns nil, and if it doesn’t find one, it returns the value of the last argument.

One other shorthand to watch out for: some programmers like to turn

(if (and a  b ... y) z)

into

(and a  b ... y  z)

but not me because, while they’re functionally identical, the former has shades of meaning—“do z if a through y are all true”—that the latter doesn’t, which could make it easier for a human reader to understand.

The Best other-window-backward

Back to other-window-backward. Using our factored-out version of the call to other-window, the function definition now looks like this:

(defun other-window-backward (&optional n)
  "Select Nth previous window."
  (interactive "p")
  (other-window (- (or n 1))))

But the best definition of all—the most Emacs-Lisp-like—turns out to be:

(defun other-window-backward (&optional n)
  "Select Nth previous window."
  (interactive "P")
  (other-window (- (prefix-numeric-value n))))

In this version, the code letter in the interactive declaration is no longer lowercase p, it’s capital P; and the argument to other-window is (- (prefix-numeric-value n)) instead of (- (or n 1)).

The capital P means “when called interactively, leave the prefix argument in raw form and assign it to n.” The raw form of a prefix argument is a data structure used internally by Emacs to record the prefix information the user gave before invoking a command. (See the section called Addendum: Raw Prefix Argument for the details of the raw prefix argument data structure.) The function prefix-numeric-value can interpret that data structure as a number in exactly the way (interactive "p") did. What’s more, if other-window-backward is called non-interactively (and n is therefore not a prefix argument in raw form), prefix-numeric-value does the right thing—namely, return n unchanged if it’s a number, and return 1 if it’s nil.

Arguably, this definition is no more or less functional than the version of other-window-backward we had before. But this version is more “Emacs-Lisp-like” because it achieves better code reuse. It uses the built-in function prefix-numeric-value rather than duplicating that function’s behavior.

Now let’s look at another example.

Line-at-a-Time Scrolling

Before I became an Emacs user, I grew accustomed to some functions in other editors that weren’t present in Emacs. Naturally I missed having those functions and decided to replace them. One example is the ability to scroll text up and down one line at a time with a single keystroke.

Emacs has two scrolling functions, scroll-up and scroll-down, which are bound to C-v and M-v. Each function takes an optional argument telling it how many lines to scroll. By default, they each scroll the text one windowful at a time. (Don’t confuse scrolling up and down with moving the cursor up and down as with C-n and C-p. Cursor motion moves the cursor and scrolls the text only if necessary. Scrolling moves the text in the window and moves the cursor only if necessary.)

Though I could scroll up and down one line at a time with C-u 1 C-v and C-u 1 M-v, I wanted to be able to do it with a single keystroke. Using the techniques from the previous section, it is easy to write two new commands for scrolling with one keystroke.

First things first, though. I can never remember which function does what. Does scroll-up mean that the text moves up, revealing parts of the file that are farther down? Or does it mean that we reveal parts of the file that are farther up, moving the text down? I’d prefer that these functions had less confusing names, like scroll-ahead and scroll-behind.

We can use defalias to refer to any Lisp function by a different name.

(defalias 'scroll-ahead 'scroll-up)
(defalias 'scroll-behind 'scroll-down)

There. Now we’ll never have to deal with those ambiguous names again (although the original names remain in addition to the new aliases).

Now to define two new commands that call scroll-ahead and scroll-behind with the right arguments. We proceed exactly as we did with other-window-backward:

(defun scroll-one-line-ahead ()
  "Scroll ahead one line."
  (interactive)
  (scroll-ahead 1))
(defun scroll-one-line-behind ()
  "Scroll behind one line."
  (interactive)
  (scroll-behind 1))

As before, we can make the functions more general by giving them an optional argument:

(defun scroll-n-lines-ahead (&optional n)
  "Scroll ahead N lines (1 by default)."
  (interactive "P")
  (scroll-ahead (prefix-numeric-value n)))
(defun scroll-n-lines-behind (&optional n)
  "Scroll behind N lines (1 by default)."
  (interactive "P")
  (scroll-behind (prefix-numeric-value n)))

Finally, we choose keys to bind to the new commands. I like C-q for scroll-n-lines-behind and C-z for scroll-n-lines-ahead:

(global-set-key "C-q" 'scroll-n-lines-behind)
(global-set-key "C-z" 'scroll-n-lines-ahead)

By default, C-q is bound to quoted-insert. I move that infrequently used function to C-x C-q:

(global-set-key "C-xC-q" 'quoted-insert)

The default binding of C-x C-q is vc-toggle-read-only, which I don’t mind losing.

C-z has a default binding of iconify-or-deiconify-frame when running under X, and suspend-emacs when running in a character terminal. In both cases, the function is also bound to C-x C-z, so there’s no need to rebind them.

Other Cursor and Text Motion Commands

Here are a few more easy commands with their suggested keybindings.

(defun point-to-top ()
  "Put point on top line of window."
  (interactive)
  (move-to-window-line 0))
(global-set-key "M-," 'point-to-top)

“Point” refers to the position of the cursor. This command makes the cursor jump to the top left of the window it’s in. The suggested keybinding replaces tags-loop-continue, which I like to put on C-x,:

(global-set-key "C-x," 'tags-loop-continue)

The next function makes the cursor jump to the bottom left of the window it’s in.

(defun point-to-bottom ()
  "Put point at beginning of last visible line."
  (interactive)
  (move-to-window-line -1))
(global-set-key "M-." 'point-to-bottom)

The suggested keybinding in this case replaces find-tag. I put that on C-x. which in turn replaces set-fill-prefix, which I don’t mind losing.

(defun line-to-top ()
  "Move current line to top of window."
  (interactive)
  (recenter 0))
(global-set-key "M-!" 'line-to-top)

This command scrolls the window so that whichever line the cursor is on becomes the top line in the window. The keybinding replaces shell-command.

There is one drawback to changing the bindings for keys in Emacs. If you become accustomed to a highly customized Emacs and then try to use an uncustomized Emacs (e.g., on a different computer or using a friend’s login account), you’ll keep pressing the wrong keys. This happens to me all the time. I’ve essentially trained myself to be unable to use an uncustomized Emacs without a lot of frustration. But I rarely use an uncustomized Emacs, so the convenience of customizing it the way I like outweighs the occasional drawbacks. Before you move commands from one key to another with wild abandon like I do, you should weigh the costs and benefits of doing so.

So far, the functions we’ve written have been very simple. Essentially, they all just rearrange their arguments and then call a single other function to do the real work. Let’s look at an example now where more programming is required.

In UNIX, a symbolic link, or symlink, is a file that refers to another file by name. When you ask for the contents of a symlink, you actually get the contents of the real file named by the symlink.

Suppose you visit a file in Emacs that is really a symlink to some other file. You make some changes and press C-x C-s to save the buffer. What should Emacs do?

  1. Replace the symbolic link with the edited version of the file, breaking the link but leaving the original link target alone.

  2. Overwrite the file pointed to by the symbolic link.

  3. Prompt you to choose one of the above actions.

  4. Something else altogether.

Different editors handle the symlink situation in different ways, so a user who has grown accustomed to one editor’s behavior may be unpleasantly surprised by another’s. Plus, I believe that the right answer changes depending on the situation, and that the user should be forced to think about what’s right each time this comes up.

Here’s what I do: when I visit a file that’s really a symlink, I have Emacs automatically make the buffer read-only. This causes a “Buffer is read-only” error as soon as I try to change anything in the buffer. The error acts as a reminder, alerting me to the possibility that I’m visiting a symlink. Then I choose how to proceed using one of two special commands I’ve designed.

Hooks

For Emacs to make the buffer read-only when I first visit the file, I have to somehow tell Emacs, “execute a certain piece of Lisp code whenever I visit a file.” The action of visiting a file should trigger a function I write. This is where hooks come in.

A hook is an ordinary Lisp variable whose value is a list of functions that get executed under specific conditions. For instance, the variable write-file-hooks is a list of functions that Emacs executes whenever a buffer is saved, and post-command-hook is a list of functions to run after every interactive command. The hook that interests us most for this example is find-file-hooks, which Emacs runs every time a new file is visited. (There are many more hooks, some of which we’ll be looking at later in the book. To discover what hooks are available, try M-x apropos RET hook RET.)

The function add-hook adds a function to a hook variable. Here’s a function to add to find-file-hooks:

(defun read-only-if-symlink ()
  (if (file-symlink-p buffer-file-name)
      (progn
        (setq buffer-read-only t)
        (message "File is a symlink"))))

This function tests whether current buffer’s file is a symlink. If it is, the buffer is made read-only and the message “File is a symlink” is displayed. Let’s look a little closer at some parts of this function.

  • First, notice that the parameter list is empty. Functions that appear in hook variables take no arguments.

  • The function file-symlink-p tests whether its argument, which is a string naming a file, refers to a symbolic link. It’s a Boolean predicate, meaning it returns true or false. In Lisp, predicates traditionally have names ending in p or -p.

  • The argument to file-symlink-p is buffer-file-name. This predefined variable has a different value in every buffer, and is therefore known as a buffer-local variable. It always refers to the name of the file in the current buffer. The “current buffer,” when find-file-hooks gets executed, is the newly found file.

  • If buffer-file-name does refer to a symlink, there are two things we want to do: make the buffer read-only, and display a message. However, Lisp only allows one expression in the “then” part of an if-then-else. If we were to write:

    (if (file-symlink-p buffer-file-name)
        (setq buffer-read-only t)
        (message "File is a symlink"))

    it would mean, “if buffer-file-name is a symlink, then make the buffer read-only, else print the message, ‘File is a symlink’.” To get both the call to setq and the call to message into the “then” part of the if, we wrap them in a progn, as in the following example.

    (progn
      (setq buffer-read-only t)
      (message "File is a symlink"))

    A progn expression evaluates each of its subexpressions in order and returns the value of the last one.

  • The variable buffer-read-only is also buffer-local and controls whether the current buffer is read-only.

Now that we’ve defined read-only-if-symlink, we can call

(add-hook 'find-file-hooks 'read-only-if-symlink)

to add it to the list of functions that are called whenever a new file is visited.

Anonymous Functions

When you use defun to define a function, you give it a name by which the function can be called from anywhere. But what if the function won’t ever be called from anywhere else? What if it needs to be available in only one place? Arguably, read-only-if-symlink is needed only in the find-file-hooks list; in fact, it might even be harmful for it to be called outside of find-file-hooks.

It’s possible to define a function without giving it a name. Such functions are appropriately known as anonymous functions. They’re created with the Lisp keyword lambda,[13] which works exactly like defun except that the name of the function is left out:

(lambda ()
  (if (file-symlink-p buffer-file-name)
      (progn
        (setq buffer-read-only t)
        (message "File is a symlink"))))

The empty parentheses after the lambda are where the anonymous function’s parameters would be listed. This function has no parameters.

An anonymous function definition can be used wherever you might use the name of a function:

(add-hook 'find-file-hooks
          '(lambda ()
             (if (file-symlink-p buffer-file-name)
                 (progn
                   (setq buffer-read-only t)
                   (message "File is a symlink")))))

Now only find-file-hooks has access to the function; no other code is able to call it.[14]

There’s one reason not to use anonymous functions in hooks. If you ever wish to remove a function from a hook, you need to refer to it by name in a call to remove-hook, like so:

(remove-hook 'find-file-hooks 'read-only-if-symlink)

This is much harder if the function is anonymous.

When Emacs alerts me that I’m editing a symlink, I may wish to replace the buffer with one visiting the target of the link instead; or I may wish to “clobber” the symlink (replacing the link itself with an actual copy of the real file) and visit that. Here are two commands for these purposes:

(defun visit-target-instead ()
  "Replace this buffer with a buffer visiting the link target."
  (interactive)
  (if buffer-file-name
      (let ((target (file-symlink-p buffer-file-name)))
        (if target
            (find-alternate-file target)
          (error "Not visiting a symlink")))
    (error "Not visiting a file")))
(defun clobber-symlink ()
  "Replace symlink with a copy of the file."
  (interactive)
  (if buffer-file-name
      (let ((target (file-symlink-p buffer-file-name)))
        (if target
            (if (yes-or-no-p (format "Replace %s with %s? "
                                     buffer-file-name
                                     target))
              (progn
                (delete-file buffer-file-name)
                (write-file buffer-file-name)))
          (error "Not visiting a symlink")))
    (error "Not visiting a file")))

Both functions begin with

(if buffer-file-name
    ...
  (error "Not visiting a file"))

(I’ve abbreviated the meat of the function as to illustrate the surrounding structure.) This test is necessary because buffer-file-name may be nil (in the case that the current buffer isn’t visiting any file—e.g., it might be the *scratch* buffer), and passing nil to file-symlink-p would generate the error, “Wrong type argument: stringp, nil.”[15] The error message means that some

function was called expecting a string—an object satisfying the predicate stringp—but got nil instead. The user of visit-target-instead or clobber-symlink would be baffled by this error message, so we detect ourselves whether buffer-file-name is nil. If it is, then in the “else” clause of the if we issue a more informative error message—“Not visiting a file”—using error. When error is called, the current command aborts and Emacs returns to its top-level to await the user’s next action.

Why wasn’t it necessary to test buffer-file-name in read-only-if-symlink? Because that function only gets called from find-file-hooks, and find-file-hooks only gets executed when visiting a file.

In the “then” part of the buffer-file-name test, both functions next have

(let ((target (file-symlink-p buffer-file-name))) ...)

Most languages have a way to create temporary variables (also called local variables) that exist only in a certain region of code, called the variable’s scope. In Lisp, temporary variables are created with let, whose syntax is

(let ((var1 value1)
      (var2 value2)
      ...
      (varn valuen))
  body1 body2 ... bodyn)

This gives var1 the value value1, var2 the value value2, and so on; and var1 and var2 can be used only within the bodyi expressions. Among other things, using temporary variables helps to avoid conflicts between regions of code that happen to use identical variable names.

So the expression

(let ((target (file-symlink-p buffer-file-name))) ...)

creates a temporary variable named target whose value is the result of calling

(file-symlink-p buffer-file-name)

As mentioned earlier, file-symlink-p is a predicate, which means it returns truth or falsehood. But because “truth” in Lisp can be represented by any expression except nil, file-symlink-p isn’t constrained to returning t if its argument really is a symlink. In fact, it returns the name of the file to which the symlink refers. So if buffer-file-name is the name of a symlink, target will be the name of the symlink’s target.

With the temporary variable target in effect, the body of the let looks like this in both functions:

(if target
    ...
  (error "Not visiting a symlink"))

After executing the body of the let, the variable target no longer exists.

Within the let, if target is nil (because file-symlink-p returned nil, because buffer-file-name must not be the name of a symlink), then in the “else” clause we issue an informative error message, “Not visiting a symlink.” Otherwise we do something else that depends on which function we’re talking about. Finally we reach a point where the two functions differ.

At this point, visit-target-instead does

(find-alternate-file target)

which replaces the current buffer with one visiting target, prompting the user first in case there are unsaved changes in the original buffer. It even reruns the find-file-hooks when the new file is visited, which is good because it, too, may be a symlink!

At the point where visit-target-instead calls find-alternate-file, clobber-symlink does this instead:

(if (yes-or-no-p ...) ...)

The function yes-or-no-p asks the user a yes or no question and returns true if the answer was “yes,” false otherwise. The question, in this case, is:

(format "Replace %s with %s? "
        buffer-file-name
        target)

This constructs a string in a fashion similar to C’s printf. The first argument is a pattern. Each %s gets replaced with the string representation of a subsequent argument. The first %s gets replaced with the value of buffer-file-name and the second gets replaced with the value of target. So if buffer-file-name is the string "foo" and target is " bar ", the prompt will read, “Replace foo with bar?” (The format function understands other %-sequences in the pattern string. For instance, %c prints a single character if the corresponding argument is an ASCII value. See the online help for format—by typing M-? f format RET—for a complete list.)

After testing the return value of yes-or-no-p to make sure the user answered “yes,” clobber-symlink does this:

(progn
  (delete-file buffer-file-name)
  (write-file buffer-file-name))

As we’ve seen, the progn is for grouping two or more Lisp expressions where only one is expected. The call to delete-file deletes the file (which is really just a symlink), and the call to write-file saves the contents of the current buffer right back to the same filename, but this time as a plain file.

I like to put these functions on C-x t for visit-target-instead (unused by default) and C-x l for clobber-symlink (by default bound to count-lines-page).

Advised Buffer Switching

Let’s conclude this chapter with an example that introduces a very useful Lisp tool called advice.

It frequently happens that I’m editing many similarly named files at the same time; for instance, foobar.c and foobar.h. When I want to switch from one buffer to the other, I use C-x b, switch-to-buffer, which prompts me for a buffer name. Since I like to keep my keystrokes to a minimum, I depend on TAB completion of the buffer name. I’ll type

C-x b fo TAB

expecting that the TAB will complete “fo” to “foobar.c”, then I’ll press RET to accept the completed input. Ninety percent of the time, this works great. Other times, such as in this example, pressing fo TAB will only expand as far as “foobar.”, requiring me to disambiguate between “foobar.c” and “foobar.h”. Out of habit, though, I often press RET and accept the buffer name “foobar.”.

At this point, Emacs creates a brand-new empty buffer named foobar., which of course isn’t what I wanted at all. Now I’ve got to kill the brand-new buffer (with C-x k, kill-buffer) and start all over again. Though I do occasionally need the ability to switch to a nonexistent buffer, that need is very rare compared with the number of times I commit this error. What I’d like is for Emacs to catch my error before letting me commit it, by only accepting the names of existing buffers when it prompts me for one.

To achieve this, we’ll use advice. A piece of advice attached to a Lisp function is code that gets executed before or after the function each time the function is invoked. Before advice can affect the arguments before they’re passed to the advised function. After advice can affect the return value that comes out of the advised function. Advice is a little bit like hook variables, but whereas Emacs defines only a few dozen hook variables for very particular circumstances, you get to choose which functions get “advised.”

Here’s a first try at advising switch-to-buffer:

(defadvice switch-to-buffer (before existing-buffer
                             activate compile)
  "When interactive, switch to existing buffers only."
  (interactive "b"))

Let’s look at this closely. The function defadvice creates a new piece of advice. Its first argument is the (unquoted) name of the existing function being advised—in this case, switch-to-buffer. Next comes a specially formatted list. Its first element—in this case, before—tells whether this is “before” or “after” advice. (Another type of advice, called "around,” lets you embed a call to the advised function inside the advice code.) Next comes the name of this piece of advice; I named it existing-buffer. The name can be used later if you want to remove or modify the advice. Next come some keywords: activate means that this advice should be active as soon as it’s defined (it’s possible to define advice but leave it inactive); and compile means that the advice code should be “byte-compiled” for speed (see Chapter 5).

After the specially formatted list, a piece of advice has an optional docstring.

The only thing in the body of this advice is its own interactive declaration, which replaces the interactive declaration of switch-to-buffer. Whereas switch-to-buffer accepts any string as the buffer-name argument, the code letter b in an interactive declaration means “accept only names of existing buffers.” By using the interactive declaration to make this change, we’ve managed to not affect any Lisp code that wants to call switch-to-buffer non-interactively. So this tiny piece of advice effectively does the whole job: it changes switch-to-buffer to accept only the names of existing buffers.

Unfortunately, that’s too restrictive. It should still be possible to switch to nonexistent buffers, but only when some special indication is given that the restriction should be lifted—say, when a prefix argument is given. Thus, C-x b should refuse to switch to nonexistent buffers, but C-u C-x b should permit it.

Here’s how this is done:

(defadvice switch-to-buffer (before existing-buffer
                             activate compile)
  "When interactive, switch to existing buffers only,
unless given a prefix argument."
  (interactive
   (list (read-buffer "Switch to buffer: "
                      (other-buffer)
                      (null current-prefix-arg)))))

Once again, we’re overriding the interactive declaration of switch-to-buffer using “before” advice. But this time, we’re using interactive in a way we haven’t seen before: we’re passing a list as its argument, rather than a string of code letters.

When the argument to interactive is some expression other than a string, that expression is evaluated to get a list of arguments that should be passed to the function. So in this case we call list, which constructs a list out of its arguments, with the result of

(read-buffer "Switch to buffer: "
             (other-buffer)
             (null current-prefix-arg))

The function read-buffer is the low-level Lisp function that prompts the user for a buffer name. It’s “low-level” in the sense that all other functions that prompt for buffer names ultimately call read-buffer. It’s called with a prompt string and two optional arguments: a default buffer to switch to, and a Boolean stating whether input should be restricted to existing buffers only.

For the default buffer, we pass the result of calling other-buffer, which computes a useful default buffer for this very purpose. (Usually it chooses the most recently used buffer that isn’t presently visible in a window.) For the Boolean stating whether to restrict input, we use

(null current-prefix-arg)

This tests whether current-prefix-arg is nil. If it is, the result will be t; if it’s not, the result will be nil. Thus, if there is no prefix argument (i.e., current-prefix-arg is nil), then we call

(read-buffer "Switch to buffer: "
             (other-buffer)
             t)

meaning “read a buffer name, restricting input to existing buffers only.” If there is a prefix argument, then we call

(read-buffer "Switch to buffer: "
             (other-buffer)
             nil)

meaning “read a buffer name with no restrictions” (allowing non-existent buffer names to be entered). The result of read-buffer is then passed to list, and the resulting list (containing one element, the buffer name) is used as the argument list for switch-to-buffer.

With switch-to-buffer thus advised, Emacs won’t let me respond to the prompt with a nonexistent buffer name unless I asked for that ability by pressing C-u first.

For completeness, you should similarly advise the functions switch-to-buffer-other-window and switch-to-buffer-other-frame.

Addendum: Raw Prefix Argument

The variable current-prefix-arg always contains the latest “raw” prefix argument, which is the same thing you get from

(interactive "P")

The function prefix-numeric-value can be applied to a “raw” prefix argument to get its numeric value, which is the same thing you get from

(interactive "p")

What does a raw prefix argument look like? Table 2-1 shows possible raw values along with their corresponding numeric values.

Table 2-1. Prefix arguments

If the User Types

Raw Value

Numeric Value

C-u followed by a (possibly negative) number

The number itself

The number itself

C-u - (with no following number)

The symbol -

-1

C-u n times in a row (with no following number or minus sign)

A list containing the number 4n

4n itself

No prefix argument

nil

1



[8] The keybinding for describe-key is M-? k if you’ve changed the help-command binding as described in Chapter 1; otherwise it’s C-h k.

[9] What’s the difference between a “parameter” and an “argument”? The terms are usually used interchangeably, but technically speaking, “parameter” refers to the variable in the function definition, while “argument” is the value that gets passed in when the function is called. The value of the argument is assigned to the parameter.

[10] Again, it’s only M-? f if you’ve changed the keybinding for help-command to M-?. >From here on, I’ll assume that you have, or if you haven’t you at least know what I mean.

[11] To see a description of interactive’s code letters, type M-? f interactive RET.

[12] Actually, Emacs won’t let you assign a value to nil.

[13] The “Lambda calculus” is a mathematical formalism having to do with the way functions instantiate their arguments. To some extent it is the theoretical basis for Lisp (and plenty of other computer languages). The word “lambda” has no significance other than being the name of a Greek letter.

[14] That’s not exactly true. It is possible for another piece of code to search the contents of the find-file-hooks list, pick out any function it finds, and execute it. The point is that the function is hidden, not exposed as with defun.

[15] Try it yourself: M-: (file-symlink-p nil) RET.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset