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)
C-M-SPC
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)
C-M-SPC
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)
C-x n n


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
C-x 2
(splits the window into 2 horizontal windows)
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

(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
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

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
M-x clone-indirect-buffer RET
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).

Nat Knight — 2016-08-07