Elisp日课:save-excursion 晦涩的命名却趁手的应用

昨晚看到电报群里热闹的讨论,抛砖分享个人学习的工作流,引引玉。(感谢学姐,这篇文章完全学习她无私分享和慷慨助人

浏览论坛内入门elisp学习的讨论,会发现都在强调 save-excursion这个函数,可能是写elisp最有用的函数之一。

在 rush blindly into details 之前,或者用 C-h f 打开 save-excursion 的黑匣子之前。我们先从抽象的思维层面考察此函数。

一、Literallly 字面义分析

先 literally 从字面上思考save-excursion,save毋庸赘言,拆解 excursion 为 ex + cur + sion 三个部分,其中 ex 是 out 向外 比如 exit, 而 cur 是行走,与 car 汽车同源,sion 为名词后缀,因此 excursion 就是走出去,远足,或者短途旅行的含义。

那么 save-excursion 是保存我的短途旅行吗(这句话非正常的表达)?或是保存远足旅行线路(似乎正常一点)? 也是教人一头雾水。

在引用手册定义之前,我们考察一个高频的应用场景。

二、工作流场景

假设当前正在读 elisp-introduction 手册,看到 save-excursion 函数,想要查询。

image

最便捷的方式就是 C-h f save-excursion:

但我比较不推荐这种方式,一是 help-info 不能编辑, 二是 不能对 save-excursion 这个函数一眼看到底的穷尽展示。

推荐从 elisp-manual 的 org 文件中查询:

在此处,一个亟待解决的问题凸显,我从当前的焦点走出去 excursion 出去做其他的事情,一番折腾之后,我还回得来吗?

可以想见,在excursion出去参阅manual的时候,又会遇到其他问题,比如查询单词,比如又查询新的函数,比如查看今天的任务等等,excursion 之外又有 excursion。只要分心走出去,几乎必然回不到最初的焦点。

此时,point-to-register 如及时雨赶到。在我们出发之前,就有了如下工作流:

1)C-x r Spc Spc 调用 point-to-register 在出发去excursion 之前,保存当前的焦点到 Spec 键上;
2) Excursion 浪出去,尽情肆意的做任何任务;
3) 任务完成之后,回到最初的焦点,C-x r j Spec 调用 jump-to-register。

以上三个步骤保障我们在任何时候,excursion出去做事,都能迷途知返。是最高频应用的工作流。

三、定义 save-excursion

以上三步的具体操作工作流,我们将其抽象出来,命名为 save-point-before-excurion 或者 save-point-for-excursion,或者将中间的两个单词去掉就是 save-excursion.

     (save-excursion
       task1
       task2
        ...
       last-task)

也就是说 save-excursion 自动完成了 point-to-register 和 jump-to-register 这两步启动和收尾的步骤,我们只须放宽心在excursion里做任务即可。

这时候,我们再打开黑匣子,查看定义:

This special form saves the identity of the current buffer and the value of point in it, evaluates BODY, and finally restores the buffer and its saved value of point. Both saved values are restored even in case of an abnormal exit via ‘throw’ or error (*note Nonlocal Exits::).

从定义中能窥见,能自然流畅逻辑无阻塞的从抽象思维到具体使用的save-excursion这个函数的方法,就是在大脑里将其自动扩展为:save-point-for-excursion.

如果本文不会招来评论区无价值的讨论,就做成日课,每日分享。

10 个赞

感谢作者分享。请问这个ELisp手册的org版本是在哪里下载的呀?

感谢分享,虽然用了好几年Emacs,但基本上就拿来当记事本用,希望楼主多分享点儿像这种用基础命令组成的整套“流”的用法场景。

2 个赞

需要自己将手册从网页转到org。(有闲时间,看一点转一点

我自己转的版本,因为陆陆续续用了一年了,里面很多私人备注,应用场景联想什么的,没法重新提取个纯净版共享出来。

我觉得用helpful-mode就很好,直接查询函数实现,还能直接跳到对应的手册位置。

Save point, and current buffer; execute BODY; restore those things.

文档已经说的很清楚了,阁下绕了地球一圈

1 个赞

嗯,你这个问题很有代表性,所以文档说得很清楚了,函数命名为 foo9547 行不行呢?

1 个赞

函数名的意义解释得很清楚了。我就是知道这个函数的用法,名字老是忘记。

1 个赞

It is often useful to move point temporarily within a localized portion of the program. This is called an “excursion”, and it is done with the ‘save-excursion’ special form.

(info "excursion")

忘记应该是忘记 excursion 这个单词怎么拼吧 :rofl:

1 个赞

额,我觉得不需要绕来绕去,Emacs 里面有很多可以讲的东西。

save-excursion 会保存 mark 的状态和当前的位置(包括光标与 window 相对的行数),执行 body,恢复这些状态。这个核心逻辑很简单,几乎所有的编辑 API 里面都有这样的功能。

这个函数曾经叫 save-mark-and-excursion,之前的 save-excursion 是只含 mark 的(是的,名字很有困惑性),在 Emacs 25.1 之后重命名成了现在的样子。说到这里你应该发现,Emacs 中难以维护的,其实并不是光标的位置,而是 mark 的状态,因为 point 只是一个值,而 mark 是 mark-ring

save-excursion 的实现就是咋看下就是用 unwind-protect 来恢复各种位置信息,实际也确实如此。所以:

  1. 就算 body 中有异常,依然可以回到先前的位置
  2. 即使函数中触发了 keyboard-quit ,也可以回到先前的位置

但是要注意如果在 BODY 中对原先位置的内容进行修改,即便 point 值依然有效(point-min < point < point-max ),你也没有办法回去了。比如你 erase-buffer 后再插入一样的内容,是无法回到先前的位置的。所以 save-excursion 的 BODY 最好仅仅做移动的操作,就如命令的名字。

save-excursion 还有几个使用场景接近的兄弟。。。

24 个赞

直达目的, 直抒胸臆, 言简意赅, 总结全面, 乃技术文章之范本.

3 个赞

狗哥的理解很到位,用最简单的讲解和大家分享,赞

2 个赞

感谢分享。这个函数很确实很有用,我居然现在才用上 :joy: :joy: ,因为没有用yas,我插入代码段时都是先写模板文件,然后在写elisp函数,在通过m-x自动输入等办法调用。

在当前buffer插入代码块后,需要回到初始位置,不用 save-excursion,是没有道理的

(defun yas-file-css-flex-center()
  "flex来做元素上下左右垂直居中的代码"
  (interactive)
  (save-excursion
    ;;(insert-file "")
    (insert "display:flex;\n justify-content:center;\n align-items:center;\n")
    )
  ;;(yas-file-ins-end)
)

图片

2 个赞

这个函数虽然趁手,可惜不能异步,如果能 异步的返回原位置 就好了

doc string 提示出来了,这个怎么做到的?

save 系列的很多函数都特别有用

我自己常用的还有 save-restriction save-match-data

这个是 ivy-rich

1 个赞

前天晚上刷到大佬推荐这篇帖子: