33.19.8 定义可点击文本

可点击文本(Clickable text)是指可以通过鼠标点击或键盘命令触发相应操作的文本。 许多主模式使用可点击文本来实现文本超链接,简称链接(links)

插入和管理链接最简单的方式是使用 button 包,参见 See 按钮。 本节将介绍如何通过文本属性在缓冲区中手动设置可点击文本, 为简洁起见,将可点击文本统称为链接(link)

实现链接包含三个独立步骤:(1) 鼠标悬停在链接上时提示可点击; (2) 使 RETmouse-2 在该链接上执行对应操作; (3) 设置 follow-link 条件,使链接支持 mouse-1-click-follows-link

要提示可点击状态,需为链接文本添加 mouse-face 文本属性, Emacs 会在鼠标悬停时高亮该链接。此外,还应通过 help-echo 文本属性 定义提示框或回显区消息。See 具有特殊含义的文本属性。 例如,Dired 用以下方式标记文件名可点击:

 (if (dired-move-to-filename)
     (add-text-properties
       (point)
       (save-excursion
         (dired-move-to-end-of-filename)
         (point))
       '(mouse-face highlight
         help-echo "mouse-2: visit this file in other window")))

要使链接可点击,需将 RETmouse-2 绑定到执行目标操作的命令。 每个命令应检查是否在链接上调用,并执行相应逻辑。 例如,Dired 主模式按键映射将 mouse-2 绑定到如下命令:

(defun dired-mouse-find-file-other-window (event)
  "In Dired, visit the file or directory name you click on."
  (interactive "e")
  (let ((window (posn-window (event-end event)))
        (pos (posn-point (event-end event)))
        file)
    (if (not (windowp window))
        (error "No file chosen"))
    (with-current-buffer (window-buffer window)
      (goto-char pos)
      (setq file (dired-get-file-for-visit)))
    (if (file-directory-p file)
        (or (and (cdr dired-subdir-alist)
                 (dired-goto-subdir file))
            (progn
              (select-window window)
              (dired-other-window file)))
      (select-window window)
      (find-file-other-window (file-name-sans-versions file t)))))

该命令使用函数 posn-windowposn-point 确定点击位置,使用 dired-get-file-for-visit 确定要打开的文件。

不必在主模式按键映射中绑定鼠标命令,也可以通过 keymap 文本属性 在链接文本内部绑定(see 具有特殊含义的文本属性)。例如:

(let ((map (make-sparse-keymap)))
  (define-key map [mouse-2] 'operate-this-button)
  (put-text-property link-start link-end 'keymap map))

通过该方式可以轻松为不同链接定义不同命令, 同时缓冲区其他文本仍可使用 RETmouse-2 的全局绑定。

Emacs 中点击链接的基础命令为 mouse-2。 为兼容其他图形应用,Emacs 也支持在链接上快速点击 mouse-1(点击时不移动鼠标)触发链接, 该行为由用户选项 mouse-1-click-follows-link 控制。 See Mouse References in The GNU Emacs Manual

要使链接支持 mouse-1-click-follows-link, 必须满足以下任一条件:(1) 为链接文本添加 follow-link 文本或覆盖层属性; 或 (2) 将 follow-link 事件绑定到按键映射(可以是主模式按键映射, 或通过 keymap 文本属性指定的局部映射)。 follow-link 属性的值或事件绑定结果,作为链接操作的判定条件。 该条件向 Emacs 说明两点:何种情况下 mouse-1 点击应视为在链接内, 以及如何计算动作码以决定 mouse-1 点击的转换目标。 链接操作条件可以是以下之一:

mouse-face

若条件为符号 mouse-face,则当某位置存在非 nilmouse-face 属性时, 视为位于链接内。动作码始终为 t

例如,Info 模式处理 mouse-1 的方式:

(keymap-set Info-mode-map "<follow-link>" 'mouse-face)
函数

若条件为函数 func,则当 (func pos) 求值非 nil 时, 位置 pos 视为在链接内。func 的返回值作为动作码。

例如,pcvs 模式仅允许 mouse-1 在文件名上触发链接:

(keymap-set map "<follow-link>"
            (lambda (pos)
              (eq (get-char-property pos 'face) 'cvs-filename-face)))
其他任意值

若条件值为其他类型,则该位置始终视为在链接内,条件本身即为动作码。 显然,此类条件仅应通过链接文本的文本属性或覆盖层属性设置 (避免作用于整个缓冲区)。

动作码决定 mouse-1 如何触发链接:

字符串或向量

若动作码为字符串或向量,mouse-1 事件会转换为字符串或向量的第一个元素; 即 mouse-1 的操作对应该字符或符号的局部/全局绑定。 例如,动作码为 "foo" 时,mouse-1 转换为 f; 为 [foo] 时,转换为 foo

其他任意非 nil 值

对于其他非 nil 的动作码,mouse-1 事件会转换为 同一位置的 mouse-2 事件。

要为 define-button-type 定义的按钮启用 mouse-1 激活功能, 需为按钮设置 follow-link 属性,属性值为前述链接操作条件。 See 按钮。例如,帮助模式处理 mouse-1 的方式:

(define-button-type 'help-xref
  'follow-link t
  'action #'help-button-action)

要为 define-widget 定义的控件设置 mouse-1, 需为控件添加 :follow-link 属性,属性值为前述链接操作条件。 例如,link 控件将 mouse-1 点击转换为 RET

(define-widget 'link 'item
  "An embedded link."
  :button-prefix 'widget-link-prefix
  :button-suffix 'widget-link-suffix
  :follow-link "\C-m"
  :help-echo "Follow the link."
  :format "%[%t%]")

若当前缓冲区位置 pos 位于链接上,函数返回非 nilpos 也可以是鼠标事件位置,由 event-start 返回(see 访问鼠标事件)。