使用after-init-hook的理由是什么?

purcell的配置中有不少的地方用到了after-init-hook,文档说这个hook是在init file和所有package加载完成后触发。

我的猜想是把需要内置package的代码放在这个hook里,这样可以确保能找到所需的函数?但是好像不用它也很难出问题。

延迟加载。

毕竟把所有的东西都放在init过程里是会减慢启动速度的……

init之后立刻加载,不也会影响“启动”速度么……

但是此时init过程已经完成,你也能看到主题界面,也不至于干等在那里啊……

是的, 其实用after-init-hook的延迟加载时是一个相当自欺欺人的事情, 现在我用这个hook加载的, 一般是evil-mode这种十万火急的mode以及keyfreqsuper-save这种与编辑文本如影随形悄无声息的mode.

对于ivy, 我参考doom-emacs的方式, 用run-with-idle-timer加载, 同时在pre-command-hook挂临时钩子. 一旦有命令运行, 就立刻强制(require 'ivy) 加载. 这样感觉上会比挂在after-init-hook好不少. company就直接用run-with-idle-timer就完事了.

其实最让我烦躁的就是lisp-interaction-mode, 这个模式名不副实. 想象一下如果我要写一段代码, 为什么我不用elisp-mode, 如果要交互式运行, 为什么不用ielm-mode呢? 最恶心的是这个小东西是开始时随着*scratch*被强制加载的, 还属于prog-mode. 导致一票诸如aggressive-indent-mode lispy hs-minor-mode 也被这个家伙加载起来了. 事实上我们用scratch buffer并不会立马用到这么多高级功能. 它一开emacs就加载反而影响我的体验.

3 个赞

设置initial-major-mode成fundamental没有用吗

1 个赞

似乎研究得很透彻啊。

对我而言,after-init-hook 可以将一些不是立即就用的东西都放到后面,分散启动加载时 CPU 的压力。你提到的run-with-idle-timerpre-command-hook是个好想法。不知道你的 Emacs 配置启动大概需要多久,可以分享一下。

原来这个变量叫这个名字, 可找死我了… :sweat:

我自己的配置还没有完善, 所以现在讨论时间似乎意义不大.

https://github.com/cireu/emacs.d 就目前的这个状况来看, 我用win10和ssd, 大概6s就能启动到看到光标的程度, emacs-init-time报时3.0s. 以后我要加上python和js ts以及commonlisp的功能, 希望可以控制在15s以内.

Centaur Emacs已经达到了该目标。可以借鉴你这个思路再优化下。不过 Windows 上启动确实太慢了,Linux、MAC 下启动飞快,基本2s 内完事,加上恢复现场也就5-6秒(如果 buffer 不算太多的话)。

给我的scratch做下广告

开了个issue,purcell回复了,不过我没怎么看懂。

也不能说自欺欺人吧……就像iOS的过渡动画也不能算自欺欺人一样。iOS过渡动画确实加快了“感知上的”响应速度。

同样,把一些庞大而且和init过程无关的modes放在after-init-hook里执行也是一样的。虽然不能加快startup过程的速度,但是能加快init过程的速度啊。而init完成后你就能看到color-theme和cursor了,不至于干等在那里不知道emacs要干嘛。

至于lisp-interation-mode……我能想到的用处就是……测试一大段代码的效果……

如有理解错误的地方,请指正。

分散 CPU 压力,UI 相关的先启动,这样感知上还是有所区别的。

看起来不错,目前用的persistent-scratch.

我想请教一下……啥东西最好直接启动,而不是放在after-init-hook里?

比如load theme,否则启动过程中一个大白框。

1 个赞

我之前也思考过这个问题,当时写了一则笔记,在这里分享给大家。如果有说的不对的还请指正:-)


为什么需要 after-init-hook 这个钩子?我觉得这和 Emacs Lisp 变量的作用域有关系。当你运行一个函数的时候,你运行的时间不同,会得到不同的结果。init 之后运行,就允许你在 init 之前,改变 after-init-hook 这个钩子中将要运行的函数的行为

下面是一个典型的例子:

     (defcustom znh/conda-env "env"
       "Conda dir"
       :type 'string
       :group 'znh)

     (setenv "WORKON_HOME" znh/conda-env)

这段代码定义了一个变量 znh/conda-env。使用 defcustom 来定义是为了之后可以很方便地通过 customize-variable 来修改这个变量。因为这个路径在不同的电脑上会有不同,我们肯定不希望切换一台电脑就修改一次代码,特别在我有两台电脑的情况下。但是上面代码却是有问题的,因为如果 custom.el 在上面这段代码之后 load,那么通过 customize-variable 做的修改就是无效的。为了保证修改的成功,我们让 (setenv "WORKON_HOME" znh/conda-env) 这段代码在 init 完成之后运行。代码如下:

     (defcustom znh/conda-env "env"
       "Conda dir"
       :type 'string
       :group 'znh)

     (add-hook 'after-init-hook
               (lambda ()
                 (setenv "WORKON_HOME" znh/conda-env)))

简而言之,我觉得 after-init-hook 的存在意义之一,是为了让 custom file 中设置的一些全局变量能够生效(我想 purcell 也是这个意思)。

… but they run after some other significant things have taken place, e.g. loading of the custom file (particularly)…

1 个赞

我主要是考虑这三个方面:

1. 推迟生效

一个包何时 require,差别并不大。但是这个包里定义的各种 hookadvice 何时启用,就需要斟酌了。假如这个包是第一个加载,它的 hookadvice 可能会影响后续加载过程,这是不是你期望的,如果不是,那么放在 after-init-hook 比较好。

比如我用到了 paredit,这个包有时侯会出现 File mode specification error: (user-error Unmatched bracket or quote) 错误(在 emacswiki 也有提到),简单的解决方法就是延迟一秒加载,所以我就定义了一个 advice,由于我用的是 (define-advice enable-paredit-mode ...),所以这个修改在 require 的时侯就生效了。

但是我一直没觉察到有问题,直到在一台新的主机上,首次运行,下载编译包的时侯出现大量 #<killend buffer> 错误,就是由于前面那个 advice 在这些临时 buffer 中都生效了,而这些临时 buffer 存在时间极短。如果改为在 after-init-hook(advice-add 'enable-paredit-mode :around ...) 就可避免。

2. 解决冲突

可分为两种情况:

  1. 包依赖

    类似 (with-eval-after-load 'xxx ...) 的作用

  2. 快捷键

    快捷键作为稀缺资源,难免出现多个包抢一个键的情况,与其每配置一个包就纠正一次,不如统一放到后边处理。

3. 需要用到 after-init-hook 之后才确定的资源

那就更不可避免 after-init-hook 了,但是我目前还没这方面的需求。我主要是在终端下使用 emacs,有些配置必须在 tty 资源确定以后才执行,因此我对 window-setup-hook 的需求更明确:

(add-hook 'window-setup-hook 'solarized-theme--tty-setup)
1 个赞