UP | HOME

use-package manual

Table of Contents

1 概览

通过 use-package ,可以使 .emacs 中的 package 配置更加独立,既不影响性能,又整洁好看。

use-package 主要通过关键字(keyword)来配置 package,这些关键字涉及 package 的安装、初始化(initialize)、配置(config)、键绑定(key-binding)、自动加载(autoload)等。后面逐步介绍这些关键字。

2 基本使用

下面是最简单的 use-package 声明:

(use-package foo)

这会加载 foo package,但只有系统存在该 package 时才会加载成功。如果无该 package, *Message* buffer 会出现警告信息。

2.1 加载前准备

:init 关键字设置 package 加载前执行的代码,可用来设置影响 package 加载过程的变量。 即使配置的 package 不存在, :init 部分的代码也会执行,所以该部分代码应该保证 package 不存在也可以执行成功。

2.2 加载后配置

:config 关键字设置 package 加载后执行的代码,主要用来对 package 进行配置。在 package 自动加载(autoload)的情况下,这些代码直到加载时才会执行。

2.3 自动加载命令(autoload)

:command 关键字会为命令创建自动加载(autoload),他的参数是 symbol 或 symbol 列表。

2.4 钩子(hook)

:hook 关键字允许将函数添加到钩子上,这里只需要钩子的基本名称。因此,以下所有内容都是等同的:

(use-package ace-jump-mode
  :hook prog-mode)   ;然而经过测试这种写法并没有生效

(use-package ace-jump-mode
  :hook (prog-mode . ace-jump-mode))

(use-package ace-jump-mode
  :commands ace-jump-mode
  :init
  (add-hook 'prog-mode-hook #'ace-jump-mode))

同样,如果命令添加到多个钩子,以下内容也是等同的:

(use-package ace-jump-mode
  :hook (prog-mode text-mode)) ;没有生效

(use-package ace-jump-mode
  :hook ((prog-mode text-mode) . ace-jump-mode))

(use-package ace-jump-mode
  :hook ((prog-mode . ace-jump-mode)
         (text-mode . ace-jump-mode)))

(use-package ace-jump-mode
  :commands ace-jump-mode
  :init
  (add-hook 'prog-mode-hook #'ace-jump-mode)
  (add-hook 'text-mode-hook #'ace-jump-mode))

2.5 键绑定

:bind 关键字用来设置键绑定:

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

这会做两件事情:

  • 首先,为 ace-jump-mode 命令创建自动加载。
  • 其次,将 C-. 绑定到该命令。加载之后,可以使用 M-x describe-personal-keybindings 查看.emacs 文件中设置的所有键绑定。

:bind 作用基本等同于以下代码:

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

此外, :bind 的配置可以是 cons 或 cons 的列表:

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

注意:像 tab 或 F1-Fn 这样的特殊键要可以写在方括号中,如 [tab] 而不是"tab"。键绑定的语法与“kbd”类似:参见Init-Rebinding

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

2.6 禁用模块

:disabled 关键字可以关闭遇到困难的模块,或停止加载目前未使用的某个模块:

(use-package ess-site
  :disabled t
  :commands R)

字节编译.emacs 文件时,输出的字节码文件中会删除所有禁用的声明,以加快启动速度。

3 基本用法示例

(use-package color-moccur
  :commands (isearch-moccur isearch-all)
  :bind (("M-s O" . moccur)
         :map isearch-mode-map
         ("M-o" . isearch-moccur)
         ("M-O" . isearch-moccur-all))
  :init
  (setq isearch-lazy-highlight t)
  :config
  (use-package moccur-edit))

本例中,来自 color-moccur.el 的 isearch-moccur 和 isearch-all 会延迟加载(autoload),并在全局和 isearch-mode-map 中进行了键绑定。当该 package 真正加载时(通过使用其中一个命令),moccur-edit 也会加载,以允许编辑 moccur buffer。

4 高级用法

4.1 扩展加载路径

如果 pacakge 的加载需要将一个目录添加到 load-path 中,使用 :load-path 。参数可以是一个符号、函数、字符串或字符串列表。如果是相对路径,基于 user-emacs-directory 进行扩展。

(use-package ess-site
  :load-path "site-lisp/ess/lisp/"
  :commands R)

注意,如果使用一个符号或函数来动态生成路径列表,必须通知字节编译器该定义,以保证该值在编译时可用。这通过使用特殊形式(special form)eval-and-compile (而不是 eval-when-compile)实现。进一步说,该值固定为编译期间确定的值,避免在每次启动时再次查找相同的信息:

(eval-and-compile
  (defun ess-site-load-path ()
    (shell-command "find ~ -path ess/lisp")))

(use-package ess-site
  :load-path (lambda () (list (ess-site-load-path)))
  :commands R)

4.2 自动安装

可以使用 use-package 配合 package.el 从 ELPA 加载 pacakge。这对于在多个机器之间共享.emacs 很有用;在.emacs 中声明的 package 都会自动下载。 :ensure 关键字会自动安装系统没有的 package。

(use-package magit
  :ensure t)

如果希望所有包 package 都这样处理,需要设置

(setq use-package-always-ensure t)

可以这样指定 安装 package 的别名:

(use-package tex-site
  :ensure auctex)

注意: :ensure 会安装系统上没有的 package,但不会进行主动更新。如果希望主动更新,可以使用 auto-package-update,如

(use-package auto-package-update
  :config
  (setq auto-package-update-delete-old-versions t)
  (setq auto-package-update-hide-results t)
  (auto-package-update-maybe))

最后,如果 Emacs 是 24.4 或更高版本,use-package 可以将一个包 pin 到特定的 archive,允许混合和匹配来自不同 archive 的 package。主要使用场景是从 gnu 和 melpa-stable 选择 package(混合选择);但需要跟踪稳定 archive 可用新版本时,使用 melpa 指定的 pacakge 也是一个有效的使用场景。

默认情况下,由于版本比较(> evil-20141208.623 evil-1.0.9),package.el 更喜欢 melpa 而不是 melpa-stable,所以即使只跟踪 melpa 中的一个包,也需要用适当的 archive 标记所有非 melpa 的 pakage。 如果觉得麻烦,那么可以通过 use-package-always-pin 来设置默认值。

如果想手动保持更新 package 并忽略上游更新,可以将其 pin 到 manual ,只要没有该名称的 respository 就可以正常工作。

如果尝试将 package pin 到一个没有使用 package-archives 配置的 archive(除了上面提到的 manual archive),use-package 将会引发错误:

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

Example:

(use-package company
  :ensure t
  :pin melpa-stable)

(use-package evil
  :ensure t)
  ;; no :pin needed, as package.el will choose the version in melpa

(use-package adaptive-wrap
  :ensure t
  ;; as this package is available only in the gnu archive, this is
  ;; technically not needed, but it helps to highlight where it
  ;; comes from
  :pin gnu)

(use-package org
  :ensure t
  ;; ignore org-mode from upstream and use a manually installed version
  :pin manual)

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

其他的 package 管理器通过重写 use-package-ensure-function 或 use-package-pre-ensure-function,可以覆盖 :ensure ,进而使用自身而不是 package.el。目前,唯一这么做的是 =straight.el=。

4.3 绑定 keymap

通常 :bind 绑定的命令是 package 中自动加载的函数。然而,如果命令实际上是键映射(keymap)的话,这就有所不同了,因为键映射不是函数,不能使用 Emacs 的自动加载机制进行自动加载。

为了处理这种情况,use-package 提供了 :bind-keymap ,它是 :bind 一个特殊受限的变体。 二者之间的唯一区别是: :bind-keymap 绑定的命令必须是 package 中定义的键映射,而不是命令函数。这通过生成一段定制代码来处理:这段代码首先加载含有键映射的 package,加载之后再次执行后续的按键,也就说,将 :bind-keymap 绑定的按键解释为一个前缀键来处理。

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

C-h c C-c p 可以查看到 C-c p 绑定到了 lambda 函数。

备注:但是实践的过程中,发现这个关键字使用还有一些注意事项:

  • 和 guide-key 有点冲突,参见 https://github.com/jwiegley/use-package/issues/685
  • 这个指令不能和 hook,或者 global-mode 一起使用,如果这样使用, bind 中绑定的按键就会在当前 mode 中变成全局按键。换个角度,既然已经是 global 了,为什么还要把这些按键绑定到 local-mole-map 呢?

具体用法示例如下:

(use-package smartparens-config
  :ensure smartparens
  :bind-keymap ("C-c s" . smartparens-mode-map)
  :bind
  (:map smartparens-mode-map
        ("c" . sp-beginning-of-next-sexp))
  )

等同于

(use-package smartparens-config
  :ensure smartparens
  :bind
  (:prefix-map smartparens-mode-map
               :prefix "C-c s"
               ("c" . sp-beginning-of-next-sexp))
  )

4.4 局部键绑定

与绑定到键映射稍微不同,局部 keymap 的键绑定只有在 package 加载后才生效。use-package 通过 :map 修饰符支持局部键绑定:

(use-package helm
  :bind (:map helm-mode-map
         ("C-c h" . helm-execute-persistent-action)))

上面声明等到 helm 加载后才生效,在 helm 的局部键映射 helm-mode-map 中将 C-c h 绑定到 helm-execute-persistent-action。

可以使用多个 :map 。首次使用 :map 之前的任何绑定都应用于全局键映射。

(use-package term
  :bind (("C-c t" . term)
         :map term-mode-map
         ("M-p" . term-send-up)
         ("M-n" . term-send-down)
         :map term-raw-map
         ("M-o" . other-window)
         ("M-p" . term-send-up)
         ("M-n" . term-send-down)))

4.5 模式和解释器

类似 :bind ,可以使用 :mode:interpreter 在变量 auto-mode-alist 和 interpreter-mode-alist 中建立延迟绑定。这些关键字的参数可以是 cons、 列表、字符串或正则表达式。

(use-package ruby-mode
  :mode "\\.rb\\'"
  :interpreter "ruby")

;; The package is "python" but the mode is "python-mode":
(use-package python
  :mode ("\\.py\\'" . python-mode)
  :interpreter ("python" . python-mode))

4.6 Magic handlers

类似于 :mode:interpreter ,如果文件开始处符合给定的正则表达式,您还可以使用 :magic:magic-fallback 执行函数。两者之间的区别在于 :magic-fallback 的优先级低于 :mode 。例如:

(use-package pdf-tools
  :load-path "site-lisp/pdf-tools/lisp"
  :magic ("%PDF" . pdf-view-mode)
  :config
  (pdf-tools-install))

4.7 custom

:custom 关键字允许设置 package 的自定义变量。

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

文档字符串并不是必须的。

这些仅适用那些希望通过 customization 定制 use-package 中声明的变量的人。从功能上来说,与 :config 中使用 setq 相比,唯一的好处在于,设置当被赋值时可能会执行代码。如果当前使用 M-x customize-option 保存设置文件,不会想用该选项。

4.8 custom-face

:custom-face 用来设置 package 自定义 face。

(use-package eruby-mode
  :custom-face
  (eruby-standard-face ((t (:slant italic)))))

4.9 延迟加载

如果没有使用 :commands, :bind, :bind*, :bind-keymap, :bind-keymap*, :mode, or :interpreter (这些都意味着 :defer ; 参阅 use-package 中的文档对每个都简单了解一下),也可以使用 :defer 关键字实现延迟加载:

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

这等同于:

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

4.9.1 延迟加载的注意事项

几乎所有情况下都不需要手动指定 :defer t 。每当使用 :bind:mode、 ~:interpreter 时,这都是隐含的。 通常,如果知道其他 package 会做一些事情导致当前 package 在适当的时间加载,只需指定 :defer, 因而即使 use-package 不会为你创建任何自动加载,也会推迟加载。

可以使用 :demand 关键字覆盖包的延迟加载。因此,即使使用 :bind ,使用 :demand 也会强制立即加载,不会为绑定键建立任何自动加载。

4.10 条件加载

4.10.1 if

可以使用 :if 关键字来预测模块的加载和初始化。 例如,只想让 edit-server 运行在图形 Emacs 下,而不是其他从命令行启动的 Emacsen:

(use-package edit-server
  :if window-system
  :init
  (add-hook 'after-init-hook 'server-start t)
  (add-hook 'after-init-hook 'edit-server-start t))

此外,还可以限制操作系统:

(use-package exec-path-from-shell
  :if (memq window-system '(mac ns))
  :ensure t
  :config
  (exec-path-from-shell-initialize))

4.10.2 after

有时,只有在另一个 package 加载之后,加载当前 package 才有意义,因为有些变量或函数此前不在作用域内。这可以通过 :after 关键字来实现,该关键字允许设置相当丰富的加载条件。这里有一个例子:

(use-package hydra
  :load-path "site-lisp/hydra")

(use-package ivy
  :load-path "site-lisp/swiper")

(use-package ivy-hydra
  :after (ivy hydra))

这种情况下,所有的 package 都按照它们出现的顺序进行了加载,使用 :after 关键字不是绝对必要的。但是,通过使用它,上面的代码可以变得与顺序无关,init 文件本质上隐含了这种顺序依赖关系。

默认情况下, :after(foo bar):after(:all foo bar) 相同,这意味着直到 foo 和 bar 加载后才会加载该 package。以下是其他一些可能的例子:

:after (foo bar)
:after (:all foo bar)
:after (:any foo bar)
:after (:all (:any foo bar) (:any baz quux))
:after (:any (:all foo bar) (:all baz quux))

当嵌套使用选择器时,例如 (:any (:all foo bar) (:all baz quux)) , 这意味着只要 foo 和 bar 其中之一加载,或者 baz 和 quux 必须都加载,package 才会加载,。

注意:如果 use-package-always-defer 设置为 t,并且使用了 :after 关键字,则需要说明声明 package 的加载方式:例如通过 :bind 。如果没有使用注册自动加载的机制,例如 :bind:hook ,包管理器也没有提供自动加载,如果不给这些声明添加 :demand 的话,package 将永远不会加载。

4.10.3 require

虽然 :after 可以让 package 在依赖加载后才加载,但如果依赖不可用时, :require 关键字更简单些,它不会加载该 package。此时的可用是指 (featurep 'foo) 计算 non-nil,例如:

(use-package abbrev
  :requires foo)

等同于

(use-package abbrev
  :if (featurep 'foo))

更方便是可以指定一个 package 列表:

(use-package abbrev
  :requires (foo bar baz))

更复杂的逻辑,比如 :after 支持的那些,只需使用 :if 和适合的 lisp 表达式就可以实现。

4.11 字节编译配置文件

use-package 另一个特点,字节编译了.emacs 文件后,它总是加载所有文件。这有助于消除未知变量和函数引起的虚假警告。

然而,有时这还不够。这时,使用 :defines:functions 关键字只为字节编译器引入虚拟变量和函数声明。

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

如果需要消除一个缺失函数的警告,可以使用 :function :

(use-package ruby-mode
  :mode "\\.rb\\'"
  :interpreter "ruby"
  :functions inf-ruby-keys
  :config
  (defun my-ruby-mode-hook ()
    (require 'inf-ruby)
    (inf-ruby-keys))

  (add-hook 'ruby-mode-hook 'my-ruby-mode-hook))

4.12 编译时阻止加载包

正常情况下,编译时,use-package 在编译配置之前加载 package,以满足字节编译器所需的任何必要的符号都在作用域中。有时这会导致问题,因为 package 可能有特殊加载要求,为此希望 use-package 可以为 eval-after-load hook 添加一个配置。这种情况下,使用 :no-require 关键字。(没有理解啥意思和具体是使用场景,不使用不就是这样么)

(use-package foo
  :no-require t
  :config
  (message "This is evaluated when `foo' is loaded"))

4.13 diminish 和 delight 辅助模式

如果安装 diminsh 和 delight,use-package 内置对它们的支持。它的目的是删除或更改 mode line 中的字符串。

通过 :diminish 关键字调用 diminish,该关键字需要一个 minor mode symbol 或符号组成的 cons 以及替代字符串;也可以仅是替代符串,这种情况下,通过末尾带有“-mode"的 package 名猜 minor modes symbol。

(use-package abbrev
  :diminish abbrev-mode
  :config
  (if (file-exists-p abbrev-file-name)
      (quietly-read-abbrev-file)))

通过 :delight 关键字调用 delight,该关键字需要 minor mode symbol 和替换字符串,或带引号的 mode-line 数据(这种情况下,通过末尾带有“-mode"的 package 名猜 minor modes symbol)。这两种用法,或两种用法组成的 list,都可以作为关键字的参数。如果没有提供参数,默认该模式名字完全隐藏。

;; Don't show anything for rainbow-mode.
(use-package rainbow-mode
  :delight)

;; Don't show anything for auto-revert-mode, which doesn't match
;; its package name.
(use-package autorevert
  :delight auto-revert-mode)

;; Remove the mode name for projectile-mode, but show the project name.
(use-package projectile
  :delight '(:eval (concat " " (projectile-project-name))))

;; Completely hide visual-line-mode and change auto-fill-mode to " AF".
(use-package emacs
  :delight
  (auto-fill-function " AF")
  (visual-line-mode))

5 加载信息

package 加载时,如果 use-package-verbose 设置为 t,或者包加载耗时超过 0.1s,就会在 *Messages* buffer 中看到指示此加载活动。如果 :config 块执行超过 0.1s 配置也同样处理。通常,应该保持 :init 应该尽可能简单和迅速,尽可能放在 :config 块中。这样,延迟加载可以帮助 Emacs 尽快启动。

此外,如果初始化或配置 package 时发生了错误,不会阻止 Emacs 继续加载。相反,use-package 会捕获到该错误,并在 *Warnings* popup buffer 中报告,这样可以在其他功能正常的 Emacs 中进行调试。

如果想查看已加载的软件包数量,它们已达到的初始化阶段以及它们花费的总时间(大致),则可以在加载 use-pacakge 后,任何使用 use-package 之前启用 use-package-compute-statistics-package,然后运行命令 Mx use-package-report 查看结果。 显示的缓冲区是一个列表,可以在列中使用 S 进行排序。

6 关键字扩展

Starting with version 2.0, use-package is based on an extensible framework that makes it easy for package authors to add new keywords, or modify the behavior of existing keywords.

Some keyword extensions are now included in the use-package distribution and can be optionally installed.

(use-package-ensure-system-package)

The :ensure-system-package keyword allows you to ensure system binaries exist alongside your package declarations.

First, you will want to make sure exec-path is cognisant of all binary package names that you would like to ensure are installed. exec-path-from-shell is often a good way to do this.

To enable the extension after you've loaded use-package:

(use-package use-package-ensure-system-package
  :ensure t)

Here’s an example of usage:

(use-package rg
  :ensure-system-package rg)

This will expect a global binary package to exist called rg. If it does not, it will use your system package manager (using the package system-packages) to attempt an install of a binary by the same name asyncronously. For example, for most macOS users this would call: brew install rg.

If the package is named differently than the binary, you can use a cons in the form of (binary . package-name), i.e.:

(use-package rg
  :ensure-system-package
  (rg . ripgrep))

In the previous macOS example, this would call: brew install ripgrep if rg was not found.

What if you want to customize the install command further?

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

:ensure-system-package can also take a cons where its cdr is a string that will get called by (async-shell-command) to install if it isn’t found.

You may also pass in a list of cons-es:

(use-package ruby-mode
  :ensure-system-package
  ((rubocop     . "gem install rubocop")
   (ruby-lint   . "gem install ruby-lint")
   (ripper-tags . "gem install ripper-tags")
   (pry         . "gem install pry")))
(use-package-chords)

The :chords keyword allows you to define key-chord bindings for use-package declarations in the same manner as the :bind keyword.

To enable the extension:

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

Then you can define your chord bindings in the same manner as :bind using a cons or a list of conses:

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

6.1 如何创建扩展

6.1.1 First step: Add the keyword

The first step is to add your keyword at the right place in use-package-keywords. This list determines the order in which things will happen in the expanded code. You should never change this order, but it gives you a framework within which to decide when your keyword should fire.

6.1.2 Second step: Create a normalizer

Define a normalizer for your keyword by defining a function named after the keyword, for example:

(defun use-package-normalize/:pin (name-symbol keyword args)
  (use-package-only-one (symbol-name keyword) args
    (lambda (label arg)
      (cond
       ((stringp arg) arg)
       ((symbolp arg) (symbol-name arg))
       (t
        (use-package-error
         ":pin wants an archive name (a string)"))))))

The job of the normalizer is take a list of arguments (possibly nil), and turn it into the single argument (which could still be a list) that should appear in the final property list used by use-package.

6.1.3 Third step: Create a handler

Once you have a normalizer, you must create a handler for the keyword:

(defun use-package-handler/:pin (name-symbol keyword archive-name rest state)
  (let ((body (use-package-process-keywords name-symbol rest state)))
    ;; This happens at macro expansion time, not when the expanded code is
    ;; compiled or evaluated.
    (if (null archive-name)
        body
      (use-package-pin-package name-symbol archive-name)
      (use-package-concat
       body
       `((push '(,name-symbol . ,archive-name)
               package-pinned-packages))))))

Handlers can affect the handling of keywords in two ways. First, it can modify the state plist before recursively processing the remaining keywords, to influence keywords that pay attention to the state (one example is the state keyword :deferred, not to be confused with the use-package keyword :defer). Then, once the remaining keywords have been handled and their resulting forms returned, the handler may manipulate, extend, or just ignore those forms.

The task of each handler is to return a list of forms representing code to be inserted. It does not need to be a progn list, as this is handled automatically in other places. Thus it is very common to see the idiom of using use-package-concat to add new functionality before or after a code body, so that only the minimum code necessary is emitted as the result of a use-package expansion.

6.1.4 Fourth step: Test it out

After the keyword has been inserted into use-package-keywords, and a normalizer and a handler defined, you can now test it by seeing how usages of the keyword will expand. For this, temporarily set use-package-debug to t, and just evaluate the use-package declaration. The expansion will be shown in a special buffer called use-package.

Author: liushangliang

Email: phenix3443+github@gmail.com

Created: 2020-04-26 日 10:53