Elisp 操作对象:文本

Table of Contents

如果使用过其它图形界面的文本组件进行编程,它们对于文本的高亮一般都是采用给对应文本贴上相应标签的方法。Emacs 的处理方法也是类似的,但是相比之下要强大的多。在 Emacs 里,在不同位置上的每个字符都可以有一个属性列表。这个属性列表和符号的属性列表很相似,都是由一个名字和值构成的对组成。名字和值都可以是一个 lisp 对象,但是通常名字都是一个符号,这样可以用这个符号来查找相应的属性值。复制文本通常都会复制相应的字符的文本属性,但是也可以用相应的函数只复制文本字符串,比如 substring-no-properties、 insert-buffer-substring-no-properties、buffer-substring-no-properties。

产生一个带属性的字符串可以用 propertize 函数

(propertize "abc" 'face 'bold)          ; => #("abc" 0 3 (face bold))

如果你在一个 text-mode 的缓冲区内用 M-x eval-expression 用 insert 函数 插入前面这个字符串,就会发现插入的文本已经是粗体字了。之所以不能在 scratch 产生这种效果,是因为通常我们是开启了 font-lock-mode,在 font-lock-mode 里,文本的 face 属性是实时计算出来的。 在插入文本之后,它的 face 属性已经很快地被改变了。你可以在关闭 font-lock-mode 后再测试一次应该是可以看到 scratch 里也是可以用这种方法插入带 face 属性的文本的。

虽然文本属性的名字可以是任意的,但是一些名字是有特殊含义的。

属性名 含义
category 值必须是一个符号,这个符号的属性将作为这个字符的属性
face 控制文本的字体和颜色
font-lock-face 和 face 相似,可以作为 font-lock-mode 中静态文本的 face
mouse-face 当鼠标停在文本上时的文本 face
fontified 记录是否使用 font lock 标记了 face
display 改变文本的显示方式,比如高、低、长短、宽窄,或者用图片代替
help-echo 鼠标停在文本上时显示的文字
keymap 光标或者鼠标在文本上时使用的按键映射
local-map 和 keymap 类似,通常只使用 keymap
syntax-table 字符的语法表
read-only 不能修改文本,通过 stickness 来选择可插入的位置
invisible 不显示在屏幕上
intangible 把文本作为一个整体,光标不能进入
field 一个特殊标记,有相应的函数可以操作带这个标记的文本
cursor (不知道具体用途)
pointer 修改鼠标停在文本上时的图像
line-spacing 新的一行的距离
line-height 本行的高度
modification-hooks 修改这个字符时调用的函数
insert-in-front-hooks 与 modification-hooks 相似,在字符前插入调用的函数
insert-behind-hooks 与 modification-hooks 相似,在字符后插入调用的函数
point-entered 当光标进入时调用的函数
point-left 当光标离开时调用的函数
composition 将多个字符显示为一个字形

查看文本属性

由于字符串和缓冲区都可以有文本属性,所以下面的函数通常不提供特定参数就是检 查当前缓冲区的文本属性,如果提供文本对象,则是操作对应的文本属性。

查看文本对象在某处的文本属性可以用 get-text-property 函数。

(setq foo (concat "abc"
                  (propertize "cde" 'face 'bold))) ; => #("abccde" 3 6 (face bold))
(get-text-property 3 'face foo)                    ; => bold
(save-excursion
  (goto-char (point-min))
  (insert foo))
(get-text-property 4 'face)                        ; => bold

get-char-property 和 get-text-property 相似,但是它是先查找 overlay 的 文本属性。overlay 是缓冲区文字在屏幕上的显示方式,它属于某个缓冲区,具 有起点和终点,也具有文本属性,可以修改缓冲区对应区域上文本的显示方式。

get-text-property 是查找某个属性的值,用 text-properties-at 可以得到某 个位置上文本的所有属性。

修改文本属性

put-text-property 可以给文本对象添加一个属性。比如

(let ((str "abc"))
  (put-text-property 0 3 'face 'bold str)
  str)                                  ; => #("abc" 0 3 (face bold))

和 put-text-property 类似,add-text-properties 可以给文本对象添加一系 列的属性。和 add-text-properties 不同,可以用 set-text-properties 直接 设置文本属性列表。你可以用 (set-text-properties start end nil) 来除去 某个区间上的文本属性。也可以用 remove-text-properties 和 remove-list-of-text-properties 来除去某个区域的指定文本属性。这两个函 数的属性列表参数只有名字起作用,值是被忽略的。

(setq foo (propertize "abcdef" 'face 'bold
                      'pointer 'hand))
;; => #("abcdef" 0 6 (pointer hand face bold))
(set-text-properties 0 2 nil foo)       ; => t
foo   ; => #("abcdef" 2 6 (pointer hand face bold))
(remove-text-properties 2 4 '(face nil) foo) ; => t
foo   ; => #("abcdef" 2 4 (pointer hand) 4 6 (pointer hand face bold))
(remove-list-of-text-properties 4 6 '(face nil pointer nil) foo) ; => t
foo   ; => #("abcdef" 2 4 (pointer hand))

查找文本属性

文本属性通常都是连成一个区域的,所以查找文本属性的函数是查找属性变化的 位置。这些函数一般都不作移动,只是返回查找到的位置。使用这些函数时最好 使用 LIMIT 参数,这样可以提高效率,因为有时一个属性直到缓冲区末尾也没 有变化,在这些文本中可能就是多余的。

next-property-change 查找从当前位置起任意一个文本属性发生改变的位置。 next-single-property-change 查找指定的一个文本属性改变的位置。 next-char-property-change 把 overlay 的文本属性考虑在内查找属性发生改 变的位置。next-single-property-change 类似的查找指定的一个考虑 overlay 后文本属性改变的位置。这四个函数都对应有 previous- 开头的函数,用于查 找当前位置之前文本属性改变的位置

(setq foo (concat "ab"
                  (propertize "cd" 'face 'bold)
                  (propertize "ef" 'pointer 'hand)))
;; => #("abcdef" 2 4 (face bold) 4 6 (pointer hand))
(next-property-change 1 foo)                  ; => 2
(next-single-property-change 1 'pointer foo)  ; => 4
(previous-property-change 6 foo)              ; => 4
(previous-single-property-change 6 'face foo) ; => 4

text-property-any 查找区域内第一个指定属性值为给定值的字符位置。 text-property-not-all 和它相反,查找区域内第一个指定属性值不是给定值的 字符位置。

(text-property-any 0 6 'face 'bold foo)          ; => 2
(text-property-any 0 6 'face 'underline foo)     ; => nil
(text-property-not-all 2 6 'face 'bold foo)      ; => 4
(text-property-not-all 2 6 'face 'underline foo) ; => 2