利用lazy-load.el按需加载Emacs插件
Emacs
字数2093 2019-05-05

随着Emacs的插件越来越多,Emacs的启动速度会越来越幔。

Emacs的启动速度慢,主要有两个原因:

  1. 加载时垃圾回收的阈值太小了,导致启动加载插件时触发了垃圾回收,减慢了启动速度
  2. 默认Emacs加载了过多的插件,导致启动的时候浪费了过多的时间去加载插件

解决第一个问题的方法如下:

(let (;; 加载的时候临时增大`gc-cons-threshold'以加速启动速度。
      (gc-cons-threshold most-positive-fixnum)
      ;; 清空避免加载远程文件的时候分析文件。
      (file-name-handler-alist nil))

    ;; Emacs配置文件内容写到下面.

)

上面的代码的目的时,在Emacs加载任何插件之前临时把 gc-cons-threshold 的值设置为最大,避免Emacs启动时触发垃圾回收。 Emacs配置文件加载完毕后,自动恢复变量 gc-cons-threshold 为默认值,避免运行时Emacs占用过多内存。

至于第二个问题的解决方案,就要用到今天我编写的 lazy-load.el 插件了。

2007 ~ 2008这两年期间,我几乎把当时所有的Emacs插件都玩过一遍,当你把几百个插件一股脑的全部默认加载,Emacs的启动时间可以从秒开延长到几分钟。 那时候就在思考怎么把Emacs的启动时间优化到秒开,同时不减少任何插件的使用,最终开发了 lazy-load.el 插件,即使我用 300+ 插件,Emacs启动速度依然是秒开。

原理

lazy-load.el 的原理很简单:

  1. 我们先把我们的 keymap 定义好,每个按键对应的函数都写好
  2. 告诉Emacs函数对应插件的文件名,这样当调用函数时,Emacs知道去哪里加载插件
  3. Emacs默认只加载非常少的插件, 加载完成后,当我们第一次触发按键时才让Emacs去动态按需加载插件和按键对应的函数

因为99%的Emacs插件在单独加载的时候,都可以在1s之内完成,所以相当于99%的插件都在运行时按需加载, 这样就大大减少了Emacs启动时需要加载的插件数量,从而最终达到提升Emacs启动时间的目的。

安装方法

  1. 下载 lazy-load 里面的 lazy-load.el 放到 ~/elisp 目录
  2. 把下面的配置加入到 ~/.emacs 中
(add-to-list 'load-path (expand-file-name "~/elisp"))
(require 'lazy-load)

使用方法

下面这段代码的意思是,第一次按 Alt + g 时,Emacs在 load-path 目录下去找 goto-line-preview.el 这个文件,加载插件并执行 goto-line-preview 这个函数。

(lazy-load-global-keys
 '(("M-g" . goto-line-preview))
 "goto-line-preview")

下面这段代码的意思是,第一次按在 ruby mode 中按 Ctrl + c t 时,Emacs在 load-path 目录下去找 ruby-extension.el 这个文件,加载插件并执行 ruby-hash-syntax-toggle 这个函数。

(lazy-load-local-keys
 '(("C-c t" . ruby-hash-syntax-toggle))
 ruby-mode-map
 "ruby-extension")

很多全局按键默认已经被Emacs占用了,必须先卸载以后才能重新绑定这些全局按键,比如 Ctrl + x, 下面这段代码就是用 lazy-load-unset-keys 卸载默认绑定的全局按键:

(lazy-load-unset-keys '("C-x C-f" "C-z" "C-q" "s-W" "s-z" "M-h" "C-x C-c" "C-\\" "s-c" "s-x" "s-v"))

高级用法

有时候,我们会用一个前缀按键取分类插件中不同的函数,比如我的 sdcv.el 插件的不同函数就可以按照下面的代码用 Ctrl + z 这个按键作为前缀按键, 先按 Ctrl + z ,再按 p 就可以触发 sdcv-search-pointer 函数

(lazy-load-global-keys
 '(("p" . sdcv-search-pointer)
   ("y" . sdcv-search-pointer+)
   ("i" . sdcv-search-input)
   (";" . sdcv-search-input+))
 "init-sdcv"
 "C-z")

对应的 lazy-load-local-keys 也支持最后一个参数传递前缀按键,只不过 lazy-load-local-keys 对应的不是 global-map ,而是插件的 keymap 。

如果Emacs默认就加载了某个插件,而不需要在运行时动态加载,也可以使用 lazy-load-set-keys 函数做单独的按键绑定操作,不用手动写一行行的重复写类似 (define-key keymap key) 的配置

(lazy-load-set-keys
 '(("M-;" . comment-dwim-with-haskell-style))
 haskell-mode-map)

示例

这里有很多 lazy-load 的示例用法