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:

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.

Snippets

What’s a snippet?

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 UltiSnips to create snippets

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 endsnippet

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` endsnippet

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 ) + '┘' ` $0 endsnippet

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 snippets

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.

Environments

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 \begin{ $1 } $0 \end{ $1 } endsnippet

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 .

Inline and display 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 $ ${1} $` !p if t [ 2 ] and t [ 2 ] [ 0 ] not in [ ',' , '.' , '?' , '-' , ' ' ] : snip . rv = ' ' else : snip . rv = '' ` $2 endsnippet

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 \[ $1 .\] $0 endsnippet

Sub- and superscripts

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]_\d\d .

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

When you wrap parts of a reg­u­lar ex­pres­sion in a group using paren­the­sis, e.g. (\d\d) , 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 ^2 endsnippet snippet cb "^3" iA ^3 endsnippet snippet compl "complement" iA ^{c} endsnippet snippet td "superscript" iA ^{ $1 } $0 endsnippet

Fractions

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/ → \frac{3}{} 4\pi^2/ → \frac{4\pi^2}{} (1 + 2 + 3)/ → \frac{1 + 2 + 3}{} (1+(2+3)/) → (1 + \frac{2+3}{}) (1 + (2+3))/ → \frac{1 + (2+3)}{}

The code for the first one is easy:

snippet // "Fraction" iA \\frac{ $1 }{ $2 } $0 endsnippet

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/ , 6\pi^2/ , a_2/ , etc.

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

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:

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 ` !p 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 ] + "}" ` { $1 } $0 endsnippet

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 \\frac{ ${VISUAL} }{ $1 } $0 endsnippet

Sympy and Mathematica

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 endsnippet priority 10000 snippet 'sympy(.*)sympy' "evaluate sympy" wr ` !p 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 ) init_printing ( ) snip . rv = eval ( 'latex(' + match . group ( 1 ) . replace ( '\\' , '' ) \ . replace ( '^' , '**' ) \ . replace ( '{' , '(' ) \ . replace ( '}' , ')' ) + ')' ) ` endsnippet

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 endsnippet priority 10000 snippet 'math(.*)math' "evaluate mathematica" wr ` !p import subprocess code = 'ToString[' + match . group ( 1 ) + ', TeXForm]' snip . rv = subprocess . check_output ( [ 'wolframscript' , '-code' , code ] ) ` endsnippet

Postfix snippets

Some other snip­pets I find worth shar­ing are post­fix snip­pets. Ex­am­ples of such snip­pets are phat → \hat{p} and zbar → \overline{z} . A sim­i­lar snip­pet is a post­fix vec­tor, for ex­am­ple v,. → \vec{v} and v., → \vec{v} . 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 \overline{ $1 } $0 endsnippet priority 100 snippet "([a-zA-Z])bar" "bar" riA \overline{` !p snip . rv = match . group ( 1 ) ` } endsnippet

priority 10 snippet "hat" "hat" riA \hat{ $1 } $0 endsnippet priority 100 snippet "([a-zA-Z])hat" "hat" riA \hat{` !p snip . rv = match . group ( 1 ) ` } endsnippet

snippet "(\\?\w+)(,\.|\.,)" "Vector postfix" riA \vec{` !p snip . rv = match . group ( 1 ) ` } endsnippet

Other snippets

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 , cc → \subset .

lim be­comes \lim_{n \to \infty} , sum → \sum_{n = 1}^{\infty} , ooo → \infty

Course specific snippets

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| → \bra{a} <q| → \bra{\psi} |a> → \ket{a} |q> → \ket{\psi} <a|b> → \braket{a}{b}

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' ) ` } endsnippet snippet "\|(.*?)\>" "ket" riA \ket{` !p snip . rv = match . group ( 1 ) . replace ( 'q' , f '\psi' ) . replace ( 'f' , f '\phi' ) ` } endsnippet 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' ) ` } endsnippet

Context

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' , 'AmsA' , 'AmsB' , 'AmsC' , 'AmsD' , 'AmsE' , 'AmsF' , 'AmsG' , 'AmsAS' , 'AmsBS' , 'AmsCS' , 'AmsDS' , 'AmsES' , 'AmsFS' , 'AmsGS' ] ] 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))" ) try : first = next ( i for i in reversed ( synstackids ) if i in texIgnoreMathZoneIds or i in texMathZoneIds ) return first != ignore except StopIteration : return False endglobal

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 ^2 endsnippet

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.

Correcting spelling mistakes 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 conclusion

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.

Liked this blog post? Con­sid­er buy­ing me a cof­fee!