与上一小节介绍的往返格式规范不同(see 往返格式规范),你可以使用变量
after-insert-file-functions 和 write-region-annotate-functions
分别控制读取和写入时的转换过程。
转换从一种表示形式开始,并生成另一种表示形式。如果只需要执行一次转换,就不会出现起始数据的冲突。 但如果涉及多次转换,当两个转换都需要以同一份原始数据为起点时,就可能产生冲突。
这种情况在 write-region 处理文本属性转换时最容易理解。例如,缓冲区中第 42 位的字符是
‘X’,并带有文本属性 foo。如果对 foo 的转换是向缓冲区插入类似
‘FOO:’ 的内容,那么第 42 位的字符就会从 ‘X’ 变成 ‘F’,
下一次转换将直接从错误的数据开始。
为避免冲突,协作式转换不会修改缓冲区,而是通过指定注解(annotations)来实现。注解是形如
(position . string) 的列表,并按 position 升序排列。
如果存在多个转换,write-region 会将它们的注解合并为一个有序列表。
之后,当缓冲区中的文本真正写入文件时,会在对应位置插入这些注解。
整个过程不会修改缓冲区内容。
与之相反,读取时,与文本混合在一起的注解会被立即处理。
insert-file-contents 会将光标移到待转换文本的开头,
然后以该文本长度为参数调用转换函数。这些函数执行完毕后应保持光标位于插入文本的开头。
这种读取方式是合理的,因为第一个转换器移除的注解不会被后续转换器误处理。
每个转换函数应当扫描并识别自己关心的注解,移除该注解,修改缓冲区文本(例如设置文本属性),
然后返回修改后的文本长度。一个函数的返回值会作为下一个函数的参数。
write-region 要调用的函数列表。列表中的每个函数会接收两个参数:
待写入区域的起始位置和结束位置。这些函数不应修改缓冲区内容,而应返回注解。
特殊情况下,函数返回时可以切换当前缓冲区。Emacs 会认为该当前缓冲区包含需要输出的修改后文本,
因此会将 write-region 的 start 和 end 参数
分别改为新缓冲区的 point-min 和 point-max,
同时丢弃之前所有注解,视为已由该函数处理完毕。
如果该变量非 nil,其值应为一个函数。
该函数会在 write-region 执行完成后被无参调用。
如果 write-region-annotate-functions 中的某个函数返回时切换了当前缓冲区,
Emacs 会多次调用 write-region-post-annotation-function,
依次从最后一个当前缓冲区往回调用,直到原始缓冲区。
因此,write-region-annotate-functions 中的函数可以创建一个缓冲区,
在该缓冲区内将此变量局部设为 kill-buffer,
填入修改后的文本并设为当前缓冲区,该缓冲区会在 write-region 结束后被自动杀死。
insert-file-contents 调用此列表中的每个函数时,
会传入一个参数:已插入的字符数,并且光标位于插入文本的开头。
每个函数应保持光标位置不变,并返回经该函数修改后插入文本的新字符数。
我们欢迎用户编写 Lisp 程序,利用这些钩子在文件中保存和读取文本属性, 从而尝试各种数据格式并找到优秀的方案。最终我们希望用户能开发出通用、好用的扩展, 以便集成到 Emacs 中。
我们建议不要尝试将任意 Lisp 对象作为文本属性的名称或值—— 因为通用性过强的程序往往难以编写,且运行缓慢。 更好的做法是选择一组既灵活又易于编码的数据类型。