0

I’m dipping my toes into reading news with Gnus. I’d like to continue using the same editor as before for composing posts, so I put these forms in my gnus.el:

(setq message-tab-body-function 'edit-in-vim)

(defun edit-in-vim ()
  (let ((filename (make-temp-file "gnus_vim_" nil ".usenet" (buffer-string))))
    (call-process "/home/bdesham/.nix-profile/bin/vim" nil nil nil filename)
    (insert-file-contents filename nil nil nil t)))

My intention is to do the following:

  1. Write the current buffer into a new temporary file.
  2. Run Vim to edit this temporary file. Wait for Vim to exit.
  3. Replace the current buffer’s contents with the file’s contents.

Step 1 works fine—when I hit Tab in a message buffer, I see

Wrote /tmp/gnus_vim_BE6h2K.usenet

at the bottom of the screen. At that point, though, Emacs hangs and won’t do anything until I type C-g. If I run ps during this hang, it doesn’t look like a new Vim process was created.

Am I using call-process wrong? Or is it not intended to be used to call programs that have their own terminal UI? How can I get this kind of external-editor functionality?

  • I've never used it, but maybe calling suspend-tty would help? Alternatively, I hear evil-mode is supposed to be a pretty good implementation of vim. – npostavs Sep 7 at 21:28
  • @npostavs That is a great suggestion. It led me to suspend-emacs, which I turned into a solution that works exactly the way I want (see my updated answer). Thank you! – bdesham Sep 8 at 1:27
1

It seems like the problem is that both Emacs and Vim want to control the entire screen. If I edit the function in the question to use date or a utility like that instead of Vim, there is no hang and the output replaces the buffer contents in the way I want.

Fortunately, Emacs has a function called suspend-emacs which can be used to suspend Emacs, run some other command, and then (using the fg command built in to Unix shells) resume Emacs right where it left off. My full solution is

(setq message-tab-body-function 'edit-in-vim)

(defun edit-in-vim ()
  (interactive "F")
  (let ((filename (make-temp-file "gnus_vim_" nil ".usenet")))
    (progn
      (save-restriction
        (widen)
        (write-region (point-min) (point-max) filename))
      (suspend-emacs (format "vim %s; fg"
                             (shell-quote-argument filename)))
      (erase-buffer)
      (insert-file-contents filename)
      (delete-file filename))))

This will probably only work under Unix (including e.g. macOS and Linux) due to the assumption that the shell has a fg command. Also note that when Vim exits, Emacs will immediately start again and continue running this function, even if you made Vim quit with a nonzero status code.

Thanks to npostavs for pointing me to the suspend-tty function, which is related to suspend-emacs!

  • 'The use of (format "'%s'" filename) to quote the filename isn’t ideal' - You should use (format "%s" (shell-quote-argument filename)) instead. Also, you can use (delete-file filename) instead of (call-process "rm" nil nil nil filename). – npostavs Sep 8 at 12:56
  • @npostavs I’ve incorporated those suggestions into the answer. Thanks again! – bdesham Sep 8 at 16:35

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.