loaddefs.el的自动生成使用被称为autoload cookie的提示行:
The following example shows how ‘doctor’ is prepared for autoloading
with a magic comment:
;;;###autoload
(defun doctor ()
"Switch to *doctor* buffer and start giving psychotherapy."
(interactive)
(switch-to-buffer "*doctor*")
(doctor-mode))
Here’s what that produces in ‘loaddefs.el’:
(autoload 'doctor "doctor" "\
Switch to *doctor* buffer and start giving psychotherapy.
\(fn)" t nil)
elisp手册里说:
If you write a function definition with an unusual macro that is not
one of the known and recognized function definition methods, use of an
ordinary magic autoload comment would copy the whole definition into
‘loaddefs.el’. That is not desirable. You can put the desired
‘autoload’ call into ‘loaddefs.el’ instead by writing this:
;;;###autoload (autoload 'foo "myfile")
(mydefunmacro foo
...)
可是这么写的话,我用宏自动产生的函数名和文档都要重新手写一遍,还不能换行。这种情况下有什么解决的办法呢?我不想重新实现loaddefs.el
autoload 的原理应该就是使用 loaddefs-generate
找到文件中的 autoload 然后统一放到另一个文件里。
是指使用宏批量生成一系列的函数吗?如果仅仅是类似你提到的 mydefunmacro
那在定义开头加上魔法注释就行。如果一个宏生成了多个函数我也不知道怎么做。
在toc-glue.el里,我一开始实现了几个不是Interactive的函数,然后想要把它们包装成具有相同行为(比如按下C-u
的时候prompt for buffer)的interactive函数作为交互接口,一个一个套wrapper太费事了,我就写了一个宏,如下:
(defmacro toc-glue:def-interactive (f &optional binds body)
"Define an interactive function based on given function F.
By default, the function simply pretty-print to current buffer.
With a prefixed argument, it prompts for a buffer to output.
plist BINDS will be passed to `interactive'.
If procedure BODY is given, it will be invoked before print the
output of F."
(let ((arglist (mapcar (lambda (desc) (car desc)) binds)))
`(defun ,(intern (concat "toc-glue:" (symbol-name f)))
;; arglist
(,@arglist &optional sel-buf-p)
;; docstring
,(concat
;; cut the first line of original function
(let ((str (documentation f)))
(substring str 0 (or (string-match "\n" str) (length str))))
"\n\nThis function is produced by `toc-glue:def-interactive'\n"
"With a universal prefix, it prompts for a buffer to output.")
(interactive (list
,@(mapcar (lambda (desc) (cadr desc)) binds)
current-prefix-arg))
(let ((buf (if sel-buf-p
(read-buffer "Output to buffer: ")
(buffer-name)))
(output (,f ,@arglist)))
(when sel-buf-p ,body)
(pp output (get-buffer buf)))
)))
(defmacro toc-glue:sexp-interact (f &optional binds)
"An alias of `toc-glue:def-interactive' that specialized for sexp.
The first argument of F is always binded to `sexp-at-point'.
By default, the function replaces last sexp with output of F,
plist BINDS will be passed to `interactive'."
(macroexpand
`(toc-glue:def-interactive ,f
((sexp (sexp-at-point)) ,@binds)
(backward-kill-sexp))))
;; example
(toc-glue:sexp-interact lisp-to-toc)
;; macroexpand =>
(defalias 'toc-glue:lisp-to-toc
#'(lambda (sexp &optional sel-buf-p)
"Convert simple outline LST to a ToC.\n\nThis function is produced by `toc-glue:def-interactive'\nWith a universal prefix, it prompts for a buffer to output."
(interactive (list (sexp-at-point) current-prefix-arg))
(let
((buf
(if sel-buf-p (read-buffer "Output to buffer: ")
(buffer-name)))
(output (lisp-to-toc sexp)))
(when sel-buf-p (backward-kill-sexp))
(pp output (get-buffer buf)))))
使用cl-lib
或者其它的包是否能为interactive提供更方便的包装器?
interactive函数自己手写autoload的话还要把文档一起附上,因为autoload cookie不能换行,所以看起来非常丑。
因为这个宏会访问函数内部数据,所以需要在生成autoload的时候对该函数求值才能生成完整的autoload,我不知道loaddefs目前是否有这个机制。
为啥你手写autoload要带函数签名呢?自己的手写autoload带个包名和宏生成的函数名不就行了?
因为它是interactive函数,interactive必须填t了,所以函数签名即使填nil也是必填项。
我还是希望能在marginalia-mode下能看到它的签名的。
也就是说你想要让魔法注释也获取宏展开后的函数的文档字符串吗?如果是那样的话应该做不到,因为非 defun
的 ;;;###autoload
本质上只是把注释后面的东西添加到 autoload 文件中,比如 compat.el 中的:
得到的结果如下:
1 个赞
直接看 loaddefs-generate--make-autoload
定义,
哪些声明能生成 autoload 是硬编码在这个 function 里的
我觉得你直接写个宏替代 interactive,而不是替代 defun,应该就行了
(defmacro my-interactive ()
`(interactive))
my-interactive
(defun foo ()
(my-interactive)
(message "gg"))
foo
C-h f foo
:
foo is an interactive Lisp function.
1 个赞
我想在这里做点tweak就可以,引入一个custom变量控制哪些宏应该被递归展开。虽然在我的包里入侵这个函数似乎不是很合理。也许我可以请求向上游提交一个修改?
;;++ line 200
;; For complex cases, try again on the macro-expansion.
((and (memq car '(easy-mmode-define-global-mode define-global-minor-mode
define-globalized-minor-mode defun defmacro
easy-mmode-define-minor-mode define-minor-mode
define-inline cl-defun cl-defmacro cl-defgeneric
cl-defstruct pcase-defmacro iter-defun cl-iter-defun))
;; ...
你可以问问,不过我估计这个要 review 很久才会过
或者你考虑下这种方式:
新建一个文件叫做xxx-mode-config,然后你用一个 dolist 或者你想要什么都行的方式依次的 call autoload 宏自动生成的函数名和对应的文档(既然你是用宏生成的函数,那文档应该也是可以自动生成的)。然后在你的主文件 xxx-mode 里 加一个 autoload 去 require 你这个 xxx-mode-config。
之前见过有一些包里有维护这类config文件,用自动化的工作流替代代码抽象也是一种思路,不过属于旁路了。
最近有在探索这类旁路,也许明年可以做点好玩的东西出来。