操作对象之四 ── 文本

文本的插入删除,查找替换操作已经在缓冲区一节中讲过了。这一节主要介绍文 本属性。

如果使用过其它图形界面的文本组件进行编程,它们对于文本的高亮一般都是采 用给对应文本贴上相应标签的方法。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 将多个字符显示为一个字形

正是由于 emacs 的文本有如此丰富的属性,使得 emacs 里的文字才变得多彩, 变得人性化。

查看文本属性

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

查看文本对象在某处的文本属性可以用 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

思考题

写一个命令,可在 text-mode 里用指定模式给选中的文本添加高亮。

函数列表

(propertize STRING &rest PROPERTIES)
(get-text-property POSITION PROP &optional OBJECT)
(get-char-property POSITION PROP &optional OBJECT)
(text-properties-at POSITION &optional OBJECT)
(put-text-property START END PROPERTY VALUE &optional OBJECT)
(add-text-properties START END PROPERTIES &optional OBJECT)
(set-text-properties START END PROPERTIES &optional OBJECT)
(remove-text-properties START END PROPERTIES &optional OBJECT)
(remove-list-of-text-properties START END LIST-OF-PROPERTIES &optional OBJECT)
(next-property-change POSITION &optional OBJECT LIMIT)
(next-single-property-change POSITION PROP &optional OBJECT LIMIT)
(next-char-property-change POSITION &optional LIMIT)
(next-single-char-property-change POSITION PROP &optional OBJECT LIMIT)
(previous-property-change POSITION &optional OBJECT LIMIT)
(previous-single-property-change POSITION PROP &optional OBJECT LIMIT)
(previous-char-property-change POSITION &optional LIMIT)
(previous-single-char-property-change POSITION PROP &optional OBJECT LIMIT)
(text-property-any START END PROPERTY VALUE &optional OBJECT)
(text-property-not-all START END PROPERTY VALUE &optional OBJECT)

问题解答

手工高亮代码

(defun my-fontify-region (beg end mode)
  (interactive
   (list (region-beginning)
         (region-end)
         (intern
          (completing-read "Which mode to use: "
                           obarray (lambda (s)
                                     (and (fboundp s)
                                          (string-match "-mode$" (symbol-name s))))
                           t))))
  (let ((buf (current-buffer))
        (font-lock-verbose nil)
        (start 1) face face-list)
    (set-text-properties beg end '(face nil))
    (with-temp-buffer
      (goto-char (point-min))
      (insert-buffer-substring buf beg end)
      (funcall mode)
      (font-lock-fontify-buffer)
      (or (get-text-property start 'face)
          (setq start (next-single-property-change start 'face)))
      (while (and start (< start (point-max)))
        (setq end (or (next-single-property-change start 'face)
                      (point-max))
              face (get-text-property start 'face))
        (and face end (setq face-list (cons (list (1- start) (1- end) face) face-list)))
        (setq start end)))
    (when face-list
      (dolist (f (nreverse face-list))
        (put-text-property (+ beg (car f)) (+ beg (cadr f))
                           'face (nth 2 f))))))

但是直接从那个临时缓冲区里把整个代码拷贝出来也可以了,但是可能某些情况 下,不好修改当前缓冲区,或者不想把那个模式里其它文本属性拷贝出来,这个 函数还是有用的。当然最主要的用途是演示使用查找和添加文本属性的方法。事 实上这个函数也是我用来高亮 muse 模式里 src 标签内源代码所用的方法。但是 不幸的是 muse 模式里这个函数并不能产生很好的效果,不知道为什么。

Comments

Included file 'disqus_thread.html' not found in _includes directory