Live \(\LaTeX\) Typesetting

Preface

I take my notes in real-time during lectures so most of my workflow is oriented towards maximizing the speed at which I can typeset. Most of the speed comes from hours and hours of practice in lecture1, but a smooth editing setup, as well as a ton of personalized macros allow for many of the painful points of \(\LaTeX\) to be handled automatically.

Editor/PDF Viewer

To begin, I’ll briefly cover the editor and pdf viewer I use. In order to reduce the reliance on any mouse usage, I use neovim for my editing, and zathura for my viewer. zathura’s vim-like keybindings mean that I can swap between my editor and my pdf and navigate seamlessly2. Aside from the keybindings, there’s nothing inherently special about zathura for TeX pdfs, I’ve previously used Skim, since it also had automatic pdf reloading.

test
An example instance of my \(\LaTeX\) environment, neovim open on the left, zathura on the right, and my compilation script in the bottom left.

In terms of neovim packages, I use vim-conceal and tex-conceal to make the source file slightly easier to read at a glance. I also particularly like hop.nvim, for file navigation. vimtex is also a major part of my setup, allowing me to open a .tex file in neovim and open its corresponding .pdf file using \lv. It also has support for continuous compilation of the .tex source within neovim, but I prefer to have a separate terminal window running the compilation.

Macros/Snippets

What I consider the main time-saving portion of my setup is the snippet/macros that I use. In neovim, I use the Ultisnips package, which allows me to define snippets that I can tab-complete, as well as snippets that automatically expand. Ultisnips also has the ability to define contexts, where certain snippets only expand when inside/outside a math expression. In my tex.snippets configuration, I first define a math context:

global !p
def math():
    return vim.eval('vimtex#syntax#in_mathzone()') == '1'

def comment(): 
    return vim.eval('vimtex#syntax#in_comment()') == '1'

def env(name):
    [x,y] = vim.eval("vimtex#env#is_inside('" + name + "')") 
    return x != '0' and x != '0'

endglobal

Two very important snippets that I use are ones that generate inline and display math environments, typing ml automatically expands into an align* environment, and typing mk automatically creates an inline $$ environment.

snippet ml "Math" wA
\\begin{align*}
$1
\\end{align*}
$0
endsnippet

snippet mk "Math" wA
$${1}$`!p
if t[2] and t[2][0] not in [',', '.', '?', '-', ' ']:
    snip.rv = ' '
else:
    snip.rv = ''
`$2
endsnippet

I then have some of my commonly used snippets, such as auto-expanding fractions, generated by typing ff inside a math environment:

context "math()"
snippet ff "Fraction" wA
\\frac{$1}{$2}$0
endsnippet

After the snippet expands, I can move between the numerator and denominator arguments using Tab, and pressing tab a third time leaves the fraction.

Another set of extremely useful snippets are my parentheses and square bracket snippets. Using the \left and \right tags, these automatically size themselves to their contents, meaning that I never have to worry about manually resizing parens. These can be achieved by typing p or s in a math environment and hitting Tab.

context "math()"
snippet p "parens" i
\\left($1\\right)$0
endsnippet

context "math()"
snippet s "square parens" i
\\left[$1\\right]$0
endsnippet

Closing out my most used snippets, derivatives can be constructed extremely quickly using snippets, typing dd automatically generates the skeleton of a total derivative, and pdd automatically generates a partial derivative.

context "math()"
snippet dd "derivative" wA
\\frac{d $1}{d $2}$0
endsnippet

context "math()"
snippet pdd "partial derivative" wA
\\frac{\partial $1}{\partial $2}$0
endsnippet

All of my snippets can be found (along with all my other config files) here.

Compilation

This is likely the least influential of the features of my setup, but for the sake of completeness I’ll cover my .tex compilation procedure. There are a lot of compilation scripts out there, and all of them are most likely good enough for in-class notetaking. I previously used latexmk:

latexmk -pdf -pvc filename.tex

Which worked just fine, but I currently use latexrun, along with entr to rerun latexrun when the source file changes. In my .zshrc, I define a function c:

c () {
	echo $1 | entr -c latexrun $1
}

Which I can then use to compile a .tex file:

c filename.tex

This will automatically compile the file the correct number of times, and will recompile when the source .tex file changes. In terms of performance, latexrun and latexmk function at practically the same speed, so the choice of compiler is completely personal preference.

Other Resources

My setup is cobbled together from bits and pieces of multiple live-TeXing configurations, but the biggest influences are undoubtedly:

My (unsolicited) advice for getting up to speed in lectures is to practice, I’ve been live-TeXing my notes for ~7 years, and it took me time to build up the muscle memory and ability to both focus on the lecture being given, as well as typesetting the content of the lecture.


  1. Comparing my notes from the beginning of my undergrad to those in graduate school, one can see that my formatting and ability to include more of the contents of the derivations and spoken conceptual content has dramatically improved. ↩︎

  2. I use yabai for tiling window management on macOS, and skhd to quickly switch between kitty and zathura↩︎