作为一个(被迫)在windows下使用Emacs的人, 我常常为emacs在windows下的龟速启动感到头大.
虽然emacs27的pdumper算是emacs启动速度的终极方案, 但是pdumper版本现在仍然有无法使用profiler的bug, 而且我也不是一个喜欢尝鲜的人. 受制于工作与学习原因, 换系统是不可能的, 因此, 剩下的方案就只有字节码编译配置和延迟加载了.
然而事情并不可能会如想象中那么简单, 有的包比如隔壁老王的aweshell, 加载的步骤无非就一句 (require 'aweshell)
. 这种情况下是没有办法通过正常的autoload系统来处理的, 而且aweshell必须在eshell运行前完成加载, 因此也不能简单的向 eshell-load-hook
添加钩子函数(这个钩子里的内容会在eshell完成加载后才运行).
再比如evil的一系列包, 最明显的莫过于evil-nerd-commenter. 陈斌为了保证没有evil的用户也能正常使用, 默认的情况下是不会加载 evilnc-comment-operator
的, 然而陈斌默认的按键设置于我来说又及其的感到不适. 只能手动 (require 'evilnc-operator)
自行绑定, 这种情况, 直接使用autoload系统也没有办法正常懒加载.
针对以上的情况, 最好的解决方案就是为函数/钩子 创建一个临时的函数(姑且叫做temp-loader
)执行加载任务, 让这个函数/钩子第一次被触发的时侯,temp-loader
也被触发进行配置, 然后temp-loader
该自行销毁, 被GC回收. 为了方便, 我创建了一个use-pacakge
的 :thook
关键字来统一处理这种情况.
使用方法
(defun cm/lazyload-aweshell ()
(require 'aweshell))
;; 直接hook函数
(use-pacakge eshell
:ensure nil
:thook (eshell . cm/lazyload-aweshell))
;; 也可以把语句包在一个列表里, 会自动用一个`progn'包裹
(use-pacakge eshell
:ensure nil
:thook (eshell . ((require 'aweshell))))
(defun cm/test-hook ()
(message "temporary message!")
(use-package evil
:thook (evil-insert-state-entry-hook . cm/test-hook))
;; 也可以混在一起写
(use-pacakge eshell
:ensure nil
:thook ((after-init-hook eshell ielm evil-insert-state-entry-hook) . ((message "Test message"))))
注意: :thook
无法支持:hook
那种把after-init-hook
缩写成after-init
的做法, 原因很简单, 如果同时有函数xxx-mode
和钩子xxx-mode-hook
那不就乱套了?
代码
(eval-when-compile
(require 'use-package)
(defun use-package-normalize/:thook (name keyword args)
(use-package-as-one (symbol-name keyword) args
(lambda (label arg)
(unless (or (use-package-non-nil-symbolp arg) (consp arg))
(use-package-error
(concat label " a <symbol/function> "
"or (<symbol/functions or list of symbols/functions> . <symbol or function>) "
"or list of these")))
(use-package-normalize-pairs
(lambda (k)
(or (use-package-non-nil-symbolp k)
(and k (let ((every t))
(while (and every k)
(if (and (consp k)
(use-package-non-nil-symbolp (car k)))
(setq k (cdr k))
(setq every nil)))
every))))
(lambda (v)
(or (listp v)
(use-package-recognize-function v)))
name label arg))))
(defalias 'use-package-autoloads/:thook 'use-package-autoloads-mode)
(defvar cm/transient-hook-count 0)
(defun use-package-handler/:thook (name _keyword args rest state)
(use-package-concat
(use-package-process-keywords name rest state)
(cl-mapcan (lambda (def)
(let ((syms (car def))
(forms (cdr def)))
(when forms
(mapcar
(lambda (sym)
(let* ((fn (intern
(format "cm/thook-fn-name-%d" cm/transient-hook-count)))
(add-clause (cond
((functionp sym)
`(advice-add #',sym :before #',fn))
((symbolp sym)
`(add-hook ',sym #',fn))))
(self-remove-clause (list (cond
((functionp sym)
`(advice-remove #',sym #',fn))
((symbolp sym)
`(remove-hook ',sym #',fn)))
`(unintern ',fn nil))))
(cl-incf cm/transient-hook-count 1)
`(progn
(fset ',fn (lambda (&rest _)
,(if (functionp forms)
`(funcall #',forms)
(cons #'progn
forms))
,@self-remove-clause))
,add-clause)))
(if (use-package-non-nil-symbolp syms)
(list syms)
syms)))))
(use-package-normalize-commands args))))
(add-to-list 'use-package-keywords :thook))