在关于保存和恢复现场的例子中,我们了解了 marker 相关的概念。 marker 与 point 都是一个表示位置的整数,不同的是marker 是动态的,由Emacs 内部根据buffer内容变化进行维护,而 point 是静态的。
它们两个结合可以构成我们今天要讨论的内容——Region
知识铺垫
在图形界面编辑器中,我们经常用鼠标拖拽高亮一段文字,这叫“选中文本”。而在 Emacs 中,这个概念叫 Region(选区)。
Region 是一个区间,它由 marker 和 point 组成的闭区间。
我们可以使用这样一个公式来描述区间的组成: $\text{Region} = [\min(\text{point}, \text{mark}), \max(\text{point}, \text{mark})]$
也就是说,起始位置是从 marker 和当前 point 中找到最小值,终点位置是二者的最大值。所以我们需要注意,在编写有关 Region 的代码时绝对不能假设使用 marker 或者 point 作为起点,或者终点。
实际上为了解决这个问题,Emacs 提供了两个函数 region-beginning 和 region-end 来获取Region的开始和结束位置
我们如何激活一个区域呢?可以通过 activate-mark 函数来激活当前选取。又或者可以通过 push-mark 在创建 marker 的同时进行激活。实际上 push-mark 的函数原形为:
(push-mark &optional LOCATION NOMSG ACTIVATE)后面几个参数是可选的,它们分别代表:
LOCATION: 表示作为新marker位置的point值,默认是当前光标所在位置NOMSG: 是否在minibuffer 中显示Mark set字样, 默认为 nil 也就是不显示ACTIVATE: 是否激活当前 Region,默认为 nil 不激活
我们可以用一个例子测试一下
(defun my/select-whole-buffer()
(interactive)
(goto-char (point-min))
(push-mark (point-max) nil t))上述例子我们将整个buffer的内容进行全选。
另外,在操作 Region 时我们经常需要判断当前 Region 是否出于激活状态。因为前面介绍过实际上 marker 和 point 天然就构成一个 Region。如果不进行判断,用户想要进行某些操作时会出现不可预料的情况。例如如果用户想在选中区域进行查找,或者在当前行查找,如果不进行判断,有可能用户没有进行选区操作但是查找时却因为隐藏的region 导致查找发生在region中
我们可以通过 use-region-p 来判断用户是否激活了选取
练习
我们实现一个函数,如果用户选择了一个区域,那么将区域中的所有字母大写,否则将光标所在单词的字母转换为大写。
将单个单词大写可以使用函数 upcase-region,而将单词大写可以使用 upcase-word。
upcase-region 接收一个起始位置和结束位置,将这个区间内的字母都转化为大写upcase-word 接收一个整数,表示将光标所在位置之后(正数)、或者之前(负数参数)的多少个单词转化为大写
upcase-word 仅仅只能将光标所在位置到单词结尾位置的字母大写,我们还需要一个方法来获取单词首部,然后通过 goto-char 的方式移动光标到词首,最后执行 upcase-word。当然有了范围也可以使用 upcase-region
获取光标所在单词的范围可以使用 bounds-of-thing-at-point。他的函数原形如下:
(bounds-of-thing-at-point THING)它支持的 THING 非常多,有 word 、list、symbol、number、filename 等等。这里我们使用 word 就行。
它返回的格式是 (START . END)。结合之前介绍的CELL的操作就可以获得START 的值。
我们可以做一个快速选择光标所在单词的函数来看看它的用法
(defun my/select-the-word()
(interactive)
(let ((bound (bounds-of-thing-at-point 'word)))
(progn
(goto-char (car bound))
(push-mark (cdr bound))
(activate-mark))))这里我们确实需要改变mark栈来和当前光标位置组成一个新的区域,所以不适合使用 save-excursion 来保存和还原现场。
另外这里也演示了如何通过 activate-mark 来激活一个选区。
有了这些函数的辅助,最终我的实现如下:
(defun my/upcase-target()
(interactive)
(if (use-region-p)
;;用户选中区域,转换区域内文本
(upcase-region (region-beginning) (region-end))
;;用户未选中文本,直接转换单词
(save-excursion ;; 考虑保存和恢复现场
(let ((bound (bounds-of-thing-at-point 'word)))
(if bound
(progn
(goto-char (car bound))
(upcase-word 1)))))))至于如何使用 upcase-region 来实现将光标所在单词转化为大写的功能,读者可以自行尝试。
总结
本篇我们了解了 Region 的概念,以及可以使用Region做一些事情。后续很多高级的功能都可以依赖region 来实现,例如在选区中搜索、替换文本。
今天介绍的函数有:
region-beginning和region-end获取选区的区间activate-mark激活选区use-region-p判断选区是否激活upcase-word将光标所在位置之后的单词转化为大写upcase-region将对应区间的单词转化为大写bounds-of-thing-at-point获取指定事物所在的区间
评论 (0)