3

I use message-mode to compose and send email. Rather than having to navigate manually through the fields, I would like to able to TAB from the To: field to the Subject: field, then TAB again to bring the point to the message body.

UPDATE. I got this partially working:

(defun message-mode-next-field ()
  (interactive)
  (next-line)
  (end-of-line)) 

(setq message-tab-body-function #'message-mode-next-field)

It works up to a point, but for some reason it gets stuck at the end of the From: line and won't advance further to the message body.

1

Troubleshooting

for some reason it gets stuck at the end of the From: line and won't advance further to the message body

The docstrings of user option message-tab-body-function:

Function to execute when ‘message-tab’ (TAB) is executed in the body.
If nil, the function bound in ‘text-mode-map’ or ‘global-map’ is executed.

and command message-tab:

Complete names according to ‘message-completion-alist’.
Execute function specified by ‘message-tab-body-function’ when
not in those headers.  If that variable is nil, indent with the
regular text mode tabbing command.

go some way towards explaining why your command message-mode-next-field might get stuck.

As the name of message-tab-body-function suggests, the function assigned to it is not necessarily intended to work or be called while point is within the headers, as opposed to the body, of the message.

In fact, inspecting the command's source

(defun message-tab ()
  "Complete names according to `message-completion-alist'.
Execute function specified by `message-tab-body-function' when
not in those headers.  If that variable is nil, indent with the
regular text mode tabbing command."
  (interactive)
  (cond
   ((let ((completion-fail-discreetly t))
      (completion-at-point))
    ;; Completion was performed; nothing else to do.
    nil)
   (message-tab-body-function (funcall message-tab-body-function))
   (t (funcall (or (lookup-key text-mode-map "\t")
                   (lookup-key global-map "\t")
                   'indent-relative)))))

reveals that message-tab-body-function is only called when completion of header field values fails.

Emacs 25+ solution

Here are two sample commands for jumping back and forth between "fields" of interest, such as header values and the start of each of the message body and signature:

(autoload 'mail-hist-forward-header "mail-hist")
(autoload 'mail-text-start          "sendmail")

(defun my-message-signature-start ()
  "Return value of point at start of message signature."
  (save-mark-and-excursion
    (message-goto-signature)
    (point)))

(defun my-message-field-forward ()
  "Move point to next \"field\" in a `message-mode' buffer.
With each invocation, point is moved to the next field of
interest amongst header values, message body and message
signature, in that order."
  (interactive)
  (cond ((message-point-in-header-p)
         (unless (mail-hist-forward-header 1)
           (call-interactively #'message-goto-body)))
        ((>= (point) (my-message-signature-start))
         (message "No further field"))
        ((message-in-body-p)
         (message-goto-signature))
        (t ; Probably on `mail-header-separator' line
         (call-interactively #'message-goto-body))))

(defun my-message-field-backward ()
  "Like `my-message-field-forward', but in opposite direction."
  (interactive)
  (cond ((or (message-point-in-header-p)
             (<= (point) (mail-text-start)))
         (unless (mail-hist-forward-header
                  (if (message-point-in-header-p) -1 0))
           (message "No further field")))
        ((<= (point) (my-message-signature-start))
         (call-interactively #'message-goto-body))
        (t ; Beyond start of signature
         (message-goto-signature))))

You can bind them to whichever keys you like, but assuming you want the tab key to jump forward and shift-tab to jump backward, you can write

(with-eval-after-load 'message
  (define-key message-mode-map "\t" #'my-message-field-forward)
  (dolist (key '(backtab S-tab S-iso-lefttab))
    (define-key message-mode-map (vector key) #'my-message-field-backward)))

The dolist form above handles all three varieties of shift-tab found in the wild, just in case. If you know which one your system uses, you can, of course, target that one specifically, or use a completely different binding altogether.

Caveats

Emacs includes a myriad of libraries for parsing, composing, encoding, etc. messages, with lisp/gnus/message.el (a.k.a. message-mode) being just one of them. Unfortunately, none of them are complete or consistent with one another. In other words, for many useful operations, each library reinvents the wheel, and in doing so often creates its own edge cases and bugs. Even libraries aiming to provide a common interface for all other libraries to use are incomplete or remain unused.

I say this as a disclaimer for the sample code above, which resorts to using two functions external to message-mode: mail-hist-forward-header from lisp/mail/mail-hist.el and mail-text-start from lisp/mail/sendmail.el.

A message-mode purist could, in fact, replace a call to mail-text-start with

(save-mark-and-excursion
  (message-goto-body)
  (point))

or, since Emacs 27.1, simply

(save-excursion
  (message-goto-body))

for free.

mail-hist-forward-header, on the other hand, has no equal in any other library which I am aware of. In addition, the operation it performs is amenable to a relatively high number of edge cases, e.g. related to unexpected starting position or whitespace.

In general, however, and assuming your messages are well-formed and not too exotic, the provided code should work reasonably well.

Emacs 24 compatibility

The code above uses the function save-mark-and-excursion, which was first introduced in Emacs 25. In versions of Emacs prior to that, the function can be replaced with save-excursion. In order to simultaneously support Emacs 25 and earlier versions, you can replace save-mark-and-excursion with your own compatibility function, e.g. my-save-mark-and-excursion, which can be defined as

(defalias 'my-save-mark-and-excursion
  (if (fboundp 'save-mark-and-excursion)
      #'save-mark-and-excursion
    #'save-excursion)
  "Backport `save-mark-and-excursion' to Emacsen before 25.1.")

Emacs 23 compatibility

In addition to the Emacs 24 workaround, Emacs 23 further lacks a definition for with-eval-after-load. The provided key binding code can thus be written as

(eval-after-load 'message
  (lambda ()
    (define-key message-mode-map "\t" #'my-message-field-forward)
    (dolist (key '(backtab S-tab S-iso-lefttab))
      (define-key message-mode-map (vector key) #'my-message-field-backward))))

instead.

7
  • Interesting. But I get Symbol’s function definition is void: message-goto-body-1 – incandescentman Jul 31 '17 at 22:36
  • @incandescentman Which version of Emacs are you running? I explain in my answer that message-goto-body-1 is an Emacs 25 feature and that you can substitute (message-goto-body-1) with (message-goto-body) (point) in the provided code for Emacs versions 24 or older. – Basil Jul 31 '17 at 23:10
  • Emacs version 25.1.1 – incandescentman Jul 31 '17 at 23:13
  • @incandescentman Ah, I see now the function was introduced in a slightly more recent version of Emacs 25, specifically in January 2017. I will edit my answer accordingly shortly. Sorry about the confusion. – Basil Jul 31 '17 at 23:19
  • 1
    @incandescentman I see now that command message-tab has also been modified since your version of Emacs, where it deliberately forces completion to return a non-nil value so as to avoid falling back on message-tab-body-function. I do not have a solution for your desired behaviour at the moment. Please consider starting a new question for that and I will have another look later this week. – Basil Jul 31 '17 at 23:36
-2

C-c C-f C-s will move your cursor to the Subject: header.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.