From: Chong Yidong Date: Sat, 19 Mar 2011 20:31:30 +0000 (-0400) Subject: Clarify relationship between save-excursion and current buffer. X-Git-Tag: emacs-pretest-24.0.90~104^3~93 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=c1bcd0d5d11ee1e2a72461069b4e39f3ee369955;p=emacs.git 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. --- diff --git a/doc/lispref/ChangeLog b/doc/lispref/ChangeLog index 90a1d24ce04..26bb5888df5 100644 --- a/doc/lispref/ChangeLog +++ b/doc/lispref/ChangeLog @@ -1,3 +1,11 @@ +2011-03-19 Chong Yidong + + * 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 * strings.texi (String Conversion): Don't mention diff --git a/doc/lispref/buffers.texi b/doc/lispref/buffers.texi index f097ef4f25a..98fb748cd36 100644 --- a/doc/lispref/buffers.texi +++ b/doc/lispref/buffers.texi @@ -85,43 +85,63 @@ 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. - - 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 + 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}). + + 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. + +@example +@group +(current-buffer) + @result{} # +@end group +@end example +@end defun + +@defun set-buffer buffer-or-name +This function makes @var{buffer-or-name} the current buffer. +@var{buffer-or-name} must be an existing buffer or the name of an +existing buffer. The return value is the buffer made current. + +This function does not display the buffer in any window, so the user +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 to specified buffer the text of the region." + "Append the text of the region to BUFFER." (interactive "BAppend to buffer: \nr") (let ((oldbuf (current-buffer))) (save-current-buffer @@ -131,27 +151,36 @@ simplified version of the command @code{append-to-buffer}: @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, +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 -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. +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 @@ -168,29 +197,9 @@ have been wrong to do this: @end example @noindent -Using @code{save-current-buffer}, as we did, handles quitting, errors, -and @code{throw}, as well as ordinary evaluation. - -@defun current-buffer -This function returns the current buffer. - -@example -@group -(current-buffer) - @result{} # -@end group -@end example -@end defun - -@defun set-buffer buffer-or-name -This function makes @var{buffer-or-name} the current buffer. -@var{buffer-or-name} must be an existing buffer or the name of an -existing buffer. The return value is the buffer made current. - -This function does not display the buffer in any window, so the user -cannot necessarily see the buffer. But Lisp programs will now operate -on it. -@end defun +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 diff --git a/doc/lispref/positions.texi b/doc/lispref/positions.texi index 0b5dcd44dd8..92846bf6d56 100644 --- a/doc/lispref/positions.texi +++ b/doc/lispref/positions.texi @@ -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. - - 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. +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. + + 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: -@cindex window excursions -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}). +@example +Warning: @code{save-excursion} defeated by @code{set-buffer} +@end example -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. +@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 -(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))) +(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 -@end defspec + +@cindex window excursions + 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}). @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