The Tao of Emacs
I know, the title of this essay is a cliché, but the Chinese word, 道, we transcribe as tao means a way or path, as in a way of life. As such, Emacs has a definite approach to how we work.1
No, no, no, this is not a flame-war. I’m not saying the Emacs Way is objectively better, but if you decide to use Emacs, you may find your work-style improved if you incorporate the Emacs Way.
In other words, don’t fight it, embrace it.
Emacs vs. Shell-Vi
Allow me to describe the Emacs Way by contrasting it against a different approach, the Shell-Vi Way. The following diagram illustrates how one would develop and compile a program:
Note: This diagram loosely shows flow not communication or control. Don’t read too much into it.
The shell is the central focus. One begins in the shell, starts a
vi
session to edit a file, exits to return to the shell in order
to kick off gcc
, and uses the shell to execute the compiled
program or debugger. In this way, the editor must be quick to
start, since one is constantly re-starting it.
The Emacs Way replaces shell’s central position. Emacs runs commands typically done by the shell, including the compiler/interpreter, the application, and its test suite.
When people joke about Emacs being an operating system, they are not too far from the truth, for to me, an operating system is just a boot loader to get me to Emacs where I can then start doing my work.
An Example of The Way
In case you are not familiar with this approach, allow me to walk through a typical example.
Changing a File’s Permissions
Let’s say, you wish to create a new script. In a shell-oriented
system, you would begin with a command to start up an editor on
that new file. However, since Emacs is already running, we just
find-file
the new script.2
Yes, not much different, but wait…
In order to test the script, we need to change the execute bit. In
the Shell Way, one would exit the editor, an run the chmod
command from the shell. In the Emacs Way, you either:
- Type
M-!
and then the equivalentchmod
command, or - Type
M-x
and thenchmod
, hitReturn
to select your file (as the current buffer is the default value), and then the permission’s value.
With the Emacs Way, one doesn’t exit the editor simply to change the value.3
Using a REPL
Another subtle, but significant difference is how Emacs handles a REPL. For years, when talking to my C colleagues (and then my Java colleagues), a REPL seemed odd. However, dynamic languages for the JVM (like Scala, Groovy and Clojure) have made this less odd.
A REPL allows you to run an interpreter and give it commands and
expressions to evaluate. For instance, with Ruby, you would run
irb
, and then type some expressions, like this:
$ irb >> 5 + 6 => 11 >> 1 + rand(6) => 3 >> 1 + rand(6) => 6 >> def die >> 1 + rand(6) >> end => nil >> die => 3 >> die => 1 >> (1..3).collect { die } => [4, 3, 6] >> (1..3).collect { die }.reduce(:+) => 10 >> def dice(num=2) >> (1..num).collect { die }.reduce(:+) >> end => nil >> dice => 6 >> dice => 11 >> dice(5) => 19
The output of every REPL for each language is different (and in
this case, irb
doesn’t color my input… that is just the
side-effect of my export mechanism), but if you’ve never tried a
REPL, this should give you a view.
This is a great way to import and investigate a new library or try
out some experimental code. While the instant feedback is great,
the actual user interface leaves a bit to be desired. Somes REPLs
are nicer than others, for instance, bpython
. is quite slick for a
terminal-only application.
Emacs can give you the best of both a REPL and an editor.
Let’s walk through the above Ruby session, keeping in mind that one
can often to have a similar approach in many other languages.
Begin with: M-x
and inf-ruby
(I type ir
and SMEX will
auto-complete it).4
We can now type 5 + 6
and all the other expressions we did in the
above REPL. Yeah, no big whoop here. Open another buffer (C-x b
if you’re following along at home) called blah.rb
, and type:
def die 1 + rand(6) end
With the cursor anywhere in there, issue M-x ruby-send-block
(or
C-c C-b
) to send this function definition over to the
REPL. Notice a bit of gibberish, but it will end with:
=> nil
Which is the results of evaluating a function definition in Ruby.
Pop into that original ruby buffer, and type die
and return
and you’ll be treated with random values from rolling our virtual die.
Better yet, back in your Ruby buffer, type die
on a line by
itself and hit C-x C-e
(or M-x ruby-send-last-sexp
), and the
die
function call is sent to the REPL and the results of the
function is displayed.
The point of this exercise is that you can type some code and either select part of it or send a full block to the REPL to be evaluated.
Think about writing unit tests, and evaluating the
expression to make sure it matches the assert
as you go along.
For this, I often use Magnar Sveen’s Expand Region project to
easily select some section of code, evaluate it (that is C-c
C-r
), and expand the region even more to validate a larger
section.
Let’s demonstrate with a slightly larger Ruby example. Add this to
your blah.rb
file:
def dice(num=2) (1..num).collect { die }.reduce(:+) end
With your cursor anywhere inside the num=2
, repeatedly hit the
C-=
key binding (whatever you have bound to the expand-region
function) until everything inside the parenthesis is selected. Then
C-c C-r
to send that expression to the Ruby REPL. This assigns a
global variable, num
to 2
, but that is fine for now.
Next, with your cursor on the opening paren in the expression: (1..num)
hit C-=
to select that expression, and again, C-c C-r
to
evaluate that expression. Yeah, it returns 1..2
which isn’t too
exciting.
Hit C-=
to add the .collect
part to our highlighted region, and
evaluate it with C-c C-r
, and you should see [1, 2]
in the
REPL. Slightly more interesting.
If you hit C-=
again, you’ll select the entire block, which is too
much, as we just want to trailing block. Since our cursor is at the
beginning, we hit C-x C-x
to put the cursor at the end of the
region, and C-8 C-f
to move forward 8 characters to add the
block, { die }
to the region. Now evaluate that. We should get
the results of rolling two die. Evaluate it again to get a
different roll.
Hit C-e
to add the reduce
statement to the region, and evaluate
that to get a number from 2 to 12. I’ve now validated to myself
that my dice rolling function is good enough, but I wouldn’t be
happy until I popped over to my test suite and started building a
unit test in a similar way.
Remote Server Files
What if the files to edit and the commands to run are located on a remote server? No problem. No, you don’t need an instance of Emacs running on the remote server…just use your local Emacs application with Tramp.
Call the normal find-file
function (C-x C-f
), but pre-pend the
host’s name, as in:
/ssh:10.52.224.67:blah.txt
Set ssh
as the default Tramp protocol in your Emacs configuration
file:
(setq tramp-default-method "ssh")
Then you can shorten the file specification to:
/10.52.224.67:blah.txt
Copy over your SSH public key to the remote system, and Tramp won’t
have to ask you for the password, since Tramp just uses ssh
and
other standard tools under the hood.
You can specify a different user account:
/bob@10.52.224.67:blah.txt
Once you’ve specified the host, tab completion works, and the list of files show up as if they were local.
At my day job, remote systems are located in a data center behind
multiple firewalls. I minimize the accessibility issues by placing
lines like the following in my ~/.ssh/config
file:
Host 10.2.3.4 # The Bastion Port 66 DynamicForward 43536 Host 10.98.19.* ProxyCommand nc -x localhost:43536 %h %p
This tells me that I can get to machines on the 10.98.19.*
subnet
through a local connection on port 43536
, when that port is first
established by connecting to 10.2.3.4
.
My day job is building private clouds, and often this form of SSH
tunneling just isn’t possible. However, you can use Tramp to jump
from a bastion to an inaccessible host by specifying a series of
jumps where each step is separated with the |
character.
For instance:
/10.98.19.229|10.0.1.85:bling.txt
This just uses the regular SSH calls under the hood to first log
into 1-.98.19.229
and then start another ssh
connection from
that system to 10.0.1.85
to get the file, bling.txt
. This is so
cool since I also can log into a system, and then tell it to use
sudo
to access an root-owned file:
/ssh:10.98.19.229|ssh:10.0.0.16|sudo:10.0.0.16:/etc/httpd/httpd.conf
In the above example, one needs to add the ssh
protocols when you
use the pipe symbol. Also, you need to specify the hostname directly
(and don’t be tempted to use localhost
), as Phil mentioned to
me.5
After opening a file, commands started from Emacs (that I mentioned
at the beginning of this essay) automatically work on the remote
machine through the same tunnels. Once I load a file this way,
I drop a bookmark (either to a register with C-x r SPC
or as a
full name using C-x r m
), and later I can jump directly to that
file without having to enter the host name.
Summary
Once again, I am not advocating that The Emacs Way is objectively
better. I am, however, cautioning new Emacs uses not to simply swap
the word, vi
for emacs
if you are using the Shell Way. Start
Emacs once and leave it running as long as your session… in my
case, that can be months until the next operating system upgrade
requires an Emacs restart.
Footnotes:
Emacs didn’t invent the Emacs Way. RMS originally borrowed the concept of shortcut keys (specifically key bindings that manipulate a document without altering the state), but this feature of Emacs clearly influenced many an IDE since.
While IDEs attempt to keep you inside the editor in a way similar to Emacs, I don’t consider extending a system with plugins (even with an open plugin architecture like that adopted by Eclipse) to compare to extending a development environment with functions (this idea was first started with Smalltalk and appropriated into Emacs soon after).
Of course, discussing historical influences can be tricky, so if you’d like to correct me, feel free to drop me a line.
I think I used vi
four or five years before I
learned it could load a second file without exiting. ;-)
Yes, vim
and other good editors can run a shell command
without requiring a re-start of the editor, however, this isn’t the
normal workflow.
While the default Emacs comes with the ability to edit Ruby
files, the REPL integration is done with the inf-ruby
package. To
install it, type M-x package-install
(tab completion or SMEX saves
you from typing it all), and type inf-ruby
at the prompt.
Please, see my complete Ruby Initialization code for Emacs for details.
Using localhost
in an sudo
in an ad-hoc multi-hop tramp
path is incorrect. While it seems to work fine, Phil informed me that
it introduces a problem.
See this StackOverflow discussion for details.
Phil wrote:
The trap with using
sudo:localhost
is very similar to the trap with usingsudo::
with remote hosts– in this case the HOST for the dynamic proxy entry becomes “localhost”, and so if you subsequently attempted to open/sudo:localhost:/path
on your local server, you are instead proxied to the remote host from the earlier multi-hop path!Or perhaps more likely than that (because on the local host you would simply use
/sudo::/path
after all), consider what happens when you use thesudo:localhost
approach for multiple remote hosts – you will shadow the proxy forroot@localhost
each time you do this, after which all but one of those remote+sudo paths would go to the wrong server!When multi-hopping, you should always use the explicit remote hostname for the
su
andsudo
methods.p.s. I got here via http://irreal.org/blog/?p=3750 where I had commented on this issue on account of your article having no comments facility, before subsequently noticing you did in fact have this contact link.