Creating an outline for Python files

A while ago I wrote a little script that generated an outline of the Python file I was working on. I’ve been using it for a while and quite like it, so I figured I’d share it.

define-command python-outline \
-docstring "Build an outline view of the current Python source file." \
%{
    evaluate-commands -save-regs 'ab' %{
        # Save the file we're building an outline for.
        reg a %val{buffile}

        try %{
            # Don't save any registers,
            # we want to keep the yank register for later.
            execute-keys -draft -save-regs '' \
                <percent> \
                s\n?^\h*(class|def)[^\n]+\n<ret> \
                '"by'
        } catch %{
            fail "No Python classes or functions in this source file."
        }

        # Make the outline appear in the toolsclient, if possible.
        evaluate-commands -try-client %opt{toolsclient} %{
            # Make a scratch buffer to render the outline
            try %{ delete-buffer! *outline* }
            edit -scratch *outline*
            # Paste the filename, then all the landmarks
            execute-keys -draft '"aP' A<ret><esc> '"b<a-p>'
            set-option buffer filetype python
            set-option buffer readonly true
            map buffer normal <ret> ':python-outline-jump<ret>'
        }
    }
}

define-command -hidden python-outline-jump %{
    evaluate-commands -save-regs ab %{
        evaluate-commands -draft %{
            # collect the line we want to search for in "b
            execute-keys xs\w+[^\n]*<ret>"by
            # collect the filename in "a
            execute-keys ggxH"ay
        }
        evaluate-commands -try-client %opt{jumpclient} %{
            buffer %reg{a}
            execute-keys /\Q <c-r>b <ret> vt
        }
    }
}

hook global WinSetOption filetype=python %{
    map window goto o -docstring "File outline" \
        '<esc>:python-outline<ret>'
    hook -once -always window WinSetOption filetype=.* %{
        unmap window goto o
    }
}

The hook at the end lets me go to the outline with go, which seems natural enough.

To jump back to the source, it selects the line you hit <ret> on and searches for it in the original buffer. That’s not 100% reliable, since (for example) there’s probably going to be many def __init__(self): lines in a Python source file, for example. But since you’re looking at the full outline, it’s not difficult to just pick the nearest uniquely-named item, go there, and scroll to the one you want.

3 Likes