2008-12-29

running console programs inside emacs

Emacs comes with a nice terminal emulator called ansi-term. Using M-x ansi-term, you can run shells and other terminal programs from within emacs, and the emulation is good enough even for 'graphical' console programs. For example, I am using the mutt mailer and slrn news reader with ansi-term. I could of course use some native emacs program for news and mail (e.g,, gnus), but in this case, I prefer the external ones (I'll discuss that in some later entry). However, I really like these external programs to run inside emacs - it makes my task management easier. YMMV, of course.

Anyway, to easily use these external programs, I want some handy key bindings (shortcuts), and the following behaviour:

  • if the program is not yet running, start it in a new ansi-term-buffer;
  • if the program is already running, switch to that buffer instead.
To do that, I define a functions (disclaimer: I am no elisp-expert; there are probably better ways to do this. But this works-for-me at least): [Update: thanks to Ian and get-buffer (see comments), a shorter solution, using only one function:]
(defun djcb-term-start-or-switch (prg &optional use-existing)
  "* run program PRG in a terminal buffer. If USE-EXISTING is non-nil "
  " and PRG is already running, switch to that buffer instead of starting"
  " a new instance."
  (interactive)
  (let ((bufname (concat "*" prg "*")))
    (when (not (and use-existing
                 (let ((buf (get-buffer bufname)))
                   (and buf (buffer-name (switch-to-buffer bufname))))))
      (ansi-term prg prg))))
Using this function, it's easy to define key bindings, as explained in the key bindings entry. However, we can make it even easier using a macro:
(defmacro djcb-program-shortcut (name key &optional use-existing)
  "* macro to create a key binding KEY to start some terminal program PRG; 
    if USE-EXISTING is true, try to switch to an existing buffer"
  `(global-set-key ,key 
     '(lambda()
        (interactive)
        (djcb-term-start-or-switch ,name ,use-existing))))
Using this macro, we can now define these key bindings (shortcuts), for example like this:
;; terminal programs are under Shift + Function Key
(djcb-program-shortcut "zsh"   (kbd "<S-f1>") t)  ; the ubershell
(djcb-program-shortcut "mutt"  (kbd "<S-f2>") t)  ; mail client
(djcb-program-shortcut "slrn"  (kbd "<S-f3>") t)  ; nttp client
(djcb-program-shortcut "htop"  (kbd "<S-f4>") t)  ; my processes
(djcb-program-shortcut "mc"    (kbd "<S-f5>") t)  ; midnight commander
(djcb-program-shortcut "raggle"(kbd "<S-f6>") t)  ; rss feed reader
(djcb-program-shortcut "irssi" (kbd "<S-f7>") t)  ; irc client
And so on. Now, to summarize, if I press, say, C-f5 (shift-F5), I'll jump to a buffer with the midnight commander. If it's already running, I'll switch to that one, otherwise, a new mc will be started.

Final note: you might want to customize ansi-term a bit for your needs; EmacsWiki has some tips. You also might want to turn off hl-line-mode.

4 comments:

Anonymous said...

This should be a drop-in for replacement for (djcb-switch-to-named-buffer):

(let ((buf (get-buffer name)))
(and buf (buffer-name (switch-to-buffer buf))))

As for (djcb-term-start-or-switch):

(or (and use-existing (djcb-switch-to-named-buffer (concat "*" prg "*")))
(ansi-term prg prg))

You could probably roll those both into your macro.

djcb said...

@Ian: sweet. If only I'd known about 'get-buffer'...

Anyway, I'll post an updated version; the two functions could be done like:

(defun djcb-term-start-or-switch (prg &optional use-existing)
"* run program PRG in a terminal buffer. If USE-EXISTING is non-nil "
" and PRG is already running, switch to that buffer instead of starting"
" a new instance."
(interactive)
(let ((bufname (concat "*" prg "*")))
(when (not (and use-existing
(let ((buf (get-buffer bufname)))
(and buf (buffer-name (switch-to-buffer bufname))))))
(ansi-term prg prg))))

Unknown said...

I use multiterm. It is almost a replacement for a real terminal.

Anonymous said...

Very nice, I would only suggest forgeting about mc and using dired instead.