32.7 标记点

每个缓冲区都有一个特殊标记,称为标记点(the mark)。新建缓冲区时,该标记存在但不指向任何位置,意味着此时缓冲区中尚未设置标记点。后续命令可以设置该标记点。

标记点为许多命令指定文本范围的边界,例如 kill-regionindent-rigidly。这些命令通常作用于光标与标记点之间的文本,这段文本称为区域(region)。如果你编写操作区域的命令,不要直接检查标记点;而应在 interactive 中使用 ‘r’ 声明。这样在交互式调用时会将光标与标记点的值作为参数传给命令,同时允许其他 Lisp 程序显式指定参数。See interactive 的代码字符

部分命令会附带设置标记点。命令仅应在对用户有用时才这样做,绝不应用于自身内部用途。例如,replace-regexp 命令在执行替换前会将标记点设为当前光标位置,方便用户替换完成后快速返回该处。

标记点一旦在缓冲区中存在,通常就不会消失。但如果启用临时标记模式,它可能变为非激活(inactive)状态。缓冲区局部变量 mark-activenil 表示标记点处于激活状态。命令可以调用 deactivate-mark 直接取消标记点激活,或将变量 deactivate-mark 设为非 nil,请求返回编辑器命令循环后取消激活。

如果启用临时标记模式,某些通常作用于光标附近文本的编辑命令,在标记点激活时会改为作用于区域。这是使用临时标记模式的主要目的。(另一个目的是激活标记点时高亮显示区域。See Emacs 显示。)

除标记点外,每个缓冲区还有一个标记环(mark ring),它是一个由历史标记点组成的列表。编辑命令修改标记点时,通常应将旧标记点保存到标记环中。变量 mark-ring-max 指定标记环的最大条目数;列表达到该长度后,新增元素会删除最旧元素。

此外还有独立的全局标记环,但仅用于少数特定用户级命令,与 Lisp 编程无关,因此此处不做说明。

Function: mark &optional force

此函数以整数形式返回当前缓冲区的标记点位置;如果从未设置过标记点则返回 nil

如果启用临时标记模式且 mark-even-if-inactivenil,标记点非激活时 mark 会报错。但如果 forcenil,则 mark 忽略非激活状态,仍返回标记点位置(或 nil)。

Function: mark-marker

此函数返回表示当前缓冲区标记点的标记。它不是副本,而是内部使用的标记。因此,直接修改该标记的位置会改变缓冲区的标记点。除非确有需要,否则请勿这样做。

(setq m (mark-marker))
     ⇒ #<marker at 3420 in markers.texi>
(set-marker m 100)
     ⇒ #<marker at 100 in markers.texi>
(mark-marker)
     ⇒ #<marker at 100 in markers.texi>

与其他标记一样,该标记可以指向任意缓冲区。如果让它指向非所属缓冲区,结果虽然一致但非常怪异。我们建议不要这样做!

Function: set-mark position

此函数将标记点设为 position 并激活标记点。旧标记点值**不会**压入标记环。

注意:仅当你希望用户看到标记点移动且丢弃旧位置时,才使用此函数。通常设置新标记点时,旧值应存入 mark-ring。因此大多数程序应使用 push-markpop-mark,而非 set-mark

Emacs Lisp 初学者常误用标记点。标记点是为方便用户保存位置。编辑命令不应随意修改标记点,除非修改是命令用户级功能的一部分。(这种情况下应在文档中说明。)如需在 Lisp 程序内部保存位置,请存入 Lisp 变量。例如:

(let ((beg (point)))
  (forward-line 1)
  (delete-region beg (point))).
Function: push-mark &optional position nomsg activate

此函数将当前缓冲区标记点设为 position,并将旧标记点副本压入 mark-ring。如果 positionnil,则使用光标位置。

push-mark 通常 激活标记点。如需激活,请将参数 activate 设为 t

除非 nomsgnil,否则会显示提示信息 ‘Mark set’。

Function: pop-mark

此函数弹出 mark-ring 顶部元素,设为缓冲区当前标记点。它不会移动光标,标记环为空时不执行操作。它会取消标记点激活。

User Option: transient-mark-mode

该变量若不为 nil,则启用 临时标记模式(Transient Mark mode)。在临时标记模式下,每个修改缓冲区的原语都会设置 deactivate-mark。因此,大多数修改缓冲区的命令也会取消标记激活状态。

启用临时标记模式且标记处于激活状态时,许多原本作用于光标附近文本的命令会改为作用于区域。这类命令应当使用函数 use-region-p 来判断是否应操作区域。See 区域

Lisp 程序可以将 transient-mark-mode 设置为非 nil、非 t 的值,以临时启用临时标记模式。若值为 lambda,则在执行任何通常会取消标记的操作(如修改缓冲区)后,临时标记模式会自动关闭。若值为 (only . oldval),则在执行任何后续移动光标且非 Shift 转换的命令后(see shift-translation),或是执行任何通常会取消标记的其他操作后,transient-mark-mode 会恢复为 oldval。(使用鼠标标记区域时会以这种方式临时启用 transient-mark-mode。)

User Option: mark-even-if-inactive

若该变量不为 nil,Lisp 程序与 Emacs 用户即使在标记未激活时也能使用标记。该选项影响临时标记模式的行为。当该选项非 nil 时,标记取消激活只会关闭区域高亮,但使用标记的命令仍会表现得如同标记依然激活一般。

Variable: deactivate-mark

若编辑命令将该变量设为非 nil,则在命令返回后,编辑命令循环会取消标记的激活状态(前提是启用了临时标记模式)。所有修改缓冲区的原语都会设置 deactivate-mark,以便在命令结束时取消标记。设置该变量会使其变为缓冲区局部变量。

若要编写在命令结束时不触发标记取消激活的缓冲区修改 Lisp 代码,可在执行修改的代码外围将 deactivate-mark 绑定为 nil。例如:

(let (deactivate-mark)
  (insert " "))
Function: deactivate-mark &optional force

若启用了临时标记模式,或 forcenil,该函数会取消标记激活状态并运行常规钩子 deactivate-mark-hook。否则不执行任何操作。

Variable: mark-active

当该变量非 nil 时,标记处于激活状态。该变量在每个缓冲区中均为局部变量。不要使用该变量的值来判断原本作用于光标附近文本的命令是否应改为作用于区域。此类判断应使用函数 use-region-p(see 区域)。

Variable: activate-mark-hook
Variable: deactivate-mark-hook

这两个常规钩子分别在标记激活与标记取消激活时运行。钩子 activate-mark-hook 也会在区域重新激活时运行,例如使用命令切回带有激活标记的缓冲区后。

Function: handle-shift-selection

该函数实现光标移动命令的 Shift 选择行为。See Shift Selection in The GNU Emacs Manual。每当调用在 interactive 声明中包含 ‘^’ 字符的命令时,Emacs 命令循环会在命令自身执行前自动调用该函数(see ^)。

shift-select-modenil 且当前命令通过 Shift 转换调用(see shift-translation),该函数会设置标记并临时激活区域,除非区域已通过此方式临时激活。否则,若区域已被临时激活,则取消标记激活状态并将变量 transient-mark-mode 恢复为之前的值。

Variable: mark-ring

该缓冲区局部变量的值为当前缓冲区已保存的历史标记列表,最新记录排在最前。

mark-ring
⇒ (#<marker at 11050 in markers.texi>
    #<marker at 10832 in markers.texi>
    ...)
User Option: mark-ring-max

该变量的值为 mark-ring 的最大容量。若向 mark-ring 中压入的标记数量超过该值,push-mark 在添加新标记时会丢弃旧标记。

启用删除选择模式时(see Delete Selection in The GNU Emacs Manual),作用于激活区域(又称“选择区(selection)”)的命令行为会略有不同。其实现方式是将函数 delete-selection-pre-hook 添加到 pre-command-hook(see 命令循环概述)。该函数会调用 delete-selection-helper,根据命令类型适当删除选择内容。若要让命令适配删除选择模式,可在函数的符号上添加 delete-selection 属性(see 访问符号属性);未设置该属性的命令不会删除选择内容。该属性可设为多种值,以根据命令功能定制行为;具体细节参见 delete-selection-pre-hookdelete-selection-helper 的文档字符串。