使用use-package简化你的.emacs

・28 分钟阅读

  • 源代码名称: use-package
  • 源代码网址: https://www.github.com/jwiegley/use-package
  • use-package的文档
  • use-package的源代码下载
  • Git URL:
    git://www.github.com/jwiegley/use-package.git
  • Git Clone代码到本地:
    git clone https://www.github.com/jwiegley/use-package
  • Subversion代码到本地:
    $ svn co --depth empty https://www.github.com/jwiegley/use-package
                              Checked out revision 1.
                              $ cd repo
                              $ svn up trunk
              
  • use-package

    通过use-package宏,你可以面向性能的方式将包配置隔离在.emacs文件中,我创建它是因为我在Emacs中使用了80多个包,事情变得难以管理,使用这个实用程序,我的总加载时间大约是2秒,但没有功能损失!

    升级到2.x的用户的说明位于在底部

    基础知识

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

    (use-package foo)

    此加载在软件包foo中,但是,只有在你的系统上有foo可用时,如果没有,则会向*Messages*缓冲区记录警告,如果成功,将记录关于"Loading foo"的消息,以及加载它所花费的时间(如果超过0.1秒)。

    在加载软件包之前使用:init关键字执行代码,它接受一个或多个窗体,直到下一个关键字:

    (use-package foo
     :init (setq foo-variable t))

    同样,: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-moccurisearch-all,并在全局级别和isearch-mode-map (查看下一节)中绑定键,当包实际加载(通过使用以下命令之一)时,也加载moccur-edit,以允许编辑moccur缓冲区。

    键绑定

    加载模块的另一个常见操作是将一个键绑定到该模块中的主命令:

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

    这做了两件事:首先,它为ace-jump-mode命令创建一个自动加载,并在实际使用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关键字时它会为这些命令创建autoloads,并且推迟加载模块直到它们被使用,因为:init表单总是运行--即使ace-jump-mode可能不在你的系统上--也要记住将:init代码限制为只能成功的方式。

    :bind关键字采用cons或conses列表:

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

    :commands关键字同样接受符号或符号列表。

    注意:tabF1 -Fn等特殊键可以用方括号编写,i,e,[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期望命令是来自给定包的autoloaded的函数,但是,如果其中一个命令实际上是一个键映射这不起作用,因为键映射不是函数,并且不能使用Emacs的autoload机制autoloaded。

    为了处理这种情况use-package提供了一个名为:bind-keymap:bind的特殊有限变体,唯一的区别是,绑定到:bind-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-execute-persistent-actionhelm-mode-map

    可以指定: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)))

    模式和解释程序

    :bind类似,你可以使用:mode:interpreterauto-mode-alistinterpreter-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))

    在使用:commands:bind:bind*:bind-keymap:bind-keymap*:mode:interpreter (所有这些都意味着:defer )的情况下,有关每个use-package的简短说明,请参见字符串说明,你仍然可以使用:defer关键字推迟加载:

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

    这与下面的内容完全相同:

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

    关于延迟加载的说明

    在几乎所有情况下,都不需要手动指定:defer t ,当使用:bind:mode:interpreter时,就隐含,通常,你只需要指定:defer如果你知道某个其他包将执行某些操作以便让你的包在适当的时间加载,因此你希望延迟加载,即使use-package isn 不为你创建任何自动加载。

    你可以使用:demand关键字替代软件包延迟,因此,即使使用:bind,使用:demand也将强制立即加载,而不是为绑定键建立自动加载。

    关于包加载的信息

    加载包时,如果将use-package-verbose设置为t,或者包要加载时间超过0.1,则会在*Messages*缓冲区中显示此加载活动,配置也会发生同样的情况,或者:config块要执行的时间超过0.1 s,一般来说,应该尽可能简单快速地保持:init窗体,并且尽可能多地使用:config块,这样,延迟加载可以帮助你的Emacs尽可能快速地启动。

    此外,如果在初始化或配置包时发生错误,这将不会阻止你的Emacs加载,相反,错误将被use-package捕获,并报告给一个特殊的*Warnings*弹出缓冲区,以便在其他功能的Emacs中调试。

    条件加载

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

    例如,我只希望edit-server运行我的主要图形Emacs而不是其他Emacsen我可以从命令行开始:

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

    在另一个例子中,我们可以在操作系统上加载条件:

    
    (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)

    当字节编译你的.emacs文件时禁用的声明完全是来自输出的ommitted以加快启动时间。

    字节编译你的.emacs

    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)))

    如果你需要静默,不要功能警告,你可以使用:functions:

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

    防止在编译时加载包

    通常use-package会在编译配置之前加载每个包,以确保所有必要的符号都位于满足字节编译器的范围内,这会导致问题,因为包可能有特殊的加载需求,而你希望使用use-packageeval-after-load hook添加配置,在这种情况下,使用:no-require关键字,这意味着:defer

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

    扩展加载路径

    如果包需要添加到load-path中以便加载的目录,请使用:load-path ,它接受符号,函数,字符串或字符串列表,如果路径是相对的,则在user-emacs-directory中展开该路径:

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

    注意,当使用符号或函数提供一个动态生成的路径列表时,必须通知字节编译器,这是通过使用特殊表单eval-and-compile (eval-when-compile相对应)完成的,此外,在编译期间确定了此值,以避免每次启动时再次查找相同的信息:

    (eval-and-compile
     (defuness-site-load-path ()
     (shell-command "find ~ -path ess/lisp")))
    (use-package ess-site
     :load-path (lambda () (list (ess-site-load-path)))
     :commands R)

    diminish和delight模式

    use-package还提供了对diminish和delight实用程序的支持 - 如果你安装了它们。它们的目的是删除或改变模式行中的小模式字符串。

    使用:diminish关键字调用缩小该关键字传递次模式符号,符号的cons及它替换字符串,或者只是替换字符串在这种情况下次模式符号被猜测为附加了"-mode"的包名称在末尾:

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

    使用:delight关键字调用愉快该关键字传递次模式符号,替换字符串或引用模式行数据 (在这种情况下,次要模式符号被猜测为包名,"-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从ELPA加载package.el的包,如果你在多台机器之间共享.emacs,这是特别,相关软件包在你的.emacs中声明后,自动下载,如果你的系统(如果希望此行为对所有软件包都是全局的,请设置(setq use-package-always-ensure t))上还没有安装:ensure关键字,那么这个关键字将自动安装该软件包:

    (use-package magit
     :ensuret)

    如果需要从use-package指定的软件包安装另一个软件包,则可以将它指定为:

    (use-package tex
     :ensure auctex)

    最后,在Emacs 24.4或更高版本上运行时,使用包可以将包钉到特定的归档中,从而允许你将来自不同归档,这个的主要用例是优先使用melpa-stablegnu存档中的包,但是当你需要跟踪stable存档中可用的新版本时使用melpa中的特定包也是一个有效的用例。

    默认情况下,由于版本化,package.el优先于melpa-stable上的melpa(> evil-20141208.623 evil-1.0.9) 因此,即使只跟踪melpa中的一个包,也需要将所有非melpa包标记为适当的归档文件,如果这真的很烦人,那么你可以设置use-package-always-pin设置默认值。

    如果试图将软件包固定在未使用package-archives (除了上面提到的魔术manual归档)配置的归档文件中,use-package将引发错误:

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

    例如:

    (use-package company
     :ensuret:pin melpa-stable)
    (use-package evil
     :ensuret)
     ;; no :pin needed, as package.el will choose the version in melpa(use-package adaptive-wrap
     :ensuret;; 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
     :ensuret;; 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窗体中指定:defer-install t ,(当然,这只对:defer t:ensure t有影响。),

    当触发由use-package生成的自动装载程序或者该特性是由:after子句加载时,将安装该软件包,但是,了解此机制的局限性是很重要的: 当你require该功能时或者当你通过包而不是use-package调用autoloaded的函数时不会触发延迟安装,因此,如果指定:defer-install t,就还需要确保use-package (通过:commands:mode,等等)专门生成任何合理入口点的autoloads。

    在代码中或交互式地,可以触发安装程序的安装,这些软件包的安装使用,use-package-install-deferred-package

    延迟安装当前与字节编译不兼容。

    扩展使用新的或已修改的关键字的软件包

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

    第一步:添加关键字

    第一步是在use-package-keywords的正确位置添加你的关键字,这个列表决定了在扩展代码中事情发生的顺序,你不应该改变这个顺序,但是,它给你一个框架来决定什么时候你的关键字应该激发。

    第二步:创建一个规范化器

    通过定义关键字来定义关键字的构造函数,方法是在关键字后面定义一个函数,例如:

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

    by的工作是获取参数(可能为空)的列表,并将它转换成应用于use-package的最终属性列表中的单个参数(它仍然是一个清单)。

    第三步:创建处理程序

    一旦有了normalizer,就必须为关键字创建一个处理程序:

    (defunuse-package-handler/:pin (name-symbolkeywordarchive-namereststate)
     (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,不要与use-package关键字:defer混淆)的关键字,然后,一旦处理了其余关键字,并且返回其结果表单,处理程序就可以操作,扩展或只忽略这些表单。

    每个处理程序的任务是返回表示要插入的代码的表单列表,它不需要是progn列表,因为它在其他地方自动处理,因此很常见的是使用use-package-concat在代码体之前或之后添加新功能的习惯用法,因此只有最少的代码才能作为use-package扩展的结果发出。

    第四步:测试它

    在将关键字插入use-package-keywords,并且定义了规范化程序和处理程序之后,你现在可以通过查看关键字的用法将如何扩展来对它进行测试,为此,临时将use-package-debug设置为t,并只计算use-package声明,这个扩展将显示在一个叫做*use-package*的特殊缓冲区中。

    一些时间结果

    在我的Retina iMac上,Emacs 24.4的"Mac port "加载花了0.57秒,配置了大约218个软件包(几乎所有软件包都是惰性的)。但是,当我第一次开始使用Emacs时(由于自动加载),我没有遇到功能损失,只有一点延迟。由于我还对许多包使用空闲加载,因此通常会降低感知延迟。

    在Linux上,相同的配置加载0.32秒。

    如果我不使用图形化的Emacs,我可以测试绝对最小时间,这是通过运行以下命令完成的:

    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开始使用包配置失败,因此,在更改之后,一定要检查*Messages*缓冲区,很可能,你将有几个使用:init的实例,但是应该使用:config (我在许多地方都是这样的)。

    :idle已被删除

    我暂时删除此功能,因为它可能导致令人讨厌的不一致。请考虑以下定义:

    (use-package vkill
     :commands vkill
     :idle (some-important-configuration-here)
     :bind ("C-x L". vkill-and-helm-occur)
     :init (defunvkill-and-helm-occur ()
     (interactive)
     (vkill)
     (call-interactively #'helm-occur))
     :config (setq vkill-show-all-processes t))

    如果加载Emacs,并且等待空闲计时器触发,则为事件序列:

    
    :init :idle <load> :config
    
    
    
    

    但是,如果我加载Emacs并立即键入cx,而不等待空闲计时器,则这是事件序列:

    
    :init <load> :config :idle
    
    
    
    

    用户可以在他们的空闲处使用featurep来测试这种情况,但是我想避免这种情况。

    :defer 现在延迟接受可选的整数参数

    :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 除以下内容以外的所有内容:disabled

    :preface可用于建立函数和变量定义1使字节编译器满意(它不会抱怨那些定义是未知的函数,因为它们在一个保护块中)而2允许你定义可在:if测试中使用的代码。

    请注意,:preface中指定的任何内容都是在加载时和字节编译时进行评估的,因此应该避免在前序中有任何副作用。

    :functions,用于将函数声明为字节编译器,

    :defines对变量执行的操作,:functions用于函数。

    运行时不再需要use-package.el

    这意味着你应该在Emacs的顶部放置以下内容,以进一步减少加载时间:

    (eval-when-compile (require 'use-package))
    (require 'diminish) ;; if you use :diminish(require 'bind-key) ;; if you use any :bind variant
    讨论
    Fansisi profile image