Reading Code with Emacs: Controlling what's Displayed
It's easy to learn about building software, but I spend most of my time maintaining software that already esists (which is, I understand, very typical ). Whether I like it or not, I read a lot more code than I write.
I've found some tools in Emacs that help me read code. They're not particularly "smart", but they work on any kind of text, and I've found them useful in a bunch of different ways. I hope you do too.
Fundamentals
Strong fundamentals are helpful everywhere. Before we get fancy you've got to be able to move around a file, open new buffers, search for strings or regexes.
For a review or a quick way to learn, the interactive tutorial is a
good place to start. You can start it with C-h t
(ctrl + h
followed by t
). I'd recommend paying particular attention to
commands for navigation and those dealing with mark
and region
.
Selecting Chunks of Code
A particularly useful command for reading is mark-sexp
(bound to
C-M-space
), which selects the
S-expression starting at
the cursor. This is very handy when working in Lisp, but in other
languages you can use it to select a word, string, or parenthesized
expression (e.g. an argument list).
bigFunction(x, y, mode, source, **kwargs)
bigFunction(x, y, mode, source, **kwargs)
In some language modes, it will also select a class or function definition.
def fibonacci(n):
if n <= 2:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
def fibonacci(n):
if n <= 2:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
I particularly like to use this with narrow
, which is …
Narrowing your Focus
Narrowing a buffer hides parts of it so you can focus on an important
subsection. I'll often narrow a a big file to just the function or
class that I'm working on. I usually use narrow-to-region
(bound to
C-x n n
) after selecting the code I want to focus on.
Like here: I want to work on just one function. To get rid of the rest, I select it and …
print("Fizz")
elif mod5:
print("Buzz")
else:
print(i)
def factorial(n):
if n < 1:
return 1
else:
return n * fib(n - 1)
def fibonacci(n):
if n < 1:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)
def factorial(n):
if n < 1:
return 1
else:
return n * fib(n - 1)
Aah, much better.
This might seem like a very small improvement, and sometimes it is. I find it useful when I have to refer back and forth from the code to notes or documentation; finding my place is much easier when the code looks small.
Narrowing to multiple regions
One tricky problem you might encounter is using narrow
to focus on
two different regions in a file. I don't have this problem very often,
but I have it often enough that I figured out how to get around it.
Opening two windows with the same file is easy enough, but trying to narrow them separately doesn't work.
def fibonacci(n):
if n <= 2:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
def factorial(n):
def inner_factorial(n, acc):
if n <= 1:
return acc
else return inner_factorial(n-1, n * acc)
return inner_factorial(n, 1)
example.py
def fibonacci(n): if n <= 2: return 1 else: return fibonacci(n-1) + fibonacci(n-2) def factorial(n): def inner_factorial(n, acc): if n <= 1: return acc else return inner_factorial(n-1, n * acc) return inner_factorial(n, 1)
example.pydef fibonacci(n): if n <= 2: return 1 else: return fibonacci(n-1) + fibonacci(n-2) def factorial(n): def inner_factorial(n, acc): if n <= 1: return acc else return inner_factorial(n-1, n * acc) return inner_factorial(n, 1)
example.py
(Try to narrow to just factorial
)
def factorial(n): def inner_factorial(n, acc): if n <= 1: return acc else return inner_factorial(n-1, n * acc) return inner_factorial(n, 1)
example.pydef factorial(n): def inner_factorial(n, acc): if n <= 1: return acc else return inner_factorial(n-1, n * acc) return inner_factorial(n, 1)
example.py
Hmmm. Two copies of the same details isn't very useful …
To have a second narrowed region, you'll have to use
clone-indirect-buffer
to open another "copy" of the file in an
indirect buffer.
This copy has its own windows and settings. You can narrow it
independently, but Emacs will keep the two copies in sync (so your
changes won't get overwritten or anything nasty like that).
def fibonacci(n):
if n <= 2:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
def factorial(n):
def inner_factorial(n, acc):
if n <= 1:
return acc
else return inner_factorial(n-1, n * acc)
return inner_factorial(n, 1)
example.py
def fibonacci(n): if n <= 2: return 1 else: return fibonacci(n-1) + fibonacci(n-2) def factorial(n): def inner_factorial(n, acc): if n <= 1: return acc else return inner_factorial(n-1, n * acc) return inner_factorial(n, 1)
example.py<1>def fibonacci(n): if n <= 2: return 1 else: return fibonacci(n-1) + fibonacci(n-2) def factorial(n): def inner_factorial(n, acc): if n <= 1: return acc else return inner_factorial(n-1, n * acc) return inner_factorial(n, 1)
example.py<2>
(Try to narrow to just factorial
)
def factorial(n): def inner_factorial(n, acc): if n <= 1: return acc else return inner_factorial(n-1, n * acc) return inner_factorial(n, 1)
example.py<1>def fibonacci(n): if n <= 2: return 1 else: return fibonacci(n-1) + fibonacci(n-2) def factorial(n): def inner_factorial(n, acc): if n <= 1: return acc else return inner_factorial(n-1, n * acc) return inner_factorial(n, 1)
example.py<2>
Now we have truly separate views into our file, and changes will be synced between them.
What's next
We've seen a few advanced commands for selecting code once you've mastered the basics, and the modes for you language of choice may have even more. You should investigate that!
These kinds of commands are most useful when you've already found the code you need to work on. Emacs also has tools for finding and filtering code. You can read about them in part 2 of this post (coming soon).