append-to-buffer 中的 save-excursion ¶append-to-buffer 中 let 表达式的函数体由一个 save-excursion 表达式构成。
save-excursion 函数会保存光标位置,并在其函数体内的所有表达式执行完毕后,将光标恢复到该位置。此外,save-excursion 还会记录原始缓冲区并在最后恢复。这就是 save-excursion 在 append-to-buffer 中的使用方式。
顺便一提,这里值得注意:Lisp 函数通常会按照一定格式排版,多行结构内的所有内容都会比所在块的首个符号向右缩进更多。在本函数定义中,let 的缩进比 defun 更深,save-excursion 的缩进又比 let 更深,如下所示:
(defun ...
...
...
(let...
(save-excursion
...
这种排版约定让我们可以清晰地看出,save-excursion 函数体内的代码被其自身括号包裹,正如 save-excursion 本身被 let 的括号包裹一样:
(let ((oldbuf (current-buffer)))
(save-excursion
...
(set-buffer ...)
(insert-buffer-substring oldbuf start end)
...))
save-excursion 函数的使用可以看作是填充模板的过程:
(save-excursion first-expression-in-body second-expression-in-body ... last-expression-in-body)
在本函数中,save-excursion 的函数体只包含一个表达式,即 let* 表达式。你已经了解 let 函数,而 let* 函数有所不同。它允许 Emacs 按顺序依次设置变量列表中的每个变量,使得变量列表后半部分的变量可以使用前面已设置变量的值。
来看 append-to-buffer 中的 let* 表达式:
(let* ((append-to (get-buffer-create buffer))
(windows (get-buffer-window-list append-to t t))
point)
BODY...)
可以看到 append-to 被绑定到 (get-buffer-create buffer) 的返回值。下一行中,append-to 被用作 get-buffer-window-list 的参数;如果使用普通 let 表达式,这是无法实现的。注意 point 会自动绑定到 nil,与 let 语句中的行为一致。
现在我们重点关注 let* 表达式函数体内的 set-buffer 与 insert-buffer-substring 函数。
在早期版本中,set-buffer 表达式的写法很简单:
(set-buffer (get-buffer-create buffer))
而现在的写法是:
(set-buffer append-to)
原因是 append-to 已经在 let* 表达式前面绑定为 (get-buffer-create buffer)。
append-to-buffer 函数的作用是将当前缓冲区的文本插入到指定缓冲区。而 insert-buffer-substring 的作用恰好相反——它从另一个缓冲区复制文本到当前缓冲区。这就是为什么 append-to-buffer 定义开头使用 let,将局部符号 oldbuf 绑定到 current-buffer 的返回值。
insert-buffer-substring 表达式如下:
(insert-buffer-substring oldbuf start end)
insert-buffer-substring 函数从第一个参数指定的缓冲区复制字符串,并将其插入到当前缓冲区。本例中,该函数的参数是 let 创建并绑定的变量 oldbuf,即执行 append-to-buffer 命令时所在的原始缓冲区。
在 insert-buffer-substring 完成工作后,save-excursion 会将操作恢复到原始缓冲区,append-to-buffer 也就完成了任务。
用框架形式表示,函数体的执行逻辑如下:
(let (bind-oldbuf-to-value-of-current-buffer) (save-excursion ; Keep track of buffer. change-buffer insert-substring-from-oldbuf-into-buffer) change-back-to-original-buffer-when-finished let-the-local-meaning-of-oldbuf-disappear-when-finished
总而言之,append-to-buffer 的工作流程如下:将当前缓冲区保存到变量 oldbuf;获取目标缓冲区(不存在则创建)并切换 Emacs 当前操作缓冲区;使用 oldbuf 将原始缓冲区的区域文本插入到新缓冲区;最后通过 save-excursion 回到原始缓冲区。
通过分析 append-to-buffer,你已经了解了一个相当复杂的函数。它展示了 let 与 save-excursion 的用法,以及如何切换并返回其他缓冲区。许多函数定义都以类似方式使用 let、save-excursion 和 set-buffer。