- 目标:use-package 是一个很好的 Emacs 配置隔离工具,符合规则书写配置可实现延迟加载,提升启动速度。对于我这样的 lisp 小白来说,是福音。
- 翻译源:https://github.com/jwiegley/use-package
- 更新频率:use-package 自身更新很快,但是核心使用方式基本没变化,所以频率不重要。 欢迎PR
- 更新记录::
- <2023-02-05 Sun 19:12> 同步至当前最新 77945e0
- <2020-04-23 Thu 16:37> 首次更新
use-package
宏允许你以性能导向、优雅、整洁的方式隔离你的 .emacs
配置文件。我创建它是因为我在 Emacs 中使用了 80 多个 package,管理起来非常困难。
有了 use-package 之后,我的总加载时间约为 2 秒,而没有功能上的损失。
注意: use-package
不是 一个包管理工具。尽管 use-package
的确有与包管理工具交互的(很有用)功能,但它的主要目的是配置和加载程序包。
- 安装 use-package
- 入门
- 快捷键绑定(Key-binding)
- Modes 和 interpreters
- Magic Handlers
- Hooks
- 包定制
- 延迟加载注意事项
- 有关包加载的信息
- 条件加载
- 字节编译(byte-compiling)你的 .emacs
- 扩展 load-path
- 在包扩展期间捕获错误
- diminishing 和 delighting 次要模式(minor modes)
- 包的安装
- 收集统计
- 关键字扩展
- 一些计时(timling)结果
- 升级到 2.x
从 这个 Github 仓库中 clone 或者从 GNU ELPA 安装(推荐)。
use-package
最简单的声明方式:
;; This is only needed once, near the top of the file
(eval-when-compile
;; Following line is not needed if use-package.el is in ~/.emacs.d
(add-to-list 'load-path "<path where use-package is installed>")
(require 'use-package))
(use-package foo)
这会加载 foo
包,但是只有 foo
在你的系统中可用的时候。如果没有的话,会打印告警日志到 *Message*
buffer。
使用 :init
关键字在包加载之前执行代码。它接受一个或者多个形式(forms),直到下一个关键字出现:
(use-package foo
:init
(setq foo-variable t))
类似, :config
可以在包加载之后执行代码。在延迟加载的场景下(请参阅下面的有关自动加载的更多信息),config 中的代码会延迟到自动加载发生之后:
(use-package foo
:init
(setq foo-variable t)
:config
(foo-mode 1))
如你所想,你可以将 :init
和 :config
组合使用:
(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
命令,然后进行全局层面和 isearch-mode-map
内部绑定(查看下一小结)。
当包实际被加载之后(通过使用这些命令中的一个), moccur-edit
也会被加载,以允许编辑 moccur
buffer。
如果您自己加载非交互式函数,请使用 :autoload
:
(use-package org-crypt
:autoload org-crypt-use-before-save-magic)
在加载模块时通常要做的是将模块的中的主要命令绑定快捷键:
(use-package ace-jump-mode
:bind ("C-." . ace-jump-mode))
它做了两件事情:首先,它为 ace-jump-mode
命令创建了自动加载(autoload),延迟到你实际使用此命令时才会 ace-jump-mode
;
第二,它为此命令绑定了快捷键 C-.
。
加载之后,你可以使用 M-x describe-personal-keybindings
来查在 .emacs
配置中所有的此类快捷键。
另外一种更直接的方法也可以实现相同效果:
(use-package ace-jump-mode
:commands ace-jump-mode
:init
(bind-key "C-." 'ace-jump-mode))
当你使用 :commands
关键字时,它会给这些命令创建自动加载,并延迟模块加载直到使用它们为止。因为 :init
总是会运行 – 即便 ace-jump-mode
可能不在你的系统上
– 切记 :init
中的代码要保证都可以运行成功。
:bind
关键字可以接受一个或多个 cons 列表。
(use-package hi-lock
:bind (("M-o l" . highlight-lines-matching-regexp)
("M-o r" . highlight-regexp)
("M-o w" . highlight-phrase)))
或者,命令名称可以被替换为 cons (desc . command)
,其中 desc
是一个描述绑定的 command
的字符串:
(use-package avy
:bind ("C-:" ("Jump to char" . avy-goto-char)
"M-g f" ("Jump to line" . avy-goto-line)))
:commands
通常也支持 symbols 和 symbols 列表。
注意: 命令内部的字符串,特殊的键位比如 tab
或者 F1
-=Fn= 必须要写在尖括号里面,比如 "C-<up>"
。独立的特殊键(和某些组合)可以写在方括号里中,
比如: 用 [tab]
代替 <tab>
。这种键位绑定语法类似 “kbd” 语法,从 https://www.gnu.org/software/emacs/manual/html_node/emacs/Init-Rebinding.html 获取更多信息。
举例:
(use-package helm
:bind (("M-x" . helm-M-x)
("M-<f5>" . helm-find-files)
([f10] . helm-buffers-list)
([S-f10] . helm-recentf)))
此外,使用 :bind
和 bind-key
重新映射命令 可以按照预期工作,因为当绑定是向量(vector)时,它直接传递给 define-key
。
所以下面的例子将重新绑定 M-q
(原先是 fill-paragraph
)到 unfill-toggle
。
(use-package unfill
:bind ([remap fill-paragraph] . unfill-toggle))
通常, :bind
的预期是 command 是将从给定的包中自动加载的函数。但有些命令实际上是 keymap,这么做会不生效,因为 keymap 不是一个函数,
无法使用 Emacs 的 autoload
机制。
为了应对这种场景, use-package
提供了一个 :bind
变体叫 :bind-keymap
。唯一的不同是由 :bind-keymap
对应的“命令”必须是包中定义的 keymaps,
而不是 commmand 函数。它的处理方式是生成自定义代码来加载包含键盘映射的包,然后在你执行按键的时候首次加载,进而将键重新解释为前缀快捷键:
比如:
(use-package projectile
:bind-keymap
("C-c p" . projectile-command-map))
与绑定到 keymap 不同,它绑定局部 keymap 中的一个快捷键,而且只有在包已经被加载之后才存在。 use-package
通过 :map
修饰符来支持它,将局部 keymap 绑定到:
(use-package helm
:bind (:map helm-command-map
("C-c h" . helm-execute-persistent-action)))
该语句会等到 helm
被加载的时候,绑定 C-c h
到 Helm 的局部 helm-mode-map
中的 helm-execute-persistent-action
。
:map
可以指定多次,发生在 :map
之前的任何绑定都会认为是全局 keymap:
(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)))
在局部 keymap 中有一个绑定特例是当该 keymap 使用为 repeat-mode
。这些 keymaps 通常专门为此定义。使用 :repeat-map
关键字,
并为其定义的 map 设定一个名称,然后(默认情况下)讲每个绑定到此 map 的命令设置 repeat-map
属性。
下面的代码创建了一个称为 git-gutter+-repeat-map
的 keymap,在其中进行四个绑定,然后为每个命令( git-gutter+-next-hunk
git-gutter+-previous-hunk
git-gutter+-stage-hunks =git-gutter+-revert-hunk=
)都设置了 repeat-map
属性。
(use-package git-gutter+
:bind
(:repeat-map git-gutter+-repeat-map
("n" . git-gutter+-next-hunk)
("p" . git-gutter+-previous-hunk)
("s" . git-gutter+-stage-hunks)
("r" . git-gutter+-revert-hunk)))
在 :repeat-map
范围内使用 :exit
来停止 repeat-map
属性设置。在 repeat map 中使用该命令,repeat map 将不再可用。
这对于在一系列的重复命令尾部使用很有用:
(use-package git-gutter+
:bind
(:repeat-map my/git-gutter+-repeat-map
("n" . git-gutter+-next-hunk)
("p" . git-gutter+-previous-hunk)
("s" . git-gutter+-stage-hunks)
("r" . git-gutter+-revert-hunk)
:exit
("c" . magit-commit-create)
("C" . magit-commit)
("b" . magit-blame)))
指定 :continue
命令会 强制 设置 repeat-map
属性(就跟没指定 :exit
一样),所以下面两个片段是等价的:
(use-package git-gutter+
:bind
(:repeat-map my/git-gutter+-repeat-map
("n" . git-gutter+-next-hunk)
("p" . git-gutter+-previous-hunk)
("s" . git-gutter+-stage-hunks)
("r" . git-gutter+-revert-hunk)
:exit
("c" . magit-commit-create)
("C" . magit-commit)
("b" . magit-blame)))
(use-package git-gutter+
:bind
(:repeat-map my/git-gutter+-repeat-map
:exit
("c" . magit-commit-create)
("C" . magit-commit)
("b" . magit-blame)
:continue
("n" . git-gutter+-next-hunk)
("p" . git-gutter+-previous-hunk)
("s" . git-gutter+-stage-hunks)
("r" . git-gutter+-revert-hunk)))
类似 :bind
,你可以使用 :mode
和 :interpreter
建立与 auto-mode-alist
和 interpreter-mode-alist
中的变量内部延迟绑定。
每个关键字的说明符都是可以 cons,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))
如果你没有设置 :commands
, :bind
, :bind*
, :bind-keymap
, :bind-keymap*
, :mode
, :interpreter
或者 :hook
(这些都实现了 :defer
;
查看 use-package
的 docstring 获取每个文档的简要说明),你仍旧可以通过 :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))
与 :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 :no-query))
这会为 pdf-view-mode
注册一个自动加载命令,延迟加载 pdf-tools
。如果 buffer 开头与字符串 "%PDF"
匹配,运行 pdf-view-mode
。
:hook
关键字允许将函数添加到包 hooks 上。因此,下面三种方式都是等价的:
(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))
同样,应用到多个 hook 时,以下的内容也是等价的:
(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))
当使用 :hook
时要忽略 “-hook” 后缀,默认情况会自动追加。比如下面的代码不会生效,因为它实际上会扩展成 prog-mode-hook-hook
并不存在:
;; DOES NOT WORK
(use-package ace-jump-mode
: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
关键字允许自定义包的自定义变量。
(use-package comint
:custom
(comint-buffer-maximum-size 20000 "Increase comint buffer size.")
(comint-prompt-read-only t "Make the prompt read only."))
上面的文档字符串不强制。
注意: 这些仅适用于想要在 use-packages 声明的地方保留自定义能力的人。功能上,相比与在 :config
块中使用 setq
的唯一优势在于:
自定义可能在分配值时执行代码。
注意: 这些自定义值 不会 保存在 Emacs 的 custom-file
中,因此你要么使用 :custom
选项 或者 使用 M-x customize-option
(它会将自定义值保存在 Emacs 的 custom-file
中),不要同时使用两者。
custom-face
关键字允许自定义包中的 faces。
(use-package eruby-mode
:custom-face
(eruby-standard-face ((t (:slant italic)))))
(use-package example
:custom-face
(example-1-face ((t (:foreground "LightPink"))))
(example-2-face ((t (:foreground "LightGreen"))) face-defspec-spec))
(use-package zenburn-theme
:preface
(setq my/zenburn-colors-alist
'((fg . "#DCDCCC") (bg . "#1C1C1C") (cyan . "#93E0E3")))
:custom-face
(region ((t (:background ,(alist-get my/zenburn-colors-alist 'cyan)))))
:config
(load-theme 'zenburn t))
:commands
等关键字提供了在特定事件发生时导致包加载的“触发器”。如果 use-package
无法确定是否有任何触发器的声明,它会在 Emacs 立即加载,
除非你指定了 :defer t
。你可以使用 :demand t
覆盖触发器的存在,让它强制立即加载。比如 :hook
表示当指定的 hook 运行时才触发。
绝大多数情况下,你都不需要手动设置 :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,而不希望其它时候启动:
(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))
:disabled
关键字可以关闭遇到困难的模块,或者停止加载你当前未使用的内容:
(use-package ess-site
:disabled
:commands R)
当字节编译(byte-compiling)你的 .emacs
文件的时候,在输出中完全省略了禁用声明(的包),以加快启动时间。
注意: :when
是 :if
的别名,而且 :unless foo
等价于 :if (not foo)
。
如果你需要对 use-package form 进行条件判断,确保它出现在 :preface
执行之前,简单的方式是用 :when
把 use-package 包裹起来。
比如,下面的例子也会在 Mac 系统中停止 :ensure
:
(when (memq window-system '(mac ns))
(use-package exec-path-from-shell
:ensure t
:config
(exec-path-from-shell-initi
有时需要在一个包加载完之后才会加载另外一个包,因为某些变量或函数在那之前不在作用域内。这种情况可以使用 :after
关键字来实现,
该关键字允许在满足确定条件后再加载。比如:
(use-package hydra
:load-path "site-lisp/hydra")
(use-package ivy
:load-path "site-lisp/swiper")
(use-package ivy-hydra
:after (ivy hydra))
上面的例子中,所有的包都是按找出现的顺序加载的,因此 :after
的使用不是强制需要的。但是,使用 :after
之后,上面的代码就于与顺序无关了,
与原始的 init 文件中的顺序没有了隐式的依赖。
默认情况下, :after (foo bar)
与 :after (:all foo bar)
相同,意味着给定的加载包必须要等到 foo
和 bar
全部加载之后。
下面是一些其他的可能性:
: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
。
注意: 如果你设置了 use-package-always-defer
为 t
,并且使用了 :after
关键字,那么你需要指定包的加载方式:比如,使用 :bind
。
如果你没有使用注册自动加载的机制之一,比如 :bind
或者 :hook
,而且您的包管理器没有提供自动加载功能,并且没有添加 :demand t
到这些声明中,
那么这些包永远不会被加载。
虽然 after
关键字可以延迟加载直到依赖项加载,但遇到 use-package
声明时有依赖项不可用,那么简单的 :requires
关键字根本不会加载包。
这种情况下,“可用”意味着如果如果 (featurep 'foo)
的计算结果为非 nil 值,则 foo 可用。比如:
(use-package abbrev
:requires foo)
等价于:
(use-package abbrev
:if (featurep 'foo))
为了方便,你可以指定依赖列表:
(use-package abbrev
:requires (foo bar baz))
对于更复杂的逻辑,比如说 :after
支持的,只需要使用 :if
合适的 Lisp 表达式即可。
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)))
如果你想要函数缺失的警告静音(silence),你可使用 :functions
:
(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))
通常, use-package
在编译配置之前,在编译时加载每个包,确保必要的标识符都在满足字节编译器的范围内。
有时这样会引起问题,由于包可能有特殊的加载需求,你要使用 use-pacakge
的所有操作就是将配置添加到 eval-after-load
hook。
这种情况下,使用 :no-require
关键字:
(use-package foo
:no-require t
:config
(message "This is evaluated when `foo' is loaded"))
如果一个包为了加载需要一个目录添加到 load-path
,使用 :load-path
。它的参数是一个符号,一个函数,一个字符串或者一个字符串列表。
如果是相对路径,会基于 user-package-directory
扩展:
(use-package ess-site
:load-path "site-lisp/ess/lisp/"
:commands R)
注意: 当使用符号(symbol)或者函数提供动态的路径列表时,你必须要将此定义告知字节编译器,以便在字节编译时可用。
这是通过使用特殊形式的 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)
默认情况下,如果 use-package-expand-minimally
是 nil(默认值),use-package 将尝试捕捉并报告在你的 init 文件中扩展使用包声明期间发生的错误。
设置 use-package-expand-minimally
为 t
完全禁用此检查。
这种行为可以被局部使用 :catch
来覆盖。设置为 t
或者 nil
,在加载时启动或者禁用捕获错误。它也可以是带有两个参数的函数:
遇到错误时正在处理的关键字,和错误对象(由 condition-case
生成)。比如:
(use-package example
;; Note that errors are never trapped in the preface, since doing so would
;; hide definitions from the byte-compiler.
:preface (message "I'm here at byte-compile and load time.")
:init (message "I'm always here at startup")
:config
(message "I'm always here after the package is loaded")
(error "oops")
;; Don't try to (require 'example), this is just an example!
:no-require t
:catch (lambda (keyword err)
(message (error-message-string err))))
执行结果:
I’m here at byte-compile and load time. I’m always here at startup Configuring package example... I’m always here after the package is loaded oops
use-package
还提供了内置的 diminish 和 delight 功能支持 – 如果你已经安装了它们的话。它们的目标是在你的 mode-line 中删除或者更改次要模式的字符串。
diminish 用 :diminish
关键字调用,可以使用次要模式标识符传递,符号和它替换字符串的 cons,或者只是一个替换字符串,这种情况下,次要模式的标识符被猜测为
包名字加上 “-mode” :
(use-package abbrev
:diminish abbrev-mode
:config
(if (file-exists-p abbrev-file-name)
(quietly-read-abbrev-file)))
delight 用 :delight
关键字调用,传递了次要模式标识符,替换字符串或者引号包裹的 mode-line 数据(这种情况下,次要模式符号被认为是包名称末尾加上 “-mode”),
这两个,或者两者的几个列表。如果没有提供任何参数,默认模式名字将完全被隐藏。
;; 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))
你可以使用 use-package
通过 package.el
从 ELPA 加载包。如果你在多台计算机之间共享 .emacs
,这会非常有用;
一旦在你的 .emacs
中声明,相关的包会自动下载。如果系统中不存在, :ensure
关键字会让包自动安装。
(use-package magit
:ensure t)
如果你需要安装与 use-package
命名的包不同的包,可以这样指定:
(use-package tex
:ensure auctex)
如果你希望此行为对所有的包都有效,打开 use-package-always-ensure
:
(require 'use-package-ensure)
(setq use-package-always-ensure t)
注意: :ensure
在包不存在时会自动安装包,但它并不能保持最新。如果你希望你的包自动升级,一个选项是使用 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 的包。
大部分场景都是从 melpa-stable
和 gnu
archive 中选择软件包,但是要使用来自 melpa
中的包,当你需要使用比 stable
archives
可用版本更新的版本时,这也是一种使用场景。
默认情况下 package.el
由于版本控制的原因 (> evil-20141208.623 evil-1.0.9)
相比 melpa-stable
更喜欢 mepla
,因此即使你想只有一个包来自 melpa
,
你需要把所有非 melpa
的包都要从这个 archive 下载。如果这个让你感到烦恼,你可以未 use-package-always-pin
设置一个默认值。
如果要手动保持包更新,而忽略上游更新,你可以将它固定(pin)为 manual
,只要没有该名称的存储库,就可以使用。
如果你固定(pin)的 archive 没有出现在 package-archives
的配置列表中, use-package
会抛出一个错误(除了上面提到的 :manual
):
Archive 'foo' requested for package 'bar' is not available.
举例:
(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
参数对于 emacs 版本小于 24.4 无效。
通过覆盖 use-package-ensure-function
和/或者 use-package-pre-ensure-function
,其它的包管理器可以重写 :ensure
使用它们而不是 package.el
。
目前,唯一执行此操作的包管理器是 straight.el。
如果你想查看已经加载包数量,它们到达了什么初始化阶段,它们花了多少时间(大约),在加载 use-package
之后打开 use-package-compute-statistics
(在
任何使用 use-package
forms 之前),然后运行命令 M-x use-package-report
查看结果。显示的 buffer 是一个列表,你可以在对应的列上使用 S
进行排序。
从 2.0 版本开始, use-package
基于可扩展的框架,使得包的作者添加新的关键字或者修改现有关键字变的很轻松。
现在,某些关键字扩展包含在 use-package
的发行版中,可以选择性安装。
:ensure-system-package
允许你确保系统的二进制文件和你的包声明一起存在。
首先,你希望保证 exec-path
能够识别你已经安装的二进制包的名称,exec-path-from-shell 是一个好的解决办法。
在你 use-package
加载之后启用扩展:
(use-package use-package-ensure-system-package
:ensure t)
下面是使用范例:
(use-package rg
:ensure-system-package rg)
它会期望全局的二进制 rg
存在。如果不存在的话,它会使用你的系统包管理器(使用 system-packages)尝试异步安装同名的二进制。
比如,macOS 用户可能会调用: brew install rg
。
如果包名字和二进制名字不同,你可以用 (binary . package-name)
的格式,即:
(use-package rg
:ensure-system-package
(rg . ripgrep))
在前面的 macOS 例子中,如果 rg
没找到的话,会调用: brew install ripgrep
。
如果你想自定义安装命令怎么办?
(use-package tern
:ensure-system-package (tern . "npm i -g tern"))
:ensure-system-package
也可以通过 (async-shell-command)
调用安装。
你也可以安装传入一个列表:
(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 dash-at-point
:if (eq system-type 'darwin)
:ensure-system-package
("/Applications/Dash.app" . "brew cask install dash"))
:ensure-system-package
会使用 system-package-install
来安装系统包,如果指定了自定义命令,将由 async-shell-command
执行。
配置变量 system-packages-package-manager
和 system-packages-use-sudo
会很有用,但是不适用于自定义命令。自定义命令如果需要的话,需要在命令本身上加上 sudo
。
:chords
关键字允许你为 use-package
定义 [[https://www.emacswiki.org/emacs/key-chord.el][key-chord]]
绑定,与 :bind
关键字类似。
启动扩展:
(use-package use-package-chords
:ensure t
:config (key-chord-mode 1))
然后你可以使用与 :bind
类似的定义方法来绑定:
(use-package ace-jump-mode
:chords (("jj" . ace-jump-char-mode)
("jk" . ace-jump-word-mode)
("jl" . ace-jump-line-mode)))
第一步在 use-package-keywords
合适的位置添加你的关键字。这个列表决定了扩展代码中的执行顺序。你永远不要修改这个顺序,
但它为你提供了一个框架,你可以在其中决定何时触发你的关键字。
通过定义以关键字命名的函数来为关键字定义 normalizer,例如:
(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)"))))))
normalizer 的工作是获取一参数列表(可能是 nil),并将其转换为应该出现在 use-package
使用的最终属性列表中的单个参数(仍旧是个列表)。
一旦你有一个 normalizer,你必须要为关键字创建一个处理器:
(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))))))
处理器有两种方式来影响关键字:首先,它可以递归处理剩余关键字之前修改 state
plist,影响关注状态的关键字(一个例子是 :deferred
关键字,不要与 :defer
混淆)。然后,一旦处理了剩余的关键字并返回了它们的结果 form,处理程序就可以操纵、扩展或直接忽略这些 forms。
每个处理器的任务是返回 一个 forms 列表 来表示要插入的代码。它不需要一个 progn
列表,因为这是在其他地方自动处理的。
因此,在代码主体之前或者之后使用 use-package-concat
添加新功能的用法很常见,作为使用包扩展的结果,只发出必要的最少代码。
在关键字插入到 use-package-keywords
之后,以及 normalizer 和处理器定义之后,你现在可以通过查看关键字的使用来测试它。
为此,使用 M-x pp-macroexpand-last-sexp
并将光标设置在 (use-package ...)
表达式之后。
在我的 iMac Retina 上,Emacs 24.4 的 “Mac port” 变种,大约配置了 218 个包(几乎所有的都是懒加载),加载了 0.57 秒。 但是,除了第一次使用 Emacs 时(由于自动加载),我没有损失任何功能。由于我对许多软件包使用了空闲加载,因此通常可以整体上减少延迟感知。
在 Linux 上,相同的配置用了 0.32 秒。
如果不用图形方式使用 Emacs,可以测试到绝对最小时间,通过以下命令完成:
time emacs -l init.elc -batch --eval '(message "Hello, world!")'
在 Mac 上平均 0.36 秒,在 Linux 上 0.26 秒。
:init
的含义发生了变化:现在它 总是 的包加载之前出现,无论 :config
推迟与否。这意味着可能需要将 :init
中的配置放到了 :config
中(在非延迟的情况下)。
对于延迟的情况,行为和之前相同。
也因为 :init
和 :config
现在意味着 “之前” 和 “之后”,所以 :pre-
和 :post-
关键字消失了,它们也不再被需要了。
最后,即使存在包配置失败的情况,也尽力让你的 Emacs 启动。因此,在此更改之后,要检查你的 *Message
buffer。最有可能的是,
在几个例子中使用 :init
,但应该跟更多地方使用 :config
。
我现在要删除此功能,是因为它可能出现让人生厌的不一致,考虑以下定义:
(use-package vkill
:commands vkill
:idle (some-important-configuration-here)
:bind ("C-x L" . vkill-and-helm-occur)
:init
(defun vkill-and-helm-occur ()
(interactive)
(vkill)
(call-interactively #'helm-occur))
:config
(setq vkill-show-all-processes t))
如果我加载我的 Eamcs 然后等到空闲计数器触发,然后这是事件的顺序:
:init :idle <load> :config
但是,如果我加载 Emacs 并立即输入 C-x L
而不等待空闲计数器触发,它的事件顺序时这样的:
:init <load> :config :idle
用户可能在闲置状态下使用 featurep
来测试这种情况,但这是我想避免的。
:defer [N]
会导致包加载 – 如果还没有的话 – 在 N
秒之后执行。
(use-package back-button
:commands (back-button-mode)
:defer 2
:init
(setq back-button-show-toolbar-buttons nil)
:config
(back-button-mode 1))
:preface
可用于建立函数和变量定义 1)让字节编译器开心(它不会抱怨定义未知的函数,因为他们在保护块中),2)允许您定义在 :if
测试中使用的代码。
注意: :preface
中指定的任何内容都会在加载时和字节编译时执行,为了确保 Lisp 求职器和字节编译器能看到定义,所以你应该避免
在你的前沿中产生任何副作用,并将它限制在符号生命和定义上。
:defines
是为变量的, :functions
是为函数的。
也就是说你可以将以下内容放在 Emacs 的顶部,进一步减少加载时间:
(eval-when-compile
(require 'use-package))
(require 'diminish) ;; if you use :diminish
(require 'bind-key) ;; if you use any :bind variant