《elisp 每日一练》——abbrev table

《elisp 每日一练》——abbrev table

Masimaro
2026-09-19 / 0 评论 / 0 阅读 / 正在检测是否收录...

一般,定义Major Mode 的时候需要管理 hookmode-classmode-namekeymapabbrev-tablesyntax-table这么六种资源。我们在前面介绍buffer 相关内容的时候我们跳过了 abbrev-table 的介绍,仅仅简单介绍了 syntax-table。在正式探索Major Mode 管理 abbrev-table 的秘密之前,需要先讨论一下 abbrev-table 的基础以及基本的使用方法

基础知识

所谓的 abbrev-table 它的全称是 Abbreviation Table 缩写表。读者如果对我之前写的vim script 教程有映像的话应该了解,abbrev 可以做到根据我们输入的简短的几个字母或者单词帮助我们扩展出想要的内容。例如我们输入 fn 按下 回车或者空格或者其他能表示输入关键字结束的按键,它会自动帮助我们将 fn 替换为 function

一般来讲,一个 Abbrev Table 就是一个专门用来存储“缩写词 --> 展开词”映射关系的哈希表/查找表。

与变量类似,Emacs中也有两种类型:buffer local 和 global 全局。buffer local 中的 abbrev-table 变量名称为 local-abbrev-table,而 全局的 abbrev-table 名称为 global-abbrev-table

定义并启用 abbrev-table

我们可以通过 define-abbrev-table 定义一个缩写表。它的函数签名如下:

(define-abbrev-table TABLENAME DEFINITIONS &optional DOCSTRING &rest PROPS)
  • TABLENAME 是当前表的名称,它是一个符号
  • DEFINITIONS 是一组定义好的映射关系,它是一个列表,列表的每一项都是一组 (key value) 形式的映射
  • DOCSTRING 是当前列表的文档
  • PROPS 是一个属性的列表。

keymap 类似,它也支持继承机制,我们可以通过 PROPS 中的 :parents 定义父表。

例如我们可以在 scratch buffer 中定义如下的 abbrev-table

(define-abbrev-table 'my/list-abbrev-table
  '(("df" "(defun ()\n)")
    ("dm" "(defmacro ())")))

我们可以通过开启buffer 的 abbrev-mode 来启用 abbrev-table,Emacs会根据输入的内容自动触发缩写,也可以调用 expand-abbrev 手动通知Emacs触发。

在触发的时候Emacs会根据事先定义的 abbrev-table,按照优先查找 local-abbrev-table 然后查找 global-abbrev-table 的顺序。所以如果我们希望我们定义的 abbrev-table 能启用,可以使用 setq 将我们定义的变量绑定到 local-abbrev-table

因此我们如果要启用自定义的 abbrev-table,完整的代码应该如下:

(define-abbrev-table 'my/list-abbrev-table
  '(("df" "(defun ()\n)")
    ("dm" "(defmacro ())")))

(setq local-abbrev-table my/list-abbrev-table)
(abbrev-mode 1) ;; 也可以不启用,手动使用 expand-abbrev 主动触发

abbrev table 的编辑

Emacs 中并没有定义类似的 inert 或者 delete 这样的函数来修改某一项的映射关系,如果要修改某一项或者插入,都是通过 define-abbrev 来进行。删除操作我们只需要将对应项设置为 nil,而修改只需要对某项重新赋值即可。插入的话新定义一项原来没有的就可以了。例如对于lisp mode,我们加入快速定义交互式函数的项

(define-abbrev my/list-abbrev-table
  "dfi" "(defun()\n(interactive))")

如果想要一次性清空可以使用 clear-abbrev-table

Emacs还提供了一个交互式的修改方案,可以使用 edit-abbrevs 命令。该命令会打开一个新的窗口显示当前启用的 abbrev 项,我们可以直接进行修改,最后执行 C-c C-c 保存。

持久化保存

对于 abbrev-table 数据来说,我们并不会经常改动,可以说它适合作为外部的配置文件。Emacs当然有提供的手段。缩写默认定义在 ~/.emacs.d/abbrev_defs 中,我们可以修改 abbrev-file-name 变量来修改默认保存的位置。因此我们可以采用更合理的方式,例如:

  • 将 C/C++ 语言的数据保存在 ~/.emacs.d/abbrevs/c
  • 将 elisp 语言的数据保存在 ~/.emacs.d/abbrevs/elisp
  • 将 python 语言的数据保存在 ~/.emacs.d/abbrevs/py

对于加载过来的数据,我们对于不合理的项可以使用上面提到的 define-abbrevedit-abbrevs 进行编辑和修改,虽然在 edit-abbrevs 模式下,执行 C-c C-c 会默认执行 save-abbrevs 将内容保存到 abbrev-file-name 所指定的文件中,但是我们同样可以在关闭Emacs之前可以使用 write-abbrev-file 将当前buffer中的 abbrev 写入到指定文件保存。例如在scratch buffer中,我们使用 (write-abbrev-file (expand-file-name "~/.emacs.d/abbrevs/elisp")) 进行保存。

对于保存的内容,我们可以通过 (quietly-read-abbrev-file &optional filename) 来读取。

练习

今天的练习我们就涉及Major Mode 了,我们根据上文使用 write-abbrev-file 保存的文件。编写一段加载指定 abbrev-file 并启用的代码

(defun my/load-abbrev-file (filename)
  (interactive "fAbbrev File: ")
  (if (file-exists-p filename)
      (progn
    (quietly-read-abbrev-file filename)
    (setq local-abbrev-table my/list-abbrev-table)
    (abbrev-mode t))))

上述代码非常简单。如果我们查看保存的内容会发现,它将我们通过 define-abbrev-table 的整个表达式都拷贝到对应的文件中,所以使用 quietly-read-abbrev-file 读取并执行代码之后会生成一个对应名称的变量,后面我只需要设置当前的 local-abbrev-table 为当前变量即可。

总结

本篇我们入门了 abbrev-table 的基础概念以及基本的操作API,本节涉及到的函数有:

  • define-abbrev-table
  • define-abbrev
  • write-abbrev-file
  • quietly-read-abbrev-file
0

评论 (0)

取消