对于 Emacs 用户来说,优化自己的配置是件乐趣无穷的事情,而且也是成为 Emacs 高手的必经之路。一般来说,新手的配置都是东拼西凑出来的,这是最快最有效的学习途径。随着对 Emacs 使用的加深,配置逐渐复杂,如果不对之前杂乱无章的配置进行重构,很难想象可以继续愉快地使用 Emacs。
这篇文章就来介绍一下我个人优化配置的心得,主要内容:包的加载原理与管理实践,希望对读者优化自己的配置有些帮助。
Package.el 问题
毫不夸张地说,高度的扩展性是 Emacs 延续几十年生生不息的主要原因, (length package-alist)
可以统计通过 package.el 安装的包数量,我是 137 个。
尽管 package.el 提供了一种便捷的方式来安装包,但它并不提供版本管理的功能,这是任何一个包管理器最基础的功能,我曾经多次因为包升级导致功能失效,这是十分让人沮丧的事情,参考这里。
社区有一些解决方案,比如 straight、borg ,但为了避免引入新问题,减轻学习负担,我目前没有采用这些方案,而是用 git 自带的 submodule 来管理一些重度使用的包(比如 lsp-mode/magit),闲暇时再专门去做升级工作,升级出问题直接回退到之前的 commit 即可,再也不用担心被工具打断的烦恼。
包加载原理
对于 package.el 管理的包,用户是无须了解 Emacs 加载包的方式就可以使用,但是如何要自己完全管理,就需要了解这些细节了。首先明确下包的定义:
Emacs 提供了两类高阶接口来进行包的自动加载:Autoload 与 Feature。
Autoload
Autoload 函数可以声明函数或宏,在真正使用时再去加载其对应的文件。
|
|
一般不直接使用 autoload 函数,而是 autoload 魔法注释,然后用一些函数来解析魔法注释自动生成 autoload 函数,比如在 my-mode 文件夹内有一文件 hello-world.el
,内容为:
|
|
使用下面的命令生成 autoloads 文件
|
|
在同一目录生成 hello-world-autoloads.el
文件,内容为:
|
|
这意味着只在第一次 M-x my-hello
时,才回去加载 hello-world.el
文件。
这里需要注意,为了让 Emacs 识别到 my-hello 函数的声明,需要去加载 hello-world-autoloads.el 文件,对于通过 package.el 管理的包,package.el 在下载该包时,会进行下面的操作:
- 解析依赖,递归下载
- 把包目录追加到 load-path 中
- 自动生成 autoloads 文件,并且加载它
这样用户就能够直接使用该包提供的函数了。如果使用 submodule 管理,上述操作则需要自己实现,后文会介绍。
Feature
Feature 是 Emacs 提供的另一种自动加载 ELisp 文件的机制,使用示例:
|
|
上述代码即生成了一个 feature,名为 hello-world,由于与文件同名,只需在使用 my-hello 前 (require 'hello-world)
即可,这样就会去自动加载 hello-world.el。
feature 的一个优势就是可以防止重复加载,所有被加载的 feature 会被记录在 features 变量中,只有在第一次 require 时才会去加载文件,后续的 require 则会直接返回。如果想要重复加载,需要先调用 unload-feature 进行卸载。
Load
|
|
上面的 autoload 与 feature 底层都是通过调用 load 函数去加载文件,但 load 为相对低级的 API,功能虽然比较丰富,但用起来不如上述两个高级 API 方便,因此一般 Emacs 用户不会直接使用它。
Submodule 管理包
在上面介绍 autoload 时,介绍了 package.el 下载一个包时的大致步骤,这里重新温故下:
- 解析依赖,递归下载
- 把包目录追加到 load-path 中
- 自动生成 autoloads 文件,并且加载它
使用 submodule 的话,只能下载包本身,上述三步都需要自己做,我目前使用 use-package 来下载、配置包,下面通过一个示例来介绍其用法:
|
|
可以看到,use-package 宏的使用非常简明扼要,而且把包的各种配置都统一起来了,强烈推荐。使用 macroexpand-1 展开 use-package,会发现和我们手动配置的代码相差无几:
|
|
use-package 解决了繁琐的配置问题,但并不能解决包依赖的问题,只能一个个下载(具体依赖见包的 Package-Requires
声明):
|
|
use-package 在 load-path 中找不到这些依赖时,会自动利用 package.el 去下载。我这里的做法是折中的,对于一些轻量的包,没必要用 submodule 管理。读者可能会觉得这种手动管理依赖的方式会比较繁琐,但是实际上不同包的依赖很有可能是相同的,比如:dash.el、s.el、f.el 等这些基础包,所以实际需要手动管理的依赖不是很多。
use-package bootstrap
|
|
常用 Git 命令
|
|
对于 submodule 的增加与删除,直接在 magit 中操作就好了, magit-status-mode
下按 o
即可。