Clarify relationship between save-excursion and current buffer.

Suggested by Uday S Reddy.

* positions.texi (Excursions): Explain the "save-excursion
defeated by set-buffer" warning.

* buffers.texi (Current Buffer): Copyedits.  Don't recommend using
save-excursion.
This commit is contained in:
Chong Yidong 2011-03-19 16:31:30 -04:00
parent 4e19a977b0
commit c1bcd0d5d1
3 changed files with 152 additions and 132 deletions

View file

@ -1,3 +1,11 @@
2011-03-19 Chong Yidong <cyd@stupidchicken.com>
* positions.texi (Excursions): Explain the "save-excursion
defeated by set-buffer" warning.
* buffers.texi (Current Buffer): Copyedits. Don't recommend using
save-excursion. Suggested by Uday S Reddy.
2011-03-16 Stefan Monnier <monnier@iro.umontreal.ca>
* strings.texi (String Conversion): Don't mention

View file

@ -85,91 +85,17 @@ This function returns @code{t} if @var{object} is a buffer,
@cindex changing to another buffer
@cindex current buffer
There are, in general, many buffers in an Emacs session. At any time,
one of them is designated as the @dfn{current buffer}. This is the
buffer in which most editing takes place, because most of the primitives
for examining or changing text in a buffer operate implicitly on the
current buffer (@pxref{Text}). Normally the buffer that is displayed on
the screen in the selected window is the current buffer, but this is not
always so: a Lisp program can temporarily designate any buffer as
current in order to operate on its contents, without changing what is
displayed on the screen.
There are, in general, many buffers in an Emacs session. At any
time, one of them is designated the @dfn{current buffer}---the buffer
in which most editing takes place. Most of the primitives for
examining or changing text operate implicitly on the current buffer
(@pxref{Text}).
The way to designate a current buffer in a Lisp program is by calling
@code{set-buffer}. The specified buffer remains current until a new one
is designated.
When an editing command returns to the editor command loop, the
command loop designates the buffer displayed in the selected window as
current, to prevent confusion: the buffer that the cursor is in when
Emacs reads a command is the buffer that the command will apply to.
(@xref{Command Loop}.) Therefore, @code{set-buffer} is not the way to
switch visibly to a different buffer so that the user can edit it. For
that, you must use the functions described in @ref{Displaying Buffers}.
@strong{Warning:} Lisp functions that change to a different current buffer
should not depend on the command loop to set it back afterwards.
Editing commands written in Emacs Lisp can be called from other programs
as well as from the command loop; it is convenient for the caller if
the subroutine does not change which buffer is current (unless, of
course, that is the subroutine's purpose). Therefore, you should
normally use @code{set-buffer} within a @code{save-current-buffer} or
@code{save-excursion} (@pxref{Excursions}) form that will restore the
current buffer when your function is done. Here, as an example, is a
simplified version of the command @code{append-to-buffer}:
@example
@group
(defun append-to-buffer (buffer start end)
"Append to specified buffer the text of the region."
(interactive "BAppend to buffer: \nr")
(let ((oldbuf (current-buffer)))
(save-current-buffer
(set-buffer (get-buffer-create buffer))
(insert-buffer-substring oldbuf start end))))
@end group
@end example
@noindent
This function binds a local variable to record the current buffer, and
then @code{save-current-buffer} arranges to make it current again.
Next, @code{set-buffer} makes the specified buffer current. Finally,
@code{insert-buffer-substring} copies the string from the original
current buffer to the specified (and now current) buffer.
If the buffer appended to happens to be displayed in some window,
the next redisplay will show how its text has changed. Otherwise, you
will not see the change immediately on the screen. The buffer becomes
current temporarily during the execution of the command, but this does
not cause it to be displayed.
If you make local bindings (with @code{let} or function arguments) for
a variable that may also have buffer-local bindings, make sure that the
same buffer is current at the beginning and at the end of the local
binding's scope. Otherwise you might bind it in one buffer and unbind
it in another! There are two ways to do this. In simple cases, you may
see that nothing ever changes the current buffer within the scope of the
binding. Otherwise, use @code{save-current-buffer} or
@code{save-excursion} to make sure that the buffer current at the
beginning is current again whenever the variable is unbound.
Do not rely on using @code{set-buffer} to change the current buffer
back, because that won't do the job if a quit happens while the wrong
buffer is current. For instance, in the previous example, it would
have been wrong to do this:
@example
@group
(let ((oldbuf (current-buffer)))
(set-buffer (get-buffer-create buffer))
(insert-buffer-substring oldbuf start end)
(set-buffer oldbuf))
@end group
@end example
@noindent
Using @code{save-current-buffer}, as we did, handles quitting, errors,
and @code{throw}, as well as ordinary evaluation.
Normally, the buffer displayed in the selected window is the current
buffer, but this is not always so: a Lisp program can temporarily
designate any buffer as current in order to operate on its contents,
without changing what is displayed on the screen. The most basic
function for designating a current buffer is @code{set-buffer}.
@defun current-buffer
This function returns the current buffer.
@ -192,6 +118,89 @@ cannot necessarily see the buffer. But Lisp programs will now operate
on it.
@end defun
When an editing command returns to the editor command loop, Emacs
automatically calls @code{set-buffer} on the buffer shown in the
selected window. This is to prevent confusion: it ensures that the
buffer that the cursor is in, when Emacs reads a command, is the
buffer to which that command applies (@pxref{Command Loop}). Thus,
you should not use @code{set-buffer} to switch visibly to a different
buffer; for that, use the functions described in @ref{Displaying
Buffers}.
When writing a Lisp function, do @emph{not} rely on this behavior of
the command loop to restore the current buffer after an operation.
Editing commands can also be called as Lisp functions by other
programs, not just from the command loop; it is convenient for the
caller if the subroutine does not change which buffer is current
(unless, of course, that is the subroutine's purpose).
To operate temporarily on another buffer, put the @code{set-buffer}
within a @code{save-current-buffer} form. Here, as an example, is a
simplified version of the command @code{append-to-buffer}:
@example
@group
(defun append-to-buffer (buffer start end)
"Append the text of the region to BUFFER."
(interactive "BAppend to buffer: \nr")
(let ((oldbuf (current-buffer)))
(save-current-buffer
(set-buffer (get-buffer-create buffer))
(insert-buffer-substring oldbuf start end))))
@end group
@end example
@noindent
Here, we bind a local variable to record the current buffer, and then
@code{save-current-buffer} arranges to make it current again later.
Next, @code{set-buffer} makes the specified buffer current, and
@code{insert-buffer-substring} copies the string from the original
buffer to the specified (and now current) buffer.
Alternatively, we can use the @code{with-current-buffer} macro:
@example
@group
(defun append-to-buffer (buffer start end)
"Append the text of the region to BUFFER."
(interactive "BAppend to buffer: \nr")
(let ((oldbuf (current-buffer)))
(with-current-buffer (get-buffer-create buffer)
(insert-buffer-substring oldbuf start end))))
@end group
@end example
In either case, if the buffer appended to happens to be displayed in
some window, the next redisplay will show how its text has changed.
If it is not displayed in any window, you will not see the change
immediately on the screen. The command causes the buffer to become
current temporarily, but does not cause it to be displayed.
If you make local bindings (with @code{let} or function arguments)
for a variable that may also have buffer-local bindings, make sure
that the same buffer is current at the beginning and at the end of the
local binding's scope. Otherwise you might bind it in one buffer and
unbind it in another!
Do not rely on using @code{set-buffer} to change the current buffer
back, because that won't do the job if a quit happens while the wrong
buffer is current. For instance, in the previous example, it would
have been wrong to do this:
@example
@group
(let ((oldbuf (current-buffer)))
(set-buffer (get-buffer-create buffer))
(insert-buffer-substring oldbuf start end)
(set-buffer oldbuf))
@end group
@end example
@noindent
Using @code{save-current-buffer} or @code{with-current-buffer}, as we
did, correctly handles quitting, errors, and @code{throw}, as well as
ordinary evaluation.
@defspec save-current-buffer body@dots{}
The @code{save-current-buffer} special form saves the identity of the
current buffer, evaluates the @var{body} forms, and finally restores

View file

@ -798,69 +798,72 @@ is zero or less.
@cindex excursion
It is often useful to move point ``temporarily'' within a localized
portion of the program, or to switch buffers temporarily. This is
called an @dfn{excursion}, and it is done with the @code{save-excursion}
special form. This construct initially remembers the identity of the
current buffer, and its values of point and the mark, and restores them
after the completion of the excursion.
portion of the program. This is called an @dfn{excursion}, and it is
done with the @code{save-excursion} special form. This construct
remembers the initial identity of the current buffer, and its values
of point and the mark, and restores them after the excursion
completes. It is the standard way to move point within one part of a
program and avoid affecting the rest of the program, and is used
thousands of times in the Lisp sources of Emacs.
The forms for saving and restoring the configuration of windows are
described elsewhere (see @ref{Window Configurations}, and @pxref{Frame
Configurations}). When only the identity of the current buffer needs
to be saved and restored, it is preferable to use
@code{save-current-buffer} instead.
If you only need to save and restore the identity of the current
buffer, use @code{save-current-buffer} or @code{with-current-buffer}
instead (@pxref{Current Buffer}). If you need to save or restore
window configurations, see the forms described in @ref{Window
Configurations} and in @ref{Frame Configurations}.
@defspec save-excursion body@dots{}
@cindex mark excursion
@cindex point excursion
The @code{save-excursion} special form saves the identity of the current
buffer and the values of point and the mark in it, evaluates
@var{body}, and finally restores the buffer and its saved values of
point and the mark. All three saved values are restored even in case of
an abnormal exit via @code{throw} or error (@pxref{Nonlocal Exits}).
This special form saves the identity of the current buffer and the
values of point and the mark in it, evaluates @var{body}, and finally
restores the buffer and its saved values of point and the mark. All
three saved values are restored even in case of an abnormal exit via
@code{throw} or error (@pxref{Nonlocal Exits}).
The @code{save-excursion} special form is the standard way to move
point within one part of a program and avoid affecting the rest of the
program. It is used more than 4000 times in the Lisp sources
of Emacs.
The value returned by @code{save-excursion} is the result of the last
form in @var{body}, or @code{nil} if no body forms were given.
@end defspec
@code{save-excursion} does not save the values of point and the mark for
other buffers, so changes in other buffers remain in effect after
@code{save-excursion} exits.
Because @code{save-excursion} only saves point and mark for the
buffer that was current at the start of the excursion, any changes
made to point and/or mark in other buffers, during the excursion, will
remain in effect afterward. This frequently leads to unintended
consequences, so the byte compiler warns if you call @code{set-buffer}
during an excursion:
@example
Warning: @code{save-excursion} defeated by @code{set-buffer}
@end example
@noindent
To avoid such problems, you should call @code{save-excursion} only
after setting the desired current buffer, as in the following example:
@example
@group
(defun append-string-to-buffer (string buffer)
"Append STRING to the end of BUFFER."
(with-current-buffer buffer
(save-excursion
(goto-char (point-max))
(insert string))))
@end group
@end example
@cindex window excursions
Likewise, @code{save-excursion} does not restore window-buffer
Likewise, @code{save-excursion} does not restore window-buffer
correspondences altered by functions such as @code{switch-to-buffer}.
One way to restore these correspondences, and the selected window, is to
use @code{save-window-excursion} inside @code{save-excursion}
(@pxref{Window Configurations}).
The value returned by @code{save-excursion} is the result of the last
form in @var{body}, or @code{nil} if no body forms were given.
@example
@group
(save-excursion @var{forms})
@equiv{}
(let ((old-buf (current-buffer))
(old-pnt (point-marker))
@end group
(old-mark (copy-marker (mark-marker))))
(unwind-protect
(progn @var{forms})
(set-buffer old-buf)
@group
(goto-char old-pnt)
(set-marker (mark-marker) old-mark)))
@end group
@end example
@end defspec
@strong{Warning:} Ordinary insertion of text adjacent to the saved
point value relocates the saved value, just as it relocates all markers.
More precisely, the saved value is a marker with insertion type
@code{nil}. @xref{Marker Insertion Types}. Therefore, when the saved
point value is restored, it normally comes before the inserted text.
point value relocates the saved value, just as it relocates all
markers. More precisely, the saved value is a marker with insertion
type @code{nil}. @xref{Marker Insertion Types}. Therefore, when the
saved point value is restored, it normally comes before the inserted
text.
Although @code{save-excursion} saves the location of the mark, it does
not prevent functions which modify the buffer from setting