This article describes my first attempt at running shells inside Emacs, which didn’t work out. I tried again recently, and I think I’m going to stick with it, so this is preserved for posterity only!
When I’m working, I pretty much live inside Emacs. It’s my IDE, debugger, text editor, email composer, spreadsheet, file manager, and all-around scratch pad. The only other app that’s as ubiquitous in my work routine is my shell, tcsh inside rxvt.
I’ve customized the hell out of both Emacs and tcsh to make them work the way I want. I have Emacs whipped into shape, and tcsh too, mostly, except for one thing: copying without the mouse.
I hate the mouse. It’s good for some things, but not my work. Switching between the mouse and the keyboard wreaks havoc on my muscle memory and fine-motor flow. Thanks to Emacs, Ion, Pine, Gaim, and Firefox‘s blessed Find As You Type, I can do pretty much everything with the keyboard. The only thing I can’t do with the keyboard is copy text from a terminal. And that irks me.
A voice in the back of my head had been nagging me about this for a while. Ryan, you can fix this, it said, and you know how. It was right. I didn’t want to admit it, but I did know how, and it did too. Why don’t you try running your shells inside Emacs?
I had a laundry list of reasons. Half of my
.cshrc would be usless. I’d forgo
acoc‘s wonderful colorized command
would never do history management as well as native tcsh. And I wouldn’t get
that cool transparent background. :P
The voice in the back of my head didn’t go away, though. Every time I reached for the mouse to copy something out of a terminal, it piped up. You could fix that, you know. Sigh. I knew.
Finally, it got the better of me. I wrote try emacs shell mode on my todo
list, and a few days later, I tried it.
M-x shell was all it took, and it worked
surprisingly well. What’s more, most of my reasons for avoiding it were
unfounded. Here’s what worked well:
Copying with the keyboard. This goes without saying. What’s more, all of my Emacs muscle memory for navigating and searching now applied to the entire contents of my shell!
Session management. All of my shells were at my fingertips, regardless of which Emacs frame I was currently in. No more context switching into a different Ion frame on a different virtual desktop just to check on a command. Even better, I could continue using my work shells at home, over VPN, just by connecting to my work Emacs!
Tcsh integration. Emacs was surprisingly sociable with tcsh. It obeyed my
.cshrc, read and wrote its command history to
~/.history, and generally played nice.
ANSI color codes. Wonder of wonders, Emacs could even display color codes properly. Just do
Find-file-at-point. This handy little elisp function opens, in a new Emacs buffer, the filename that your cursor is currently on. After an
ls, if I wanted to look at a file, I just moved up to it and hit
find-file-at-point), and Emacs would open the file. Slick.
It wasn’t perfect, though. Here’s what didn’t work well:
Not a terminal. I gained keyboard navigation at the expense of having a true terminal emulator. I couldn’t run any app that needed anything more than a dumb terminal – Pine, BitTorrent, less, and others. I could use Term mode, which provides a full terminal, but that defeats the purpose of running inside emacs.
History. As I suspected, command history wasn’t quite as good as stock tcsh. It worked ok for history in the current shell’s session, but I didn’t have access to history from previous sessions.
Remote completion. Emacs could tab-complete local files and directories, but as soon as I sshed to another machine, it was helpless. OK, that’s not quite true…I tried TRAMP, and it worked, but it was unbearably, unusably slow. Sigh.
Case-sensitive completion. File and directory tab completion is case sensitive. It doesn’t obey the completion-ignore-case variable. Boo.
Slow color code parsing. My joy at finding
ansi-color-for-comint-mode-onturned to horror as soon as I did an
ls -lin a large directory. Emacs’ color code parsing is dog slow. I could use
ansi-color-for-comint-mode-filterto just ignore the color codes, but I couldn’t bear to go without them.
So, you might ask, what was the verdict? The jury’s still out. I’m currently wrestling GNU screen into submission, since it also allows keyboard navigation and copy/paste over shell contents. So far, I’ve synchronized its paste buffer and the X selection, and I’ve Emacs-ified its copy-scrollback mode. Stay tuned for more!
Here’s what I added to my .emacs to make shell-mode behave the way I wanted:
(custom-set-variables '(comint-scroll-to-bottom-on-input t) ; always insert at the bottom '(comint-scroll-to-bottom-on-output t) ; always add output at the bottom '(comint-scroll-show-maximum-output t) ; scroll to show max possible output '(comint-completion-autolist t) ; show completion list when ambiguous '(comint-input-ignoredups t) ; no duplicates in command history '(comint-completion-addsuffix t) ; insert space/slash after file completion ) ; interpret and use ansi color codes in shell output windows (ansi-color-for-comint-mode-on) ; make completion buffers disappear after 3 seconds. (add-hook 'completion-setup-hook (lambda () (run-at-time 3 nil (lambda () (delete-windows-on "*Completions*"))))) ;; run a few shells. (shell "*shell5*") (shell "*shell6*") (shell "*shell7*") ; C-5, 6, 7 to switch to shells (global-set-key [(control \5)] (lambda () (interactive) (switch-to-buffer "*shell5*"))) (global-set-key [(control \6)] (lambda () (interactive) (switch-to-buffer "*shell6*"))) (global-set-key [(control \7)] (lambda () (interactive) (switch-to-buffer "*shell7*")))