Intro to elisp 5. A Few More Complex Functions

In this chapter, we build on what we have learned in previous chapters by looking at more complex functions. The copy-to-buffer function illustrates use of two save-excursionexpressions in one definition, while the insert-buffer function illustrates use of an asterisk in an interactive expression, use of or, and the important distinction between a name and the object to which the name refers.

5.1 The Definition of copy-to-buffer

After understanding how append-to-buffer works, it is easy to understand copy-to-buffer. This function copies text into a buffer, but instead of adding to the second buffer, it replaces all the previous text in the second buffer.

The body of copy-to-buffer looks like this,

1
2
3
4
5
6
7
8
...
(interactive "BCopy to buffer: \nr")
(let ((oldbuf (current-buffer)))
(with-current-buffer (get-buffer-create buffer)
(barf-if-buffer-read-only)
(erase-buffer)
(save-excursion
(insert-buffer-substring oldbuf start end)))))

The copy-to-buffer function has a simpler interactive expression than append-to-buffer.

The definition then says

1
(with-current-buffer (get-buffer-create buffer) ...

First, look at the earliest inner expression; that is evaluated first. That expression starts with get-buffer-create buffer. The function tells the computer to use the buffer with the name specified as the one to which you are copying, or if such a buffer does not exist, to create it. Then, the with-current-buffer function evaluates its body with that buffer temporarily current.

(This demonstrates another way to shift the computer’s attention but not the user’s. The append-to-buffer function showed how to do the same with save-excursion and set-buffer. with-current-buffer is a newer, and arguably easier, mechanism.)

The barf-if-buffer-read-only function sends you an error message saying the buffer is read-only if you cannot modify it.

The next line has the erase-buffer function as its sole contents. That function erases the buffer.

Finally, the last two lines contain the save-excursion expression with insert-buffer-substring as its body. The insert-buffer-substring expression copies the text from the buffer you are in (and you have not seen the computer shift its attention, so you don’t know that that buffer is now called oldbuf).

Incidentally, this is what is meant by “replacement”. To replace text, Emacs erases the previous text and then inserts new text.

In outline, the body of copy-to-buffer looks like this:

1
2
3
4
5
6
(let (bind-oldbuf-to-value-of-current-buffer)
(with-the-buffer-you-are-copying-to
(but-do-not-erase-or-copy-to-a-read-only-buffer)
(erase-buffer)
(save-excursion
insert-substring-from-oldbuf-into-buffer)))

5.2 The Definition of insert-buffer

insert-buffer is yet another buffer-related function. This command copies another buffer into the current buffer. It is the reverse of append-to-buffer or copy-to-buffer, since they copy a region of text from the current buffer to another buffer.

Here is a discussion based on the original code. The code was simplified in 2003 and is harder to understand.

(See New Body for insert-buffer, to see a discussion of the new body.)

In addition, this code illustrates the use of interactive with a buffer that might be read-only and the important distinction between the name of an object and the object actually referred to.

The Code for insert-buffer

Here is the earlier code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(defun insert-buffer (buffer)
"Insert after point the contents of BUFFER.
Puts mark after the inserted text.
BUFFER may be a buffer or a buffer name."
(interactive "*bInsert buffer: ")
(or (bufferp buffer)
(setq buffer (get-buffer buffer)))
(let (start end newmark)
(save-excursion
(save-excursion
(set-buffer buffer)
(setq start (point-min) end (point-max)))
(insert-buffer-substring buffer start end)
(setq newmark (point)))
(push-mark newmark)))

As with other function definitions, you can use a template to see an outline of the function:

1
2
3
4
(defun insert-buffer (buffer)
"documentation..."
(interactive "*bInsert buffer: ")
body...)

5.2.1 The Interactive Expression in insert-buffer

In insert-buffer, the argument to the interactive declaration has two parts, an asterisk, ‘*’, and ‘bInsert buffer: ’.

A Read-only Buffer

The asterisk is for the situation when the current buffer is a read-only buffer—a buffer that cannot be modified. If insert-buffer is called when the current buffer is read-only, a message to this effect is printed in the echo area and the terminal may beep or blink at you; you will not be permitted to insert anything into current buffer. The asterisk does not need to be followed by a newline to separate it from the next argument.

‘b’ in an Interactive Expression

The next argument in the interactive expression starts with a lower case ‘b’. (This is different from the code for append-to-buffer, which uses an upper-case ‘B’. See The Definition of append-to-buffer.) The lower-case ‘b’ tells the Lisp interpreter that the argument for insert-buffer should be an existing buffer or else its name. (The upper-case ‘B’ option provides for the possibility that the buffer does not exist.) Emacs will prompt you for the name of the buffer, offering you a default buffer, with name completion enabled. If the buffer does not exist, you receive a message that says “No match”; your terminal may beep at you as well.

The new and simplified code generates a list for interactive. It uses the barf-if-buffer-read-only and read-buffer functions with which we are already familiar and the progn special form with which we are not. (It will be described later.)

5.2.2 The Body of the insert-buffer Function

The body of the insert-buffer function has two major parts: an or expression and a let expression. The purpose of the or expression is to ensure that the argument buffer is bound to a buffer and not just the name of a buffer. The body of the let expression contains the code which copies the other buffer into the current buffer.

In outline, the two expressions fit into the insert-buffer function like this:

1
2
3
4
5
6
7
(defun insert-buffer (buffer)
"documentation..."
(interactive "*bInsert buffer: ")
(or ...
...
(let (varlist)
body-of-let... )

To understand how the or expression ensures that the argument buffer is bound to a buffer and not to the name of a buffer, it is first necessary to understand the or function.

Before doing this, let me rewrite this part of the function using if so that you can see what is done in a manner that will be familiar.

5.2.3 insert-buffer With an if Instead of an or

The job to be done is to make sure the value of buffer is a buffer itself and not the name of a buffer. If the value is the name, then the buffer itself must be got.

You can imagine yourself at a conference where an usher is wandering around holding a list with your name on it and looking for you: the usher is bound to your name, not to you; but when the usher finds you and takes your arm, the usher becomes bound to you.

In Lisp, you might describe this situation like this:

1
2
(if (not (holding-on-to-guest))
(find-and-take-arm-of-guest))

We want to do the same thing with a buffer—if we do not have the buffer itself, we want to get it.

Using a predicate called bufferp that tells us whether we have a buffer (rather than its name), we can write the code like this:

1
2
(if (not (bufferp buffer))              ; if-part
(setq buffer (get-buffer buffer))) ; then-part

Here, the true-or-false-test of the if expression is (not (bufferp buffer)); and the then-part is the expression (setq buffer (get-buffer buffer)).

In the test, the function bufferp returns true if its argument is a buffer—but false if its argument is the name of the buffer. (The last character of the function name bufferp is the character ‘p’; as we saw earlier, such use of ‘p’ is a convention that indicates that the function is a predicate, which is a term that means that the function will determine whether some property is true or false. See Using the Wrong Type Object as an Argument.)

The function not precedes the expression (bufferp buffer), so the true-or-false-test looks like this:

1
(not (bufferp buffer))

not is a function that returns true if its argument is false and false if its argument is true. So if (bufferp buffer) returns true, the not expression returns false and vice versa.

Using this test, the if expression works as follows: when the value of the variable buffer is actually a buffer rather than its name, the true-or-false-test returns false and the ifexpression does not evaluate the then-part. This is fine, since we do not need to do anything to the variable buffer if it really is a buffer.

On the other hand, when the value of buffer is not a buffer itself, but the name of a buffer, the true-or-false-test returns true and the then-part of the expression is evaluated. In this case, the then-part is (setq buffer (get-buffer buffer)). This expression uses the get-buffer function to return an actual buffer itself, given its name. The setq then sets the variable buffer to the value of the buffer itself, replacing its previous value (which was the name of the buffer).

5.2.4 The or in the Body

The purpose of the or expression in the insert-buffer function is to ensure that the argument buffer is bound to a buffer and not just to the name of a buffer. The previous section shows how the job could have been done using an if expression. However, the insert-buffer function actually uses or. To understand this, it is necessary to understand how orworks.

An or function can have any number of arguments. It evaluates each argument in turn and returns the value of the first of its arguments that is not nil. Also, and this is a crucial feature of or, it does not evaluate any subsequent arguments after returning the first non-nil value.

The or expression looks like this:

1
2
(or (bufferp buffer)
(setq buffer (get-buffer buffer)))

The first argument to or is the expression (bufferp buffer). This expression returns true (a non-nil value) if the buffer is actually a buffer, and not just the name of a buffer. In the or expression, if this is the case, the or expression returns this true value and does not evaluate the next expression—and this is fine with us, since we do not want to do anything to the value of buffer if it really is a buffer.

On the other hand, if the value of (bufferp buffer) is nil, which it will be if the value of buffer is the name of a buffer, the Lisp interpreter evaluates the next element of the orexpression. This is the expression (setq buffer (get-buffer buffer)). This expression returns a non-nil value, which is the value to which it sets the variable buffer—and this value is a buffer itself, not the name of a buffer.

The result of all this is that the symbol buffer is always bound to a buffer itself rather than to the name of a buffer. All this is necessary because the set-buffer function in a following line only works with a buffer itself, not with the name to a buffer.

Incidentally, using or, the situation with the usher would be written like this:

1
(or (holding-on-to-guest) (find-and-take-arm-of-guest))

5.2.5 The let Expression in insert-buffer

After ensuring that the variable buffer refers to a buffer itself and not just to the name of a buffer, the insert-buffer function continues with a let expression. This specifies three local variables, start, end, and newmark and binds them to the initial value nil. These variables are used inside the remainder of the let and temporarily hide any other occurrence of variables of the same name in Emacs until the end of the let.

The body of the let contains two save-excursion expressions. First, we will look at the inner save-excursion expression in detail. The expression looks like this:

1
2
3
(save-excursion
(set-buffer buffer)
(setq start (point-min) end (point-max)))

The expression (set-buffer buffer) changes Emacs’s attention from the current buffer to the one from which the text will copied. In that buffer, the variables start and end are set to the beginning and end of the buffer, using the commands point-min and point-max. Note that we have here an illustration of how setq is able to set two variables in the same expression. The first argument of setq is set to the value of its second, and its third argument is set to the value of its fourth.

After the body of the inner save-excursion is evaluated, the save-excursion restores the original buffer, but start and end remain set to the values of the beginning and end of the buffer from which the text will be copied.

The outer save-excursion expression looks like this:

1
2
3
4
5
(save-excursion
(inner-save-excursion-expression
(go-to-new-buffer-and-set-start-and-end)
(insert-buffer-substring buffer start end)
(setq newmark (point)))

The insert-buffer-substring function copies the text into the current buffer from the region indicated by start and end in buffer. Since the whole of the second buffer lies between start and end, the whole of the second buffer is copied into the buffer you are editing. Next, the value of point, which will be at the end of the inserted text, is recorded in the variable newmark.

After the body of the outer save-excursion is evaluated, point is relocated to its original place.

However, it is convenient to locate a mark at the end of the newly inserted text and locate point at its beginning. The newmark variable records the end of the inserted text. In the last line of the let expression, the (push-mark newmark) expression function sets a mark to this location. (The previous location of the mark is still accessible; it is recorded on the mark ring and you can go back to it with C-u C-.) Meanwhile, point is located at the beginning of the inserted text, which is where it was before you called the insert function, the position of which was saved by the first save-excursion.

The whole let expression looks like this:

1
2
3
4
5
6
7
8
(let (start end newmark)
(save-excursion
(save-excursion
(set-buffer buffer)
(setq start (point-min) end (point-max)))
(insert-buffer-substring buffer start end)
(setq newmark (point)))
(push-mark newmark))

Like the append-to-buffer function, the insert-buffer function uses let, save-excursion, and set-buffer. In addition, the function illustrates one way to use or. All these functions are building blocks that we will find and use again and again.

5.2.6 New Body for insert-buffer

The body in the GNU Emacs 22 version is more confusing than the original.

It consists of two expressions,

1
2
3
4
5
6
(push-mark
(save-excursion
(insert-buffer-substring (get-buffer buffer))
(point)))

nil

except, and this is what confuses novices, very important work is done inside the push-mark expression.

The get-buffer function returns a buffer with the name provided. You will note that the function is not called get-buffer-create; it does not create a buffer if one does not already exist. The buffer returned by get-buffer, an existing buffer, is passed to insert-buffer-substring, which inserts the whole of the buffer (since you did not specify anything else).

The location into which the buffer is inserted is recorded by push-mark. Then the function returns nil, the value of its last command. Put another way, the insert-buffer function exists only to produce a side effect, inserting another buffer, not to return any value.

5.3 Complete Definition of beginning-of-buffer

The basic structure of the beginning-of-buffer function has already been discussed. (See A Simplified beginning-of-buffer Definition.) This section describes the complex part of the definition.

As previously described, when invoked without an argument, beginning-of-buffer moves the cursor to the beginning of the buffer (in truth, the beginning of the accessible portion of the buffer), leaving the mark at the previous position. However, when the command is invoked with a number between one and ten, the function considers that number to be a fraction of the length of the buffer, measured in tenths, and Emacs moves the cursor that fraction of the way from the beginning of the buffer. Thus, you can either call this function with the key command M-<, which will move the cursor to the beginning of the buffer, or with a key command such as C-u 7 M-< which will move the cursor to a point 70% of the way through the buffer. If a number bigger than ten is used for the argument, it moves to the end of the buffer.

The beginning-of-buffer function can be called with or without an argument. The use of the argument is optional.

5.3.1 Optional Arguments

Unless told otherwise, Lisp expects that a function with an argument in its function definition will be called with a value for that argument. If that does not happen, you get an error and a message that says ‘Wrong number of arguments’.

However, optional arguments are a feature of Lisp: a particular keyword is used to tell the Lisp interpreter that an argument is optional. The keyword is &optional. (The ‘&’ in front of ‘optional’ is part of the keyword.) In a function definition, if an argument follows the keyword &optional, no value need be passed to that argument when the function is called.

The first line of the function definition of beginning-of-buffer therefore looks like this:

1
(defun beginning-of-buffer (&optional arg)

In outline, the whole function looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
(defun beginning-of-buffer (&optional arg)
"documentation..."
(interactive "P")
(or (is-the-argument-a-cons-cell arg)
(and are-both-transient-mark-mode-and-mark-active-true)
(push-mark))
(let (determine-size-and-set-it)
(goto-char
(if-there-is-an-argument
figure-out-where-to-go
else-go-to
(point-min))))
do-nicety

The function is similar to the simplified-beginning-of-buffer function except that the interactive expression has "P" as an argument and the goto-char function is followed by an if-then-else expression that figures out where to put the cursor if there is an argument that is not a cons cell.

(Since I do not explain a cons cell for many more chapters, please consider ignoring the function consp. See How Lists are Implemented, and Cons Cell and List Types.)

The "P" in the interactive expression tells Emacs to pass a prefix argument, if there is one, to the function in raw form. A prefix argument is made by typing the key followed by a number, or by typing C-u and then a number. (If you don’t type a number, C-u defaults to a cons cell with a 4. A lowercase "p" in the interactive expression causes the function to convert a prefix arg to a number.)

The true-or-false-test of the if expression looks complex, but it is not: it checks whether arg has a value that is not nil and whether it is a cons cell. (That is what consp does; it checks whether its argument is a cons cell.) If arg has a value that is not nil (and is not a cons cell), which will be the case if beginning-of-buffer is called with a numeric argument, then this true-or-false-test will return true and the then-part of the if expression will be evaluated. On the other hand, if beginning-of-buffer is not called with an argument, the value of arg will be nil and the else-part of the if expression will be evaluated. The else-part is simply point-min, and when this is the outcome, the whole goto-char expression is (goto-char (point-min)), which is how we saw the beginning-of-buffer function in its simplified form.