《elisp 每日一练》—— Region

《elisp 每日一练》—— Region

Masimaro
2026-08-04 / 0 评论 / 1 阅读 / 正在检测是否收录...

在关于保存和恢复现场的例子中,我们了解了 marker 相关的概念。 markerpoint 都是一个表示位置的整数,不同的是marker 是动态的,由Emacs 内部根据buffer内容变化进行维护,而 point 是静态的。

它们两个结合可以构成我们今天要讨论的内容——Region

知识铺垫

在图形界面编辑器中,我们经常用鼠标拖拽高亮一段文字,这叫“选中文本”。而在 Emacs 中,这个概念叫 Region(选区)。

Region 是一个区间,它由 markerpoint 组成的闭区间。

我们可以使用这样一个公式来描述区间的组成: $\text{Region} = [\min(\text{point}, \text{mark}), \max(\text{point}, \text{mark})]$

也就是说,起始位置是从 marker 和当前 point 中找到最小值,终点位置是二者的最大值。所以我们需要注意,在编写有关 Region 的代码时绝对不能假设使用 marker 或者 point 作为起点,或者终点。

实际上为了解决这个问题,Emacs 提供了两个函数 region-beginningregion-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 是否出于激活状态。因为前面介绍过实际上 markerpoint 天然就构成一个 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 非常多,有 wordlistsymbolnumberfilename 等等。这里我们使用 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-beginningregion-end 获取选区的区间
  • activate-mark 激活选区
  • use-region-p 判断选区是否激活
  • upcase-word 将光标所在位置之后的单词转化为大写
  • upcase-region 将对应区间的单词转化为大写
  • bounds-of-thing-at-point 获取指定事物所在的区间
0

评论 (0)

取消