关于 save-excursion 和 save-restriction 一起使用时的问题

我是 elisp 新人, 正在阅读 Emacs 内置的 Emacs Lisp Intro. 在 [Emacs Lisp Intro > Narrowing & Widening > save-restriction] 这边,

反复读了几遍, 还是很困惑. 诸君能否帮助理解一下呢?

PS: 此处疑点有两个. 一是顺序问题; 二是图片中第二行的 one right after the other, 这说的大概是紧靠, 为何仅在 “紧靠” 时需要这样写呢? (最后那个例子就是未 “紧靠” 的)

save-excursion 会记录你的当前使用的 buffer,在退出时恢复到那个 buffer。而 restriction 不会记录他在哪个 buffer。

save-excursionsave-restriction 可以用伪代码表示为

(let ((g0 (record-current-excursion))
      (g1 (record-current-restriction)))
  (unwind-protect
       (progn
         body ...)
    (restore-excursion g0)
    (restore-restriction g1)))

假如在 body 里面切换过 buffer,退出代码块时 restore-excursion 会帮助你回到原来的 buffer,然后 restore-restriction 就会被正常地执行。

如果反过来的话,退出来先执行 restore-restriction。此时 restriction 的 current-buffer 上下文不正确,尚未回到原来的 buffer,无法达到期望的结果

1 个赞

可书上讲的是, 应该 先恢复 restriction 再恢复 excursion. 而且, 如果 save-restriction 只能记录调用时的 buffer 的话, 那么像下面这样的代码也会出错的 (准确来说是, 与你的预期不符):

(save-excursion
  (save-restriction
    (set-buffer "1.txt")
    (widen)))

cireu 讲解的很清楚,不妨自己找个例子试一下

foo
foo
foo
bar
bar
bar
baz
baz
baz

(defun test1 ()
  "Test 1."
  (interactive)
    (save-excursion
      (save-restriction
        (goto-char (point-min))
        (forward-line 2)
        (narrow-to-region (point-min) (point))
        (goto-char (point-min))
        (replace-string "foo" "bar"))))

(defun test2 ()
  "Test 2."
  (interactive)
  (save-restriction
    (save-excursion
      (goto-char (point-min))
      (forward-line 2)
      (narrow-to-region (point-min) (point))
      (goto-char (point-min))
      (replace-string "foo" "bar"))))

自己运行一下 test1test2 两个函数,看看有什么不同

2 个赞

很棒的例子, 谢谢. 我一直将注意力放在 body 中可能出现的 widen 却疏忽了 narrow

所以有时候光看,想着想着就把自己绕糊涂了 :joy: 自己动手实验一下反而就豁然开朗

平时实验,推荐用论坛内大佬开发的一个包,不会污染当前的 Emacs 环境

我自己倒是更常用 with-emacs

写起来更顺畅,毕竟 repl 用起来还是有一点局促。

1 个赞

示例代码和书都没错,是你没理解概念. 搜一下“先进后出”.

我最近用到了这个技巧, emacs.d/lisp/init-misc.el at master · redguardtoo/emacs.d · GitHub

好强,待会儿研究下