在第二天里面,我们介绍了光标移动和行索引区间的api函数,并且学会了如何移动光标。实际上在操作Emacs进行文本编译的动作时很多时候都在移动光标,只是我们感觉不到。
如果我们随意的移动光标就容易带来一个问题,我们执行某个操作之后找不到光标在哪了,如果我们还要花时间找光标在哪,就很浪费效率
背景知识铺垫
如果我们自己实现一个简单的版本,我们可能会考虑使用 let ((old-point (point))) 这样的语句来先保存最后通过 (goto-char old-point) 来进行设置。这个思路是对的,但是它主要的问题是point记录的是当前位置的索引,如果我们在当前光标位置之前插入了字符串改变了光标所在文本的索引,那么根据之前保存的位置值就是一个错误的值。
为了解决上一个问题,Emacs引入了 marker 的概念。与 point 不同,marker 是指向 Buffer 中特定位置的指针。如果在 Marker 之前插入或删除了文本,Emacs 的 C 语言底层会自动更新 Marker 的数值,使其依然指向原本对应的那个文字点。
我们可以通过 push-mark 和 pop-mark 来保存和恢复一个mark。从名字上可以看出来它是一个栈操作。需要注意的是,出栈时并不会返回栈顶存储的值,我们可以通过 mark 函数来获取栈顶的值。
为了清楚的体现第二个区别,我们可以写这样一个例子
(defun my/test-point()
(interactive)
(let ((old-point (point)))
(goto-char (point-min))
(insert "this is a test for point")
(goto-char old-point)))
(defun my/test-mark()
(interactive)
(push-mark)
(goto-char (point-min))
(insert "this is a test for point")
(goto-char (mark)) ;; 通过栈顶记录的marker 值来恢复光标位置
(pop-mark))到这里我们似乎解决问题了。我们可以通过管理marker 栈来实现保存和恢复现场。但是我们没有考虑一种情况:如果在push 之后 pop 恢复之前程序出错了,怎么办?我们要为这段代码加一个异常处理机制。
到这里我们就可以请出今天的主角了—— save-excursion 。这是一个用C实现的底层函数,用于保证在任何情况下都能恢复现场。你可以将它理解为底层通过try catch 包装了的 push-mark 和 pop-mark。
根据这点我们可以知道它需要注意的点:
- 它只用来还原光标现场,而不会还原中间对文本执行的插入、删除操作
- 避免在大的循环中使用,多次进行
push-mark和pop-mark会不断制造和销毁临时的marker资源,影响性能
它的定义如下:
(save-excursion &rest BODY)我们可以将上面的例子做一个改动
(defun my/test-mark()
(interactive)
(save-excursion
(goto-char (point-min))
(insert "this is a test for marker")))有了这个函数,我们能从繁琐的现场保存与恢复中解放出来,将注意力集中在具体功能的实现上。
练习
这次的练习比较简单,我们就简单的为上一次的复制行的函数加一个恢复现场的功能
(defun my/duplicate-current-line()
(interactive)
(save-excursion
(let ((line-next (buffer-substring (line-beginning-position) (line-end-position))))
(goto-char (line-end-position))
(newline)
(insert line-next))))总结
本篇我们讨论了Emacs中保护和还原现场,提到了 marker 和 point。同时也引入了 save-excursion 来保护和还原现场。
既然本篇章提到了 marker,Emacs中 marker 和 point 组成了一个很重要的概念—— region。一般我们叫它选区,也就是在编辑器中我们看到的用户选中区域。这部分内容将在后面进行介绍。
评论 (0)