4

I want to call the function markdown-back-to-heading, which is native in Markdown mode in Emacs. I understand that interactive turns non-interactive functions interactive, or formally 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).

I tried:

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

This throws an error: Wrong type argument: commandp, markdown-back-to-heading.

So I wrapped it with an interactive function, and it works:

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

Is there a better way to turn the native function into an interactive command?

miguelmorin
  • 5,025
  • 4
  • 29
  • 64
  • 1
    What you did is right: *write your own command, by adding an `interactive` spec.* What kind of `interactive` spec you use depends on the interactive behavior you want. (@Stefan's answer mentions using Shift selection, for example.) It it's for more than your own individual use, or if for some other reason you need to be able to use the original function interactively, then consider advising the function (as Stefan also indicated). – Drew Nov 01 '19 at 22:32

3 Answers3

3

You can alternatively use the interactive-form symbol property.

For details see C-hig (elisp)Using Interactive

Here's a simple example:

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

The more complex version that I actually use is:

(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))))
phils
  • 71,335
  • 11
  • 153
  • 198
  • Nice one, and good example of using `kill-process` interactively. Is the result of your first example the same as `advice-add`? – miguelmorin Nov 03 '19 at 10:36
  • The *practical* effect is extremely similar to Stefan's example of `:before` advice; but they're achieving it in different ways. If you used both, the symbol property has precedence (just as it does in the case where the *original* function declares itself as interactive). – phils Nov 03 '19 at 11:29
2

If want to make markdown-back-to-heading interactive, you have a few different good options:

  • file a bug report to get upstream to make it so. Including a patch along with the bug-report can help speed up the process.
  • use an advice such as:

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

If instead you want to improve the interactivity of a function, e.g. if you want to support 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.

miguelmorin
  • 5,025
  • 4
  • 29
  • 64
Stefan
  • 27,908
  • 4
  • 53
  • 82
0

Following @Stefan's suggestion, I filed a Github issue and submitted a patch, which adds the line (interactive "P") in the source code:

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

and now I can keybind it with

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

I had installed markdown-mode from MELPA, so this change required uninstalling the package, then these steps from the repo README. I forked the repo and cloned the repo locally:

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

and added these lines to Emacs initialization:

(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)
miguelmorin
  • 5,025
  • 4
  • 29
  • 64