Emacs use-package 中文文档

  • 目标:use-package 是一个很好的 Emacs 配置隔离工具,符合规则书写配置可实现延迟加载,提升启动速度。对于我这样的 lisp 小白来说,是福音。
  • 翻译源:https://github.com/jwiegley/use-package
  • 更新方式:不定期更新
  • 更新时间:2020-04-23 16:37

use-package 宏允许你以性能导向、优雅、整洁的方式隔离你的 .emacs 配置文件。我创建它是因为我在 Emacs 中使用了 80 多个 package,管理起来非常困难。 有了 use-package 之后,我的总加载时间约为 2 秒,而没有功能上的损失。

注意: use-package 不是 一个包管理工具。尽管 use-package 的确有与包管理工具交互的(很有用)功能,但它的主要目的是配置和加载程序包。

升级到 2.x 的用户注意事项在底部。

  • 安装 use-package

从 [[https://github.com/jwiegley/use-package][这个]] Github 仓库中 clone 或者从 [[https://melpa.org/][melpa]] 安装(推荐)。

  • 入门

use-package 最简单的声明方式:

  1. ;; This is only needed once, near the top of the file
  2. (eval-when-compile
  3. ;; Following line is not needed if use-package.el is in ~/.emacs.d
  4. (add-to-list 'load-path "<path where use-package is installed>")
  5. (require 'use-package))
  6. (use-package foo)

这会加载 foo 包,但是只有 foo 在你的系统中可用的时候。如果没有的话,会打印警告到 *Message* buffer。

使用 :init 关键字在包加载之前执行代码。它接受一个或者多个形式(forms),直到下一个关键字出现:

  1. (use-package foo
  2. :init
  3. (setq foo-variable t))

类似, :config 可以在包加载之后执行代码。如果是延迟加载的(请参阅下面的有关自动加载的更多信息),config 中的代码会延迟到自动加载发生之后:

  1. (use-package foo
  2. :init
  3. (setq foo-variable t)
  4. :config
  5. (foo-mode 1))

和你想的一样,你可以使用 :init:config 一起使用。

  1. (use-package color-moccur
  2. :commands (isearch-moccur isearch-all)
  3. :bind (("M-s O" . moccur)
  4. :map isearch-mode-map
  5. ("M-o" . isearch-moccur)
  6. ("M-O" . isearch-moccur-all))
  7. :init
  8. (setq isearch-lazy-highlight t)
  9. :config
  10. (use-package moccur-edit))

在这种情况下,我想从 color-moccur.el 中自动加载 isearch-moccurisearch-all 命令,然后进行全局级别和 isearch-mode-map 内部绑定(查看下一小结)。 当实际加载包时(使用了这些命令之一), moccur-edit 也会被加载,以允许编辑 moccur buffer。

  • 键绑定

加载模块通常要做的另外一件事是给模块中主要的命令绑定快捷键:

  1. (use-package ace-jump-mode
  2. :bind ("C-." . ace-jump-mode))

它做了两件事情:首先,它为 ace-jump-mode 命令创建了自动加载(直到你使用它才会被加载 ace-jump-mode );第二,它绑定了 C-.ace-jump-mode 命令上。 加载之后,你可以使用 M-x describe-personal-keybindings 来查看你在整个 .emacs 中绑定的快捷键。

实现相同效果的另外一种(更精确)方法:

  1. (use-package ace-jump-mode
  2. :commands ace-jump-mode
  3. :init
  4. (bind-key "C-." 'ace-jump-mode))

当你使用 :commands 关键字时,它会给这些命令创建自动加载,并延迟模块加载直到使用它们为止。因为 :init 总是会运行 — 即便 ace-jump-mode 可能不在你的系统上 — 切记 :init 中的代码要保证都可以运行成功。

:bind 关键字可以接受一个元素,也可以接受一个列表:

  1. (use-package hi-lock
  2. :bind (("M-o l" . highlight-lines-matching-regexp)
  3. ("M-o r" . highlight-regexp)
  4. ("M-o w" . highlight-phrase)))

:commands 通常也支持 symbols 和 symbols 列表。

注意: 命令内部的字符串,特殊的键比如 tab 或者 F1-Fn 必须要写在尖括号里面,比如说: "C-<up>" 。独立的特殊键(和某些组合)可以写在方括号里中, 比如: 用 [tab] 代替 <tab> 。键盘的绑定语法类似于 “kbd” 语法,从 https://www.gnu.org/software/emacs/manual/html_node/emacs/Init-Rebinding.html 获取更多信息。

举例:

  1. (use-package helm
  2. :bind (("M-x" . helm-M-x)
  3. ("M-<f5>" . helm-find-files)
  4. ([f10] . helm-buffers-list)
  5. ([S-f10] . helm-recentf)))

此外,使用 :bindbind-key 重新映射命令可以按照预期工作,因为绑定是向量(vector)时,它直接传递给 define-key 。 所以下面的例子将重新绑定 M-q (原先是 fill-paragraph )到 unfill-toggle

  1. (use-package unfill
  2. :bind ([remap fill-paragraph] . unfill-toggle))

** 绑定到键映射(keymaps)

通常, :bind 期望命令是将从给定的包中自动加载的函数。然而,有些命令实际上是键盘映射,直接操作不起作用,因为键盘映射不是一个函数,而且无法使用 Emacs 的 autoload 机制。

为了处理这种情况, use-package 提供了一个 :bind 特殊的变体叫做 :bind-keymap 。唯一的区别是由 :bind-keymap 包括的「命令」必须是包中定义的键映射, 而不是命令函数。它的处理方式是生成自定义代码来加载包含键盘映射的包,然后在你执行按键的时候首次加载,进而将键重新解释为前缀键。

比如:

  1. (use-package projectile
  2. :bind-keymap
  3. ("C-c p" . projectile-command-map))

** 在本地键映射中绑定

与绑定到键映射不同,它绑定本地键映射的中的一个键,而且只有在包已经被加载之后才存在。 use-package 通过 :map 修饰符来支持它,将本地的键映射绑定到:

  1. (use-package helm
  2. :bind (:map helm-command-map
  3. ("C-c h" . helm-execute-persistent-action)))

该语句会等到 helm 被加载的时候,然后绑定 C-c h 到 Helm 的本地键映射中的 helm-execute-persistent-actionhelm-mode-map

:map 可以使用多次,在第一次使用 :map 之前的任何绑定都会认为是全局绑定。

  1. (use-package term
  2. :bind (("C-c t" . term)
  3. :map term-mode-map
  4. ("M-p" . term-send-up)
  5. ("M-n" . term-send-down)
  6. :map term-raw-map
  7. ("M-o" . other-window)
  8. ("M-p" . term-send-up)
  9. ("M-n" . term-send-down)))
  • Modes 和 interpreters

类似 :bind ,你可以使用 :mode:interpreter 建立与 auto-mode-alistinterpreter-mode-alist 中的变量内部延迟绑定。 每个关键字的说明符都是可以元素,列表,字符串或者正则表达式。

  1. (use-package ruby-mode
  2. :mode "\\.rb\\'"
  3. :interpreter "ruby")
  4. ;; The package is "python" but the mode is "python-mode":
  5. (use-package python
  6. :mode ("\\.py\\'" . python-mode)
  7. :interpreter ("python" . python-mode))

如果你没有设置 :commands, :bind, :bind*, :bind-keymap, :bind-keymap*, :mode, :interpreter 或者 :hook (这些都实现了 :defer ; 查看 use-package 的 docstring 获取每个文档的简要说明),你仍旧可以通过 :defer 关键字实现延迟加载:

  1. (use-package ace-jump-mode
  2. :defer t
  3. :init
  4. (autoload 'ace-jump-mode "ace-jump-mode" nil t)
  5. (bind-key "C-." 'ace-jump-mode))

与下面这样效果完全相同:

  1. (use-package ace-jump-mode
  2. :bind ("C-." . ace-jump-mode))
  • Magic Handlers

:mode:interpreter 类似,你也可以使用 :magic:magic-fallback 来实现如果文件的开头和给定的正则表达式匹配,则引发某些功能运行。 两者之间的区别在于 :magic-fallback:mode 的优先级低。比如:

  1. (use-package pdf-tools
  2. :load-path "site-lisp/pdf-tools/lisp"
  3. :magic ("%PDF" . pdf-view-mode)
  4. :config
  5. (pdf-tools-install :no-query))

这会为 pdf-view-mode 注册一个自动加载命令,延迟加载 pdf-tools 。如果 buffer 开头与字符串 "%PDF" 匹配,运行 pdf-view-mode

  • Hooks

:hook 关键字允许将函数添加到包 hooks 上。因此,下面三种方式都是等价的:

  1. (use-package ace-jump-mode
  2. :hook prog-mode)
  3. (use-package ace-jump-mode
  4. :hook (prog-mode . ace-jump-mode))
  5. (use-package ace-jump-mode
  6. :commands ace-jump-mode
  7. :init
  8. (add-hook 'prog-mode-hook #'ace-jump-mode))

同样,应用到多个 hook 时,以下的内容也是等价的:

  1. (use-package ace-jump-mode
  2. :hook (prog-mode text-mode))
  3. (use-package ace-jump-mode
  4. :hook ((prog-mode text-mode) . ace-jump-mode))
  5. (use-package ace-jump-mode
  6. :hook ((prog-mode . ace-jump-mode)
  7. (text-mode . ace-jump-mode)))
  8. (use-package ace-jump-mode
  9. :commands ace-jump-mode
  10. :init
  11. (add-hook 'prog-mode-hook #'ace-jump-mode)
  12. (add-hook 'text-mode-hook #'ace-jump-mode))

当使用 :hook 时要忽略 “-hook” 后缀,默认情况会自动添加上。比如下面的代码不会生效,因为它实际上会扩展成 prog-mode-hook-hook 并不存在:

  1. ;; DOES NOT WORK
  2. (use-package ace-jump-mode
  3. :hook (prog-mode-hook . ace-jump-mode))

如果你不喜欢这种行为的话,设置 use-package-hook-name-suffix 成 nil。默认情况下,它的值是 “-hook”。

使用 :hook:bind, :mode, :interpreter 等等。被 hook 的隐式读取为 :commands (意味着它们将为该模块建立交互式 autoload 定义, 如果尚未定义函数的话),因为 :defer t 也被 :hook 隐含了。

  • 包定制化

** 自定义变量

:custom 关键字允许包自定义变量。

  1. (use-package comint
  2. :custom
  3. (comint-buffer-maximum-size 20000 "Increase comint buffer size.")
  4. (comint-prompt-read-only t "Make the prompt read only."))

其中的文档字符串不是必需的。

注意: 这些适用于想要在 use-packages 声明的地方自定义包的人。功能上,相比与在 :config 块中使用 setq 的优势在于:在分配值是,使用 custom 可能会执行代码。 如果你使用 M-x customize-option 并保存在设置文件中,你可能不希望使用这个选项。

** 自定义 faces

custom-face 关键字允许自定义包的 faces。

  1. (use-package eruby-mode
  2. :custom-face
  3. (eruby-standard-face ((t (:slant italic)))))
  • 延迟加载注意事项

绝大多数情况下,你都不需要手动设置 :defer t 。当使用 :bind :mode 或者 :interpreter 都已经隐含了。通常,只有知道一些包会在某些情况下需要时间加载, 你才需要指定 :defer ,因此,即使 use-package 没有为你创建任何自动加载,你也可以设置延迟加载。

你可以使用 :demand 关键字覆盖包的延迟,这种情况下,即便你使用了 :bind:demand 也会立即强制加载,并且不会为绑定键建立自动加载。

  • 有关包加载的信息

加载包的时候,如果将 use-package-verbose 设置为 t, 或者加载包的时间超过了 0.1 秒,那么你会在 *Message* buffer 中看到一个信息(来表明加载活动)。 对于配置或者 :config 块执行时间超过 0.1 秒,也会出现相同的情况。通常情况,你应该保持 :init 尽可能的简单,而把更多的配置放到 :config 块中。 这样,延迟加载可以让你的 Emacs 启动更快。

另外,如果在包的初始化或者配置过程中出现了错误,不会阻止你的 Emacs 加载。相反,错误会被包捕获,然后报告给特殊的 *Warning* 弹出 buffer,以便你使用 功能正常的 Emacs 中调试。

  • 条件加载

你可以使用 :if 关键字来条件判断包模块的加载和初始化。

比如说,我只希望 edit-server 在我主要的,GUI Emacs,而不希望其它时候启动:

  1. (use-package edit-server
  2. :if window-system
  3. :init
  4. (add-hook 'after-init-hook 'server-start t)
  5. (add-hook 'after-init-hook 'edit-server-start t))

在另外一个例子中,我们可以有条件的加载一些东西:

  1. (use-package exec-path-from-shell
  2. :if (memq window-system '(mac ns))
  3. :ensure t
  4. :config
  5. (exec-path-from-shell-initialize))

:disabled 关键字可以关闭遇到问题的模块,或者对当前不使用的模块阻止加载。

  1. (use-package ess-site
  2. :disabled
  3. :commands R)

当字节编译( byte-compiling )你的 .emacs 文件,在声明中禁用(disabled)将从输出中完全省略,可以加快启动时间。

注意: :when:if 的别名,而且 :unless foo 等价于 :if (not foo) 。比如说,下面的 :ensure 也会在 Mac 系统中停止工作:

  1. (when (memq window-system '(mac ns))
  2. (use-package exec-path-from-shell
  3. :ensure t
  4. :config
  5. (exec-path-from-shell-initialize)))

** :preface 之前的条件加载

如果你需要对 use-package form 进行条件判断,确保它出现在 :preface 执行之前,简单的使用 :when form 即可。

** 按顺序(in sequence)加载包

有时需要在一个包加载完之后才会加载另外一个包,因为那个时候变量或者函数才在范围内。这种情况可以使用 :after 关键字来实现,它允许在满足确定条件之后然后再加载。 比如:

  1. (use-package hydra
  2. :load-path "site-lisp/hydra")
  3. (use-package ivy
  4. :load-path "site-lisp/swiper")
  5. (use-package ivy-hydra
  6. :after (ivy hydra))

这种情况下,所有的软件包都是按找出现的顺序加载的,因此 :after 的使用不是强制必要的。 但是,使用了之后,上面的代码变的与顺序无关了,而对你 init 文件本身没有显式的依赖。

默认情况下, :after (foo bar):after (:all foo bar) 相同,意味着给定的加载包必须要等到 foobar 全部加载之后。这是其它的可能情况:

  1. :after (foo bar)
  2. :after (:all foo bar)
  3. :after (:any foo bar)
  4. :after (:all (:any foo bar) (:any baz quux))
  5. :after (:any (:all foo bar) (:all baz quux))

当你使用嵌套的选择器时,比如 (:any (:all foo bar) (:all baz quux)) ,意味着同时加载 foobar 或者 bazquux

注意: 如果你设置了 use-package-always-defert ,并且使用了 :after 关键字,那么你需要指定包的加载方式:比如,使用 :bind 。 如果你没有使用注册自动加载的机制之一,比如 :bind 或者 :hook ,而且您的包管理器没有提供自动加载功能,如果没有添加 :demand t 到这些声明中, 那么这些包可能永远不会被加载。

** 如果依赖项缺失,则阻止加载

虽然 after 关键字可以延迟加载直到依赖项加载,如果 use-package 声明的包不可用永远不要加载依赖包使用 :requires 会更简单一些。 在这种情况下,foo 的「可用」表示的是 (featurep 'foo) 的值是非 nil 值。比如:

  1. (use-package abbrev
  2. :requires foo)

等价于:

  1. (use-package abbrev
  2. :if (featurep 'foo))

你可以指定依赖列表:

  1. (use-package abbrev
  2. :requires (foo bar baz))

对于更复杂的逻辑,比如说 :after 支持的,只需要使用 :if 合适的 Lisp 表达式即可。

  • 字节编译(Byte-compiling)你的 .emacs

use-package 的另一个特性是 .emacs 被字节编译时总加载它可以加载的每一个文件。这会有助于消除有关未知变量和函数的虚假警告。

但是,有时候这样还不够。有些时候,出于字节编译的目的,使用 :defines:functions 关键字仅用来引入虚拟变量和函数声明。

  1. (use-package texinfo
  2. :defines texinfo-section-list
  3. :commands texinfo-mode
  4. :init
  5. (add-to-list 'auto-mode-alist '("\\.texi$" . texinfo-mode)))

如果你想要函数缺失的警告静音(silence),你可使用 :functions

  1. (use-package ruby-mode
  2. :mode "\\.rb\\'"
  3. :interpreter "ruby"
  4. :functions inf-ruby-keys
  5. :config
  6. (defun my-ruby-mode-hook ()
  7. (require 'inf-ruby)
  8. (inf-ruby-keys))
  9. (add-hook 'ruby-mode-hook 'my-ruby-mode-hook))

** 组织包在编译时(compile-time)加载

通常, use-package 在编译配置之前,在编译时加载每个包,确保必要的标识符在范围内,以满足字节编译器的要求。有时这样会引起问题,由于包可能有特殊的加载需求, 你要使用 use-pacakge 的所有操作就是将配置添加到 eval-after-load hook。这种情况下,使用 :no-require 关键字:

  1. (use-package foo
  2. :no-require t
  3. :config
  4. (message "This is evaluated when `foo' is loaded"))
  • 扩展 load-path

如果你想将目录添加到 load-path 进行加载,使用 :load-path 。参数是一个符号,一个函数,一个字符串或者一个字符串列表。如果是相对路径, 会在 user-package-directory 中扩展:

  1. (use-package ess-site
  2. :load-path "site-lisp/ess/lisp/"
  3. :commands R)

注意: 当使用符号或者函数提供动态的路径列表时,你必须要将次定义告知字节编译器,以便在字节编译时可用。这是通过使用特殊形式的 eval-and-compile ( 与 eval-when-compile 相反)来完成的。进一步,这个值是在编译器期间确定下来的,为了避免在每次启动时再次查找相同的信息:

  1. (eval-and-compile
  2. (defun ess-site-load-path ()
  3. (shell-command "find ~ -path ess/lisp")))
  4. (use-package ess-site
  5. :load-path (lambda () (list (ess-site-load-path)))
  6. :commands R)
  • 在包扩展中捕获错误

默认情况下,如果 use-package-expand-minimally 是 nil(默认值),use-package 将尝试捕捉并报告在你的 init 文件中扩展使用包声明期间发生的错误。 设置 use-package-expand-minimallyt 完全禁用此检查。

可以使用 :catch 关键字来本地覆盖此行为,设置为 t 或者 nil ,在加载时启动或者禁用捕获错误。它也可以是带有两个参数的函数:遇到错误时正在处理的关键字, 和错误对象(由 condition-case 生成)。比如:

  1. (use-package example
  2. ;; Note that errors are never trapped in the preface, since doing so would
  3. ;; hide definitions from the byte-compiler.
  4. :preface (message "I'm here at byte-compile and load time.")
  5. :init (message "I'm always here at startup")
  6. :config
  7. (message "I'm always here after the package is loaded")
  8. (error "oops")
  9. ;; Don't try to (require 'example), this is just an example!
  10. :no-require t
  11. :catch (lambda (keyword err)
  12. (message (error-message-string err))))

执行结果:

  1. Im here at byte-compile and load time.
  2. Im always here at startup
  3. Configuring package example...
  4. Im always here after the package is loaded
  5. oops
  • diminishing 和 delighting 次要模式(minor modes)

use-package 还提供了内置的 diminish 和 delight 功能支持 — 如果你已经安装了它们的话。它们的目标是在你的 mode-line 中删除或者更改次要模式的字符串。

[[https://github.com/myrjola/diminish.el][diminish]] 用 :diminish 关键字调用,可以使用次要模式标识符传递,符号和它替换字符串的 cons,或者只是一个替换字符串,这种情况下,次要模式的标识符被猜测为 包名字加上 “-mode” :

  1. (use-package abbrev
  2. :diminish abbrev-mode
  3. :config
  4. (if (file-exists-p abbrev-file-name)
  5. (quietly-read-abbrev-file)))

[[https://elpa.gnu.org/packages/delight.html][delight]] 用 :delight 关键字调用,传递了次要模式标识符,替换字符串或者引号包裹的 [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Mode-Line-Data.html][mode-line 数据]](这种情况下,次要模式符号被认为是包名称末尾加上 “-mode”), 这两个,或者两者的几个列表。如果没有提供任何参数,默认模式名字将完全被隐藏。

  1. ;; Don't show anything for rainbow-mode.
  2. (use-package rainbow-mode
  3. :delight)
  4. ;; Don't show anything for auto-revert-mode, which doesn't match
  5. ;; its package name.
  6. (use-package autorevert
  7. :delight auto-revert-mode)
  8. ;; Remove the mode name for projectile-mode, but show the project name.
  9. (use-package projectile
  10. :delight '(:eval (concat " " (projectile-project-name))))
  11. ;; Completely hide visual-line-mode and change auto-fill-mode to " AF".
  12. (use-package emacs
  13. :delight
  14. (auto-fill-function " AF")
  15. (visual-line-mode))
  • 包安装

你可以使用 use-package 通过 package.el 从 ELPA 加载包。如果你在多台计算机之间共享 .emacs ,这会非常有用;在你的 .emacs 中声明之后,相关的包会自动下载。 :ensure 关键字会让包自动安装(如果系统上不存在的话)。

  1. (use-package magit
  2. :ensure t)

如果你需要安装与 use-package 命名的包不同的包,可以这样指定:

  1. (use-package tex
  2. :ensure auctex)

如果你希望此行为对所有的包都有效,打开 use-package-always-ensure

  1. (require 'use-package-ensure)
  2. (setq use-package-always-ensure t)

注意: :ensure 在包不存在时会自动安装包,但它并不能保持最新。如果你希望你的包自动升级,一个选择是使用 [[https://github.com/rranelli/auto-package-update.el][auto-package-update]] ,类似:

  1. (use-package auto-package-update
  2. :config
  3. (setq auto-package-update-delete-old-versions t)
  4. (setq auto-package-update-hide-results t)
  5. (auto-package-update-maybe))

最后,在 Emacs 24.4 或者更高的版本上运行时,use-package 可以将包固定到特定的 archive,允许你混用和匹配来自不同 archive 的包。大部分场景都是从 melpa-stablegnu archive 中选择软件包,但是要使用来自 melpa 中的包,当你需要使用比 stable archives 可用版本更新的版本时,这也是一种使用场景。

默认情况下 package.el 由于版本控制的原因 (> evil-20141208.623 evil-1.0.9) 相比 melpa-stable 更喜欢 mepla ,因此即使你想只有一个包来自 melpa , 你需要把所有非 melpa 的包都要从这个 archive 下载。如果这个让你感到烦恼,你可以设置 use-package-always-pin 设置为默认值。

如果要手动保持包更新,而忽略上游更新,你可以将它固定为 manual ,只要没有该名称的存储库,就可以使用。

如果你固定的 archive 没有出现在 package-archives 的配置列表中, use-package 会抛出一个错误(除了上面提到的 :manual ):

  1. Archive 'foo' requested for package 'bar' is not available.

举例:

  1. (use-package company
  2. :ensure t
  3. :pin melpa-stable)
  4. (use-package evil
  5. :ensure t)
  6. ;; no :pin needed, as package.el will choose the version in melpa
  7. (use-package adaptive-wrap
  8. :ensure t
  9. ;; as this package is available only in the gnu archive, this is
  10. ;; technically not needed, but it helps to highlight where it
  11. ;; comes from
  12. :pin gnu)
  13. (use-package org
  14. :ensure t
  15. ;; ignore org-mode from upstream and use a manually installed version
  16. :pin manual)

注意: :pin 参数对于 emacs 版本小于 24.4 无效。

** 和其它包管理器一起使用

通过覆盖 use-package-ensure-function 和/或者 use-package-pre-ensure-function ,其它的包管理器可以重写 :ensure 使用它们而不是 package.el 。 目前,唯一执行此操作的包管理器是 [[https://github.com/raxod502/straight.el][straight.el]]。

  • 收集统计

如果你想查看已经加载包数量,它们到达了什么初始化阶段,它们花了多少时间(大约),在加载 use-package 之后打开 use-package-compute-statistics (在 任何使用 use-package forms 之前),然后运行命令 M-x use-package-report 查看结果。显示的 buffer 是一个列表,你可以在对应的列上使用 S 进行排序。

  • TODO 关键字扩展

从 2.0 版本开始, use-package 基于可扩展的框架,使得包的作者添加新的关键字或者修改现有关键字变的很轻松。

现在,某些关键字扩展包含在 use-package 的发行版中,可以选择性安装。

** (use-package-ensure-system-package)

:ensure-system-package 允许你确保系统的二进制文件和你的包声明一起存在。

首先,你希望保证 exec-path 能够识别你已经安装的二进制包的名称,[[https://github.com/purcell/exec-path-from-shell][exec-path-from-shell]] 是一个好的解决办法。

在你 use-package 加载之后启用扩展:

  1. (use-package use-package-ensure-system-package
  2. :ensure t)

下面是使用范例:

  1. (use-package rg
  2. :ensure-system-package rg)

它会期望全局的二进制 rg 存在。如果不存在的话,它会使用你的系统包管理器(使用 system-packages)尝试异步安装同名的二进制。比如,macOS 用户可能会调用: brew install rg

如果包名字和二进制名字不同,你可以用 (binary . package-name) 的格式,即:

  1. (use-package rg
  2. :ensure-system-package
  3. (rg . ripgrep))

在前面的 macOS 例子中,如果 rg 没找到的话,会调用: brew install ripgrep

如果你想自定义安装命令怎么办?

  1. (use-package tern
  2. :ensure-system-package (tern . "npm i -g tern"))

:ensure-system-package 也可以通过 (async-shell-command) 调用安装。

你也可以安装传入一个列表:

  1. (use-package ruby-mode
  2. :ensure-system-package
  3. ((rubocop . "gem install rubocop")
  4. (ruby-lint . "gem install ruby-lint")
  5. (ripper-tags . "gem install ripper-tags")
  6. (pry . "gem install pry")))

最后,如果包依赖项不提供全局可执行文件,你可以通过类似以下的字符串来检查文件路径的存在确保包存在:

  1. (use-package dash-at-point
  2. :if (eq system-type 'darwin)
  3. :ensure-system-package
  4. ("/Applications/Dash.app" . "brew cask install dash"))

:ensure-system-package 会使用 system-package-install 来安装系统包,如果指定了自定义命令,将由 async-shell-command 执行。

配置变量 system-packages-package-managersystem-packages-use-sudo 会很有用,但是不适用于自定义命令。自定义命令如果需要的话,需要在命令本身上加上 sudo

** (use-package-chords)

:chords 关键字允许你为 use-paackage 定义 key-chord 绑定,与 :bind 关键字类似。

打开扩展:

  1. (use-package use-package-chords
  2. :ensure t
  3. :config (key-chord-mode 1))

然后你可以使用与 :bind 类似的定义方法来绑定:

  1. (use-package ace-jump-mode
  2. :chords (("jj" . ace-jump-char-mode)
  3. ("jk" . ace-jump-word-mode)
  4. ("jl" . ace-jump-line-mode)))

* TODO 如何创建扩展

/这部分我不怎么关心…/

  • 一些计时结果

在我的 iMac Retina 上,Emacs 24.4 的 “Mac port” 变种,大约配置了 218 个包(几乎所有的都是懒加载),加载了 0.57 秒。但是,除了第一次使用 Emacs 时(由于自动加载) ,我没有损失任何功能。由于我对许多软件包使用了空闲加载,因此通常可以整体上减少延迟感知。

在 Linux 上,相同的配置用了 0.32 秒。

如果不用图形方式使用 Emacs,可以测试到绝对最小时间,通过以下命令完成:

  1. time emacs -l init.elc -batch --eval '(message "Hello, world!")'

在 Mac 上平均 0.36 秒,在 Linux 上 0.26 秒。

  • 升级到 2.x

** :init 语义现在是一致的

:init 的含义发生了变化:现在它 /总是/ 的包加载之前出现,无论 :config 推迟与否。这意味着可能需要将 :init 中的配置放到了 :config 中(在非延迟的情况下)。 对于延迟的情况,行为和之前相同。

也因为 :init:config 现在意味着 “之前” 和 “之后”,所以 :pre-:post- 关键字消失了,它们也不再被需要了。

最后,即使存在包配置失败的情况,也尽力让你的 Emacs 启动。因此,在此更改之后,要检查你的 *Message buffer。最有可能的是,在几个例子中使用 :init ,但应该 跟更多地方使用 :config

** :idle 被移除了

我现在要删除此功能,是因为它可能出现让人生厌的不一致,考虑以下定义:

  1. (use-package vkill
  2. :commands vkill
  3. :idle (some-important-configuration-here)
  4. :bind ("C-x L" . vkill-and-helm-occur)
  5. :init
  6. (defun vkill-and-helm-occur ()
  7. (interactive)
  8. (vkill)
  9. (call-interactively #'helm-occur))
  10. :config
  11. (setq vkill-show-all-processes t))

如果我加载我的 Eamcs 然后等到空闲计数器触发,然后这是事件的顺序:

  1. :init :idle <load> :config

但是,如果我加载 Emacs 并立即输入 C-x L 而不等待空闲计数器触发,它的事件顺序时这样的:

  1. :init <load> :config :idle

用户可能在闲置状态下使用 featurep 来测试这种情况,但这是我想避免的。

** :defer 现在一个可选的数字参数

:defer [N] 会导致包加载 — 如果还没有的话 — 在 N 秒之后执行。

  1. (use-package back-button
  2. :commands (back-button-mode)
  3. :defer 2
  4. :init
  5. (setq back-button-show-toolbar-buttons nil)
  6. :config
  7. (back-button-mode 1))

TODO Add :preface, occurring before everything except :disabled TODO Add :functions, for declaring functions to the byte-compiler ** use-package.el 在运行时不在需要

也就是说你可以将以下内容放在 Emacs 的顶部,进一步减少加载时间:

  1. (eval-when-compile
  2. (require 'use-package))
  3. (require 'diminish) ;; if you use :diminish
  4. (require 'bind-key) ;; if you use any :bind variant