Home » Technology » How Im able to take notes in mathematics lectures using LaTeX and Vim

How Im able to take notes in mathematics lectures using LaTeX and Vim

A while back I an­swered a ques­tion on Quora: Can peo­ple ac­tu­al­ly keep up with note-​taking in Math­e­mat­ics lec­tures with LaTeX.
There, I ex­plained my work­flow of tak­ing lec­ture notes in LaTeX using Vim and how I draw fig­ures in Inkscape.
How­ev­er, a lot has changed since then and I’d like to write a few blog posts ex­plain­ing my work­flow.

I start­ed using LaTeX to write lec­ture notes in the sec­ond se­mes­ter of my bach­e­lor in math­e­mat­ics, and I’ve been using it ever since,
which makes for a total of more than 1700 pages of notes.
To give you an idea of what those notes look like, here are some ex­am­ples:




These lec­ture notes — in­clud­ing fig­ures — are made while at­tend­ing the lec­ture and have not been edit­ed af­ter­wards.
To make note tak­ing using LaTeX vi­able, I had four goals in mind:

  • Writ­ing text and math­e­mat­i­cal for­mu­las in LaTeX should be as fast as the lec­tur­er writ­ing on a black­board: no delay is ac­cept­able.
  • Draw­ing fig­ures should be al­most as fast as the lec­tur­er.
  • Man­ag­ing notes, i.e. adding a note, com­pil­ing all my notes, com­pil­ing the last two lec­tures, search­ing in notes, etc. should be easy and quick.
  • An­no­tat­ing pdf doc­u­ments using LaTeX should be pos­si­ble for when I want to write notes along­side a pdf doc­u­ment.

This blog post will focus on the first item: writ­ing LaTeX.

Vim and LaTeX

For writ­ing text and math­e­mat­i­cal for­mu­las in LaTeX, I use Vim.
Vim is a pow­er­ful gen­er­al pur­pose text ed­i­tor that’s very ex­ten­si­ble.
I use it for writ­ing code, LaTeX, mark­down, … ba­si­cal­ly every­thing that’s text-​based.
It has a fair­ly steep learn­ing curve, but once you’ve got the ba­sics down, it’s hard to get back to an ed­i­tor with­out Vim key­bind­ings.
Here’s what my screen looks like when I’m edit­ing LaTeX:

Vim and Zathura

On the left you see Vim and on the right my pdf view­er, Za­thu­ra, which also has Vim-​like key­bind­ings.
I’m using Ubun­tu with bspwm as my win­dow man­ag­er.
The LaTeX plu­g­in I’m using in Vim is vim­tex.
It pro­vides syn­tax high­light­ing, table of con­tents view, sync­tex, etc.
Using vim-​plug, I con­fig­ured it as fol­lows:

Plug 'lervag/vimtex'
let g:tex_flavor='latex'
let g:vimtex_view_method='zathura'
let g:vimtex_quickfix_mode=0
set conceallevel=1
let g:tex_conceal='abdmg'

The last two lines con­fig­ure the con­ceal­ment.
This is a fea­ture where LaTeX code is re­placed or made in­vis­i­ble when your cur­sor is not on that line.
By mak­ing [, ], $ in­vis­i­ble, they’re less ob­tru­sive which gives you a bet­ter overview of the doc­u­ment. This fea­ture also re­places bigcap by by , in by etc.
The fol­low­ing an­i­ma­tion should make that clear.


With this set up, I come to the crux of this blog post: writ­ing LaTeX as fast as the lec­tur­er can write on the black­board.
This is where snip­pets come into play.


What’s a snip­pet?

A snip­pet is a short reusable piece of text that can be trig­gered by some other text.
For ex­am­ple, when I type sign and press Tab, the word sign will be ex­pand­ed to a sig­na­ture:


Snip­pets can also be dy­nam­ic: when I type today and press Tab, the word today will be re­placed by the cur­rent date, and box Tab be­comes a box that au­to­mat­i­cal­ly grows in size.



You can even use one snip­pet in­side an­oth­er:


Using Ul­tiSnips to cre­ate snip­pets

I use the plu­g­in Ul­tiSnips to man­age my snip­pets. My con­fig­u­ra­tion is

Plug 'sirver/ultisnips'
let g:UltiSnipsExpandTrigger = '<tab>'
let g:UltiSnipsJumpForwardTrigger = '<tab>'
let g:UltiSnipsJumpBackwardTrigger = '<s-tab>'

The code for the sign snip­pet is the fol­low­ing:

snippet sign "Signature"
Yours sincerely,

Gilles Castel

For dy­nam­ic snip­pets, you can put code be­tween back­ticks `` which will be run when the snip­pet is ex­pand­ed.
Here, I’ve used bash to for­mat the cur­rent date: date + %F.

snippet today "Date"
`date +%F`

You can also use Python in­side a `!p ... ` block.
Have a look at the code for the box snip­pet:

snippet box "Box"
`!p snip.rv = '┌' + '─' * (len(t[1]) + 2) + '┐'`$1 │
`!p snip.rv = '└' + '─' * (len(t[1]) + 2) + '┘'`

These Python code blocks will be re­placed by the value of the vari­able snip.rv.
In­side these blocks, you have ac­cess to the cur­rent state of the snip­pet, e.g. t[1] con­tains the first tab stop, fn the cur­rent file­name, …

LaTeX snip­pets

Using snip­pets, writ­ing LaTeX is a lot faster than writ­ing it by hand.
Es­pe­cial­ly some of the more com­plex snip­pets can save you a lot of time and frus­tra­tion. Let’s begin with some sim­ple snip­pets.


To in­sert an en­vi­ron­ment, all I have to do is type beg at the be­gin­ning of a line.
Then I type the name of the en­vi­ron­ment, which is mir­rored in the end com­mand. Press­ing Tab places the cur­sor in­side the newly cre­at­ed en­vi­ron­ment.


The code for this snip­pet is the fol­low­ing.

snippet beg "begin / end" bA

The b means that this snip­pet will only be ex­pand­ed at the be­gin­ning of a line and A stands for auto ex­pand, which means I do not have to press Tab to ex­pand the snip­pet. Tab stops — i.e. places you can jump to by press­ing Tab and Shift+Tab — are rep­re­sent­ed by $1, $2, … and the last one with $0.

In­line and dis­play math

Two of my most fre­quent­ly used snip­pets are mk and dm.
They’re the snip­pets re­spon­si­ble for start­ing math mode.
The first one is a snip­pet for in­line math, the sec­ond one for dis­played math.


The snip­pet for in­line math is ‘smart’: it knows when to in­sert a space after the dol­lar sign.
When I start typ­ing a word di­rect­ly be­hind the clos­ing $, it adds a space.
How­ev­er, when I type a non-​word char­ac­ter, it does not add a space, which would be pre­ferred for ex­am­ple in the case of ‘$p$-​value’.


The code for this snip­pet is the fol­low­ing.

snippet mk "Math" wA
if t[2] and t[2][0] not in [',', '.', '?', '-', ' ']:
    snip.rv = ' '
    snip.rv = ''

The w at the end of the first line means that this snip­pet will ex­pand at word bound­aries, so e.g. hellomk won’t ex­pand, but hello mk will.

The snip­pet for dis­played math is more sim­ple, but it also is quite handy; it makes me never for­get end­ing equa­tions with a pe­ri­od.


snippet dm "Math" wA
.] $0

Sub- and su­per­scripts

An­oth­er use­ful snip­pet is one for sub­scripts.
It changes changes a1 to a_1 and a_12 to a_12.


The code for this snip­pet uses a reg­u­lar ex­pres­sion for its trig­ger.
It ex­pands the snip­pet when you type a char­ac­ter fol­lowed by a digit, which en­cod­ed by [A-Za-z]d, or a char­ac­ter fol­lowed by _ and two dig­its: [A-Za-z]_dd.

snippet '([A-Za-z])(d)' "auto subscript" wrA
`!p snip.rv = match.group(1)`_`!p snip.rv = match.group(2)`

snippet '([A-Za-z])_(dd)' "auto subscript2" wrA
`!p snip.rv = match.group(1)`_`!p snip.rv = match.group(2)`

When you wrap parts of a reg­u­lar ex­pres­sion in a group using paren­the­sis, e.g. (dd), you can use them in the ex­pan­sion of the snip­pet via match.group(i) in Python.

As for su­per­scripts, I use td, which be­comes ^. How­ev­er, for squared, cubed, com­ple­ment and a hand­ful of other com­mon ones, I use ded­i­cat­ed snip­pets such as sr, cb and comp.


snippet sr "^2" iA

snippet cb "^3" iA

snippet compl "complement" iA

snippet td "superscript" iA


One of my most con­ve­nient snip­pets is one for frac­tions. This makes the fol­low­ing ex­pan­sions:

// frac
3/ frac3
4pi^2/ frac4pi^2
(1 + 2 + 3)/ frac1 + 2 + 3
(1+(2+3)/) (1 + frac2+3)
(1 + (2+3))/ frac1 + (2+3)


The code for the first one is easy:

snippet // "Fraction" iA

The sec­ond and third ex­am­ples are made pos­si­ble using reg­u­lar ex­pres­sions to match for ex­pres­sions like 3/, 4ac/, 6pi^2/, a_2/, etc.

snippet '((d+)|(d*)(\)?([A-Za-z]+)((^|_)(d+|d))*)/' "Fraction" wrA
\frac`!p snip.rv = match.group(1)`$1$0

As you can see, reg­u­lar ex­pres­sions can be­come quite over­whelm­ing, but here’s a di­a­gram that should ex­plain it:

Diagram of the regular expression

In the fourth and fifth cases, it tries to find the match­ing paren­the­sis. As this isn’t pos­si­ble using the reg­u­lar ex­pres­sion en­gine of Ul­tiSnips, I re­sort­ed to using Python:

priority 1000
snippet '^.*)/' "() Fraction" wrA
stripped = match.string[:-1]
depth = 0
i = len(stripped) - 1
while True:
	if stripped[i] == ')': depth += 1
	if stripped[i] == '(': depth -= 1
	if depth == 0: break;
	i -= 1
snip.rv = stripped[0:i] + "\frac" + stripped[i+1:-1] + ""

The last snip­pet con­cern­ing frac­tions I’d like to share is one that uses your se­lec­tion to make a frac­tion. You can use it by first se­lect­ing some text, then press­ing Tab, typ­ing / and press­ing Tab again.


The code makes use of the $VISUAL vari­able that rep­re­sents your se­lec­tion.

snippet / "Fraction" iA

Sympy and Math­e­mat­i­ca

An­oth­er cool — but less used — snip­pet is one that uses sympy to eval­u­ate math­e­mat­i­cal ex­pres­sions. For ex­am­ple: sympy Tab ex­pands to sympy | sympy, and sympy 1 + 1 sympy Tab ex­pands to 2.


snippet sympy "sympy block " w
sympy $1 sympy$0

priority 10000
snippet 'sympy(.*)sympy' "evaluate sympy" wr
from sympy import *
x, y, z, t = symbols('x y z t')
k, m, n = symbols('k m n', integer=True)
f, g, h = symbols('f g h', cls=Function)
snip.rv = eval('latex(' + match.group(1).replace('\', '') 
    .replace('^', '**') 
    .replace('', '(') 
    .replace('', ')') + ')')

For the Math­e­mat­i­ca users out there, you can do some­thing sim­i­lar:


priority 1000
snippet math "mathematica block" w
math $1 math$0

priority 10000
snippet 'math(.*)math' "evaluate mathematica" wr
import subprocess
code = 'ToString[' + match.group(1) + ', TeXForm]'
snip.rv = subprocess.check_output(['wolframscript', '-code', code])

Post­fix snip­pets

Some other snip­pets I find worth shar­ing are post­fix snip­pets.
Ex­am­ples of such snip­pets are phathatp and zbaroverlinez.
A sim­i­lar snip­pet is a post­fix vec­tor, for ex­am­ple v,.vecv and v.,vecv.
The order of , and . doesn’t mat­ter, so I can press them both at the same time.
These snip­pets are a real time-​saver, be­cause you can type in the same order the lec­tur­er writes on the black­board.


Note that I can still use bar and hat pre­fix too, as I’ve added them with a lower pri­or­i­ty.
The code for those snip­pets is:

priority 10
snippet "bar" "bar" riA

priority 100
snippet "([a-zA-Z])bar" "bar" riA
overline`!p snip.rv=match.group(1)`
priority 10
snippet "hat" "hat" riA

priority 100
snippet "([a-zA-Z])hat" "hat" riA
hat`!p snip.rv=match.group(1)`
snippet "(\?w+)(,.|.,)" "Vector postfix" riA
vec`!p snip.rv=match.group(1)`

Other snip­pets

I have about 100 other com­mon­ly used snip­pets.
They are avail­able here.
Most of them are quite sim­ple.
For ex­am­ple, !> be­comes mapsto, -> be­comes to, etc.


fun be­comes f: R to R :, !>mapsto, ->to, ccsubset.


lim be­comes lim_n to infty, sumsum_n = 1^infty, oooinfty



Course spe­cif­ic snip­pets

Be­side my com­mon­ly used snip­pets, I also have course spe­cif­ic snip­pets.
These are loaded by adding the fol­low­ing to my .vimrc:

set rtp+=~/current_course

where current_course is a sym­link to my cur­rent­ly ac­ti­vat­ed course (more about that in an­oth­er blog post).
In that fold­er, I have a file ~/current_course/UltiSnips/tex.snippets in which I in­clude course spe­cif­ic snip­pets. For ex­am­ple, for quan­tum me­chan­ics, I have snip­pets for bra/ket no­ta­tion.

<a| braa
<q| brapsi
|a> keta
|q> ketpsi
<a|b> braketab

As psi is used a lot in quan­tum me­chan­ics, I re­place all in­stances of q in a braket with psi when ex­pand­ed.


snippet "<(.*?)|" "bra" riA
bra`!p snip.rv = match.group(1).replace('q', f'psi').replace('f', f'phi')`

snippet "|(.*?)>" "ket" riA
ket`!p snip.rv = match.group(1).replace('q', f'psi').replace('f', f'phi')`

snippet "(.*)\bra(.*?)([^|]*?)>" "braket" riA
`!p snip.rv = match.group(1)`braket`!p snip.rv = match.group(2)``!p snip.rv = match.group(3).replace('q', f'psi').replace('f', f'phi')`


One thing to con­sid­er when writ­ing these snip­pets is, ‘will these snip­pets col­lide with usual text?’
For ex­am­ple, ac­cord­ing to my dic­tio­nary, there are about 72 words in Eng­lish and 2000 words in Dutch that con­tain sr, which means that while I’m typ­ing the word disregard, the sr would ex­pand to ^2, giv­ing me di^2egard.

The so­lu­tion to this prob­lem is adding a con­text to snip­pets. Using the syn­tax high­light­ing of Vim, it can be de­ter­mined whether or not Ul­tiSnips should ex­pand the snip­pet de­pend­ing if you’re in math or text. I came up with the fol­low­ing:

global !p
texMathZones = ['texMathZone'+x for x in ['A', 'AS', 'B', 'BS', 'C',
'CS', 'D', 'DS', 'E', 'ES', 'F', 'FS', 'G', 'GS', 'H', 'HS', 'I', 'IS',
'J', 'JS', 'K', 'KS', 'L', 'LS', 'DS', 'V', 'W', 'X', 'Y', 'Z']]

texIgnoreMathZones = ['texMathText']

texMathZoneIds = vim.eval('map('+str(texMathZones)+", 'hlID(v:val)')")
texIgnoreMathZoneIds = vim.eval('map('+str(texIgnoreMathZones)+", 'hlID(v:val)')")

ignore = texIgnoreMathZoneIds[0]

def math():
	synstackids = vim.eval("synstack(line('.'), col('.') - (col('.')>=2 ? 1 : 0))")
		first = next(
            i for i in reversed(synstackids)
            if i in texIgnoreMathZoneIds or i in texMathZoneIds
		return first != ignore
	except StopIteration:
		return False

Now you can add context "math()" to the snip­pets you’d only want to ex­pand in a math­e­mat­i­cal con­text.

context "math()"
snippet sr "^2" iA

Note that a ‘math­e­mat­i­cal con­text’ is a sub­tle thing.
Some­times you add some text in­side a math en­vi­ron­ment by using text....
In that case, you do not want snip­pets to ex­pand.
How­ev­er, in the fol­low­ing case: [ text$...$ ], they should ex­pand.
This is why the code for the math con­text is a bit com­pli­cat­ed.
The fol­low­ing an­i­ma­tion il­lus­trates these sub­tleties.


Cor­rect­ing spelling mis­takes on the fly

While in­sert­ing math­e­mat­ics is an im­por­tant part of my note-​taking setup, most of the time I’m typ­ing Eng­lish. At about 80 words per minute, my typ­ing skills are not bad, but I still make a lot of typos.
This is why I added a key­bind­ing to Vim that cor­rects the spelling mis­takes, with­out in­ter­rupt­ing my flow. When I press Ctrl+L while I’m typ­ing, the pre­vi­ous spelling mis­take is cor­rect­ed. It looks like this:


My con­fig­u­ra­tion for spell check is the fol­low­ing:

setlocal spell
set spelllang=nl,en_gb
inoremap <C-l> <c-g>u<Esc>[s1z=`]a<c-g>u

It ba­si­cal­ly jumps to the pre­vi­ous spelling mis­take [s, then picks the first sug­ges­tion 1z=, and then jumps back `]a. The <c-g>u in the mid­dle make it pos­si­ble to undo the spelling cor­rec­tion quick­ly.

In con­clu­sion

Using snip­pets in Vim, writ­ing LaTeX is no longer an an­noy­ance, but rather a plea­sure.
In com­bi­na­tion with spell check on the fly, it al­lows for a com­fort­able math­e­mat­i­cal note-​taking setup.
A few pieces are miss­ing though, for ex­am­ple draw­ing fig­ures dig­i­tal­ly and em­bed­ding them in a LaTeX doc­u­ment. This is a topic I’d like to tack­le in a fu­ture blog post.


Leave a Reply

Your email address will not be published. Required fields are marked *



Check Also

Why Racket? Why Lisp?


I Got a Knuth Check for 0x$3.00