如何在 Emacs-LISP 中将一个(非交互)函数变成一个命令(交互函数)?

How to turn a (non-interactive) function into a command (interactive function) in Emacs-LISP?

我想调用函数 markdown-back-to-heading,它在 Emacs 的 Markdown 模式下是原生的。我知道 interactive 将非交互函数变成交互函数,或者正式地 functions into commands:

Special Form: interactive arg-descriptor

This special form declares that a function is a command, and that it may therefore be called interactively (via M-x or by entering a key sequence bound to it).

我试过了:

(define-key markdown-mode-map (kbd "C-c C-h") 'markdown-back-to-heading)

这会引发错误:Wrong type argument: commandp, markdown-back-to-heading

所以我用交互功能包装了它,它起作用了:

(defun my-markdown-back-to-heading ()
  "Wrap function to be called interactively."
  (interactive)
  (markdown-back-to-heading))

(define-key markdown-mode-map (kbd "C-c C-h") 'my-markdown-back-to-heading)

有没有更好的方法将原生函数变成交互式命令?

如果想markdown-back-to-heading互动,你有几个不同的好选择:

  • 提交一个错误报告,让上游做到这一点。在错误报告中包含一个补丁可以帮助加快这个过程。
  • 使用如下建议:

    (advice-add 'markdown-back-to-heading :before
                (lambda () (interactive "^") nil))
    

如果您想提高功能的交互性,例如如果你想支持 shift-selection, you can add the interactive code ^ with (interactive "^") instead of (interactive) so that Emacs knows this is a navigation command (and hence if you use it with a shifted-binding it will select the corresponding text). Here is a manual page with the list of interactive codes, and other options for interactivity at the manual page you mentioned.

按照@Stefan 的建议,我filed a Github issue and submitted a patch,在源代码中添加行(interactive "P")

(defun markdown-back-to-heading (&optional invisible-ok)
  "Move to previous heading line, or beg of this line if it's a heading.
Only visible heading lines are considered, unless INVISIBLE-OK is non-nil."
  (interactive "P")
  (markdown-move-heading-common #'outline-back-to-heading invisible-ok))

现在我可以用

键绑定它
(define-key markdown-mode-map (kbd "C-c C-h") 'markdown-back-to-heading)

我已经从 MELPA 安装了 markdown-mode,因此需要进行此更改 uninstalling the package, then these steps from the repo README. I forked the repo 并在本地克隆存储库:

git clone git@github.com:miguelmorin/markdown-mode

并将这些行添加到 Emacs 初始化中:

(add-to-list 'load-path (expand-file-name "~/code/markdown-mode"))

(autoload 'markdown-mode "markdown-mode"
   "Major mode for editing Markdown files" t)
(add-to-list 'auto-mode-alist '("\.markdown\'" . markdown-mode))
(add-to-list 'auto-mode-alist '("\.md\'" . markdown-mode))

(autoload 'gfm-mode "markdown-mode"
   "Major mode for editing GitHub Flavored Markdown files" t)
(add-to-list 'auto-mode-alist '("README\.md\'" . gfm-mode))

(require 'markdown-mode)

您也可以使用 interactive-form 符号 属性。

详情见C-hig (elisp)Using Interactive

这是一个简单的例子:

;; Enable M-x kill-process (to kill the current buffer's process).
;; (This is not normally a command, but it is useful as one.)
(put 'kill-process 'interactive-form '(interactive))

我实际使用的比较复杂的版本是:

(put 'kill-process 'interactive-form
     '(interactive
       (let ((proc (get-buffer-process (current-buffer))))
         (if (process-live-p proc)
             (unless (yes-or-no-p (format "Kill %S? " proc))
               (error "Process not killed"))
           (error (format "Buffer %s has no process" (buffer-name))))
         (list proc))))