/ minted

Syntax Highlighting in LaTeX with minted

This post serves as an introduction to minted, a pygments-based syntax highlighter for LaTeX. Adding pygments to LaTeX streamlines so many things. The post provides a few examples of things you can do with minted, details the installation process, and covers some basic security.

Code

You can view the code related to this post under the post-01-overview tag.

Overview

The easiest way to present code in LaTeX is to use the verbatim environment. It's quick, it preserves formatting, and it requires no set up. It's also very bland. Its ease of use comes at the cost of basically all the context clues well-formatted and styled code can provide.

The next step up (or rather many steps up) is the listings package. Out the box, it supports a broad range of languages. It's eminently configurable. You can define new languages yourself, add different keywords, and style to your heart's content. It's very good at being straightforward. Moving beyond its predefined scopes (or easily discoverable internet styles) is a challenge, though, because parsing and tokenizing code in LaTeX is just about as hard and ridiculous as it sounds.

minted has become a solid competitor. It uses the pygments project to parse and highlight. You've probably seen pygments in action already. It's a beast of an application that can do just about anything you want re: syntax highlighting. minted isn't quite as flexible, but it does have access to most of the pygments features. Recognizable styles, a massive library of lexers, and simple customization through Python make minted, by way of pygments, a veritable utility knife.

sample.tex
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
\documentclass{article}
% chktex-file 18
\usepackage[
paperwidth=2.5in,
paperheight=3in,
total={2in,2.8in}
]{geometry}
\usepackage{listings}
\usepackage{minted}

\setlength{\parindent}{0pt}

\begin{document}
\begin{center}

This is verbatim:

\begin{verbatim}
#!/bin/bash

echo "Hello, world!"
\end{verbatim}

\hrule
\vspace{6pt}

This is vanilla {\ttfamily listings}:

\begin{lstlisting}[language=Bash]
#!/bin/bash

echo "Hello, world!"
\end{lstlisting}

\hrule
\vspace{6pt}

This is vanilla {\ttfamily minted}:

\begin{minted}{bash}
#!/bin/bash

echo "Hello, world!"
\end{minted}
\end{center}
\end{document}

sample-3

There's a bit more to the listings vs. minted debate. Essentially it boils down to where you want to customize. Personally, I feel like a general-purpose scripting language used in all areas of tech is a stronger contender than a typesetting system many of my peers have struggled to learn. I don't know, though (and if I'm wrong, I'd love to hear about it). At its core, TeX tokenizes everything. I'm just not sure that it can achieve the same level of regex wizardry that goes into some of the pygments code.

Installing

minted requires a few things to get up and running.

Python

You'll need Python to get started. Pygments needs >=2.6 or >=3.3, depending on your version of Python. You can lazily install both with any trouble. For example, via dnf,

$ sudo dnf install python{2,3}

pip

Next you'll need pip, a wonderful package manager for Python. It's ridiculously easy to install. Rather than install it globally (i.e. to /usr/bin), we're going to install it locally via the --user flag.

$ wget https://bootstrap.pypa.io/get-pip.py; python get-pip.py --user; rm get-pip.py
This should be sufficient
$ wget https://bootstrap.pypa.io/get-pip.py; python2 get-pip.py --user; rm get-pip.py
This will install for Python 2 explicitly
$ wget https://bootstrap.pypa.io/get-pip.py; python3 get-pip.py --user; rm get-pip.py
This will install for Python 3 explicitly

However, this doesn't put pip on our path.

$ which pip
/usr/bin/which: no pip in (<path directories>)

The --user flag installed pip to our user site packages. We can check the base directory, which should have the desired bin, via

$ python -m site --user-base
~/.local

Since we have an easy way to discover the directory, we have an easy way to add it to our .whateverrc:

$ echo 'export PATH="$(python -m site --user-base)/bin:$PATH"' >> .whateverrc

You can also manually add it, which might be a good idea if you're doing other PATH manipulations.

pygments

With pip installed, we can quickly install pygments.

$ pip install --user pygments
This should be sufficient
$ pip2 install --user pygments
This will install for Python 2 explicitly
$ pip3 install --user pygments
This will install for Python 3 explicitly

Installing both isn't necessary. As of writing, Pygments is compatible with
both major versions of Python.

TeX Dependencies

minted provides a list of its dependencies. If you've got access to something like tlmgr, it should be pretty easy to update them.

keyval kvoptions fancyvrb fvextra upquote float ifthen calc ifplatform pdftexcmds etoolbox xstring xcolor lineno framed shellesc

If you don't (e.g. modern RHEL derivatives, I think), you'll have to get creative. This is the easiest route:

$ sudo dnf install 'texlive-*'
...
Install >5779 Packages

Total download size: >2.4 G
Installed size: >3.8 G
Is this ok [y/N]:

If, like me, you're running an SSD on a budget, the easiest isn't very convenient. Maybe you just don't feel like warehousing all of TeX Live to snag 16 dependencies. If you're not going to install everything, you need to figure out what you have to install. dnf/yum makes this somewhat trivial. If you're stuck with dpkg/dpkg-query, the discovery will be much more involved (but also I think you can run tlmgr so there's that).

install-texlive-dependencies
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash

# Pulled from https://github.com/gpoore/minted/blob/master/source/minted.pdf
DEPENDENCIES=(
keyval
kvoptions
fancyvrb
fvextra
upquote
float
ifthen
calc
ifplatform
pdftexcmds
etoolbox
xstring
xcolor
lineno
framed
shellesc
)
PACKAGES=()
# Loop over all the dependencies
for dependency in "${DEPENDENCIES[@]}"; do
# Check dnf for the parent package and trim its output
PACKAGES+=($(
dnf provides "tex($dependency.sty)" \
| awk -F':' '/^texlive/{ gsub("-[0-9]+$", "", $1); print $1 }'
))
done
# Remove duplicates
PACKAGES=($(echo "${PACKAGES[@]}" | tr ' ' '\n' | sort -u))
# Install dependencies
sudo dnf install "${PACKAGES[@]}"

minted

Convoluted dependency resolution aside, minted itself is a breeze to install (like pygments; we've already done all the hard work).

$ sudo dnf install texlive-minted

-shell-escape

Because minted relies on an external application (pygments) to highlight, it can't just run in a tiny, neatly contained environment. TeX essentially exposes streams but, by default, access to the operating system is locked down. -shell-escape neatly sidesteps those restrictions, but it doesn't come without risk. Just like anything else, it's probably not a great idea to provide shell access until you understand what's going on. Don't download random things off the internet and execute them blindly. Don't run in superuser mode all the time. You know, basic stuff.

This is what happens when you try to run minted without -shell-escape. Notice at the beginning that external actions are limited (restricted \write18 enabled). The document will not compile (even without -halt-on-error).

$ pdflatex -interaction=nonstopmode -halt-on-error sample.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016) (preloaded format=pdflatex)
restricted \write18 enabled.
entering extended mode
(./sample.tex
LaTeX2e <2016/03/31>
...

Package ifplatform Warning:
shell escape is disabled, so I can only detect \ifwindows.

...

! Package minted Error: You must invoke LaTeX with the -shell-escape flag.

See the minted package documentation for explanation.
Type H <return> for immediate help.
...

l.9

! ==> Fatal error occurred, no output PDF file produced!
Transcript written on sample.log.

With -shell-escape, any external action is available (\write18 enabled) and the document compiles.

$ pdflatex -interaction=nonstopmode -halt-on-error -shell-escape sample.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016) (preloaded format=pdflatex)
\write18 enabled.
entering extended mode
(./sample.tex
LaTeX2e <2016/03/31>
...
Output written on sample.pdf (1 page, 48380 bytes).
Transcript written on sample.log.

Chances are you're not actually building from the CLI every time. You've probably got an editor with some build commands stored. Don't add -shell-escape to all of your build profiles. It's a pain to toggle custom builds off and on, but having to rebuild your system after an attack is worse. Look for something like User Builds, Custom Commands, or the like.

For example, in TeXstudio, you can add custom builds via Configure TeXstudio > Builds > User Commands. In Texmaker, the same menu is available via User > User Commands > Edit User Commands.

Similarly, in Sublime via the LaTeXTools package, you can add custom builds to your project file (or anywhere else, for that matter).

posts-latex-minted.sublime-project
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"folders":
[
{
"path": "."
}
],
"build_systems":
[
{
"name": "Escalated pdflatex",
"target": "make_pdf",
"selector": "text.tex.latex",
"builder": "script",
"script_commands": [
[
"pdflatex",
"-synctex=1",
"-interaction=nonstopmode",
"-shell-escape",
"$file_base_name"
]
]
}
]
}

Useful features

You've already seen how simple it is to add code to a tex file. minted also makes it easy to include external source code without worrying about getting it to play well with your editor. The \inputminted macro lets you load any file while specifying the lexer.

input.tex
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
\documentclass{article}
\usepackage[
paperwidth=6in,
paperheight=4in,
total={5.5in,3.9in}
]{geometry}
\usepackage{minted}

\setlength{\parindent}{0pt}

\begin{document}
\begin{center}
\inputminted{bash}{../convert-pdf-to-png}
\end{center}
\end{document}

input

The default style is one of many available to minted. You can check the styles available on your system via

$ pygmentize -L styles
Pygments version 2.2.0, (c) 2006-2017 by Georg Brandl.

Styles:
~~~~~~~
* default:
The default style (inspired by Emacs 22).
* emacs:
The default style (inspired by Emacs 22).
* friendly:
A modern style based on the VIM pyte theme.
* colorful:
A colorful style, inspired by CodeRay.
* autumn:
A colorful style, inspired by the terminal highlighting style.
* murphy:
Murphy's style from CodeRay.
* manni:
A colorful style, inspired by the terminal highlighting style.
* monokai:
This style mimics the Monokai color scheme.
* perldoc:
Style similar to the style used in the perldoc code blocks.
* pastie:
Style similar to the pastie default style.
* borland:
Style similar to the style used in the borland IDEs.
* trac:
Port of the default trac highlighter design.
* native:
Pygments version of the "native" vim theme.
* fruity:
Pygments version of the "native" vim theme.
* bw:

* vim:
Styles somewhat like vim 7.0
* vs:

* tango:
The Crunchy default Style inspired from the color palette from the Tango Icon Theme Guidelines.
* rrt:
Minimalistic "rrt" theme, based on Zap and Emacs defaults.
* xcode:
Style similar to the Xcode default colouring theme.
* igor:
Pygments version of the official colors for Igor Pro procedures.
* paraiso-light:

* paraiso-dark:

* lovelace:
The style used in Lovelace interactive learning environment. Tries to avoid the "angry fruit salad" effect with desaturated and dim colours.
* algol:

* algol_nu:

* arduino:
The Arduino® language style. This style is designed to highlight the Arduino source code, so exepect the best results with it.
* rainbow_dash:
A bright and colorful syntax highlighting theme.
* abap:

You can preview any of the styles by visiting the pygments demo and trying out a highlighter. Once pygments has parsed the code, you'll be able to change the style at whim.

The default styles alone add a tremendous amount of utility to minted. There are many other settings that may be tweaked. Sharing style changes is an easy way to underscore minted's versatility.

style.tex
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
\documentclass{article}
% chktex-file 18
\usepackage[
paperwidth=2.5in,
paperheight=3in,
total={2in,2.8in}
]{geometry}
\usepackage{minted}
\usepackage{xcolor}

\setlength{\parindent}{0pt}

\definecolor{monokaibg}{HTML}{272822}
\definecolor{friendlybg}{HTML}{f0f0f0}

\begin{document}
\begin{center}

This is the {\ttfamily monokai} style.

\begin{minted}[
style=monokai,
bgcolor=monokaibg
]{bash}
#!/bin/bash

echo "Hello, world!"
\end{minted}

\hrule
\vspace{6pt}

This is the {\ttfamily colorful} style.

\begin{minted}[
style=colorful,
]{bash}
#!/bin/bash

echo "Hello, world!"
\end{minted}

\hrule
\vspace{6pt}

This is the {\ttfamily friendly} style.

\begin{minted}[
style=friendly,
bgcolor=friendlybg
]{bash}
#!/bin/bash

echo "Hello, world!"
\end{minted}
\end{center}
\end{document}

style

If you want to use the same style throughout your document, minted makes that simple too. The \newminted macro defines a configuration for a specific language, e.g. python. It can then be used as an environment in place of minted by appending code to the end, e.g. pythoncode.

newminted.tex
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
\documentclass{article}
% chktex-file 16
% chktex-file 18
% chktex-file 36
\usepackage[
paperwidth=2.5in,
paperheight=3in,
total={2in,2.8in}
]{geometry}
\usepackage{minted}
\usepackage{xcolor}

\setlength{\parindent}{0pt}
\setlength{\parskip}{0pt}

\definecolor{monokaibg}{HTML}{272822}
\definecolor{monokaifg}{rgb}{0.97,0.97,0.95}

\newminted{bash}{
style=monokai,
bgcolor=monokaibg
}

\BeforeBeginEnvironment{bashcode}{\color{monokaifg}}
\AfterEndEnvironment{bashcode}{\color{black}}

\begin{document}
\begin{center}
\begin{bashcode}
#!/bin/bash

echo "Hello, world!"
\end{bashcode}
\hrule
\begin{minted}{bash}
#!/bin/bash

echo "Hello, world!"
\end{minted}
\hrule
\begin{bashcode}
#!/bin/bash

FOO=($(find . -type f))
# Nothing's perfect
echo "${FOO[@]}"
\end{bashcode}
\end{center}
\end{document}

newminted

You can use the same logic with \inputminted via \newmintedfile. Rather than defining a new environment, \newmintedfile creates a new macro. It has an optional name parameter to make things easier (otherwise the macro is called \<language>file).

newmintedfile.tex
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
\documentclass{article}
\usepackage[
paperwidth=6in,
paperheight=4in,
total={5.5in,3.9in}
]{geometry}
\usepackage{minted}
\usepackage{xcolor}

\setlength{\parindent}{0pt}

\definecolor{mannibg}{HTML}{f0f3f3}

\newmintedfile[bashcode]{bash}{
style=manni,
bgcolor=mannibg
}

\begin{document}
\begin{center}
\bashcode{../convert-pdf-to-png}
\end{center}
\end{document}

newmintedfile

What's Next

Sometime very soon I hope to look explore minted in combination with some other tools to build on its features. I've got some examples in use right now but I need to break them out and annotate them.

CJ Harries

I did a thing once. Change "blog." to "cj@" and you've got my email. All these opinions are mine and might not be shared by clients or employers.

Read More