Latexindent

2017/06/04



I came across a very nice source file beautifier called latexindent which is a part of the standard distribution.

CTAN page, GitHub repo

The script should be run on the source and it would do the stuff I usually like in source files, such as wrapping, indentation of lists and environments, and so on.

The issue with source files is that everyone using formats them differently, since the processing is very forgiving. In particular, there is lots of whitespace which can be inserted to make source files more human readable. The latexindent tool would allow me to automatically standardize the source code and not think about the various ways one can format the source.

(Previously in some projects, in particular in joint ones, I have spent some time reformatting the source files to my liking; and while latexindent might not format everything how I would like, it is extremely configurable, and I can live with it because of the time it would save me.)

About this post

I am writing this post to document my process of learning latexindent, for future reference. There are two main things: configure the tool, and figure out how to call it automatically (I guess, from vim).

Note. I also learned about the existence of arara (GitHub repo) but for now I do not think I need it for now.

Issues

When setting up latexindent I found the following issues that I had to resolve manually:

Configuration


Configuration files

There is a file .indentconfig.yaml in my home folder which looks something like this:

1
2
paths:
- /path/to/my/homefolder/.vim/latexindent/latexindent.yaml

It loads the main configuration file latexindent.yaml which is in my .vim folder and it put under the git version control. It is appropriate to put the configuration file in the .vim folder since that folder has other configuration pertaining to .

The main configuration file latexindent.yaml was copied from the default configuration file and modified accordingly. The default configuration file is well-documented, but the comprehensive PDF documentation is found here on CTAN.

Multiple configurations

I would like to run the script with nothing, wrap, and remove-breaks options:

The first option does not load anything additional, and the second and the third options load additional configs which are in the same ~/.vim/latexindent/ folder. Therefore, to re-wrap run remove-breaks and then wrap configs.


Details of configuration

Here are the main things which I configure:

file extensions

This overrides the default behavior (the most recent yaml takes priority), I will only want to indent source files with .tex extension:

1
2
fileExtensionPreference:
    .tex: 1

backups

Since I am using git, I do not really need backups and will always overwrite the file. Ok, please create one backup and give it a silly extension which is already in my default .gitignore list for :

1
2
3
4
backupExtension: .mtc1
onlyOneBackUp: 1
maxNumberOfBackUps: 0
cycleThroughBackUps: 0

verbatim environments and commands

Some part of the defaults that I just keep for now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# preferences for information displayed in the log file
logFilePreferences:
    showEveryYamlRead: 1
    showAmalgamatedSettings: 0
    endLogFileWith: '--------------' 
    showGitHubInfoFooter: 1

#  verbatim environments- environments specified 
#  in this hash table will not be changed at all!
verbatimEnvironments:
    verbatim: 1
    lstlisting: 1

#  verbatim commands such as \verb! body !, \lstinline$something else$
verbatimCommands:
    verb: 1
    lstinline: 1

no wrapping of special blocks

I will use nolatexindent to mark block I do not want wrapped:

1
2
3
4
5
6
#  no indent blocks (not necessarily verbatim 
#  environments) which are marked as %\begin{noindent}
#  or anything else that the user puts in this hash
#  table
noIndentBlock:
    nolatexindent: 1

Therefore the following text will not be touched:

1
2
3
4
5
6
7
% \begin{noindent}
       this code
              won't
be touched
by
            latexindent.pl!
%\end{noindent}

whitespace

Remove trailing whitespace, nice:

1
2
3
4
# remove trailing whitespace from all lines 
removeTrailingWhitespace:
    beforeProcessing: 1
    afterProcessing: 1

This is done both before and after processing since I want wrapping to be done automatically.

preamble

Here are the default settings for not touching preamble which I keep:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# \begin{document} and \end{document} are treated differently
# by latexindent within filecontents environments
fileContentsEnvironments:
    filecontents: 1
    filecontents*: 1

# indent preamble
indentPreamble: 0

# assume no preamble in cls, sty, by default
lookForPreamble:
    .tex: 1

# some preambles can contain \begin and \end statements
# that are not in their 'standard environment block', for example,
# consider the following key = values:
#    preheadhook={\begin{mdframed}[style=myframedstyle]},
#    postfoothook=\end{mdframed},
preambleCommandsBeforeEnvironments: 0

indentation

not touching much for now

This is a quite tricky business, and I do not think I want to touch it for now. Here is the default value of indentation to be a tab:

1
2
# Default value of indentation
defaultIndent: "\t"

Also I would like the script to consider the etaremune environment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# if you want the script to look for \item commands 
# and format it, as follows (for example),
#       \begin{itemize}
#           \item content here
#                 next line is indented
#                 next line is indented
#           \item another item
#       \end{itemize}
# then populate indentAfterItems. See also itemNames
indentAfterItems:
    itemize: 1
    enumerate: 1
    etaremune: 1
    list: 1

There are many other parameters of the indentation that I will consider at some point, but will not list them for now.

sections/subsection indentation

Here is a cool feature which allows to indent all text within section or subsection etc; it is turned off for now but I can consider using it. If I want to turn it on then set indentAfterThisHeading: to 1 (and not an integer because it’s a true-false config).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# if you want to add indentation after
# a heading, such as \part, \chapter, etc
# then populate it in here - you can add 
# an indent rule to indentRules if you would 
# like something other than defaultIndent
#
# you can also change the level if you like, 
# or add your own title command
indentAfterHeadings:
    part:
       indentAfterThisHeading: 0
       level: 1
    chapter: 
       indentAfterThisHeading: 0
       level: 2
    section:
       indentAfterThisHeading: 0
       level: 3

modify line breaks

Here is the most exciting wrapping part that I will consider seriously. The indentation configuration is comprehensive and very configurable but it does not make much sense before I start working with latexindent.

condense blank lines set to (almost) off

I do not like to condense blank lines, especially in the end of the file. However, I will try to live with this feature since it makes the source better visible on the screen

1
2
3
modifyLineBreaks:
    preserveBlankLines: 1
    condenseMultipleBlankLinesInto: 1

Line breaks and wrapping

Adding the following to the configuration will wrap everything at 79 columns, precisely what I need:

1
2
3
modifyLineBreaks:
    textWrapOptions: 
        columns: 79

There is an issue however since after the vim wrapping this additional wrapping does not create full paragraphs. Therefore first one needs to delete all relevant line breaks, and wrap only then. Let’s see how it goes.

First, we need to specify where paragraphs stop:

1
2
3
4
5
6
7
8
9
10
modifyLineBreaks:
    paragraphsStopAt:
        environments: 1
        commands: 0
        ifElseFi: 0
        items: 1
        specialBeginEnd: 0
        heading: 0
        filecontents: 0
        comments: 0

Then the following removes paragraph line breaks:

1
2
    removeParagraphLineBreaks:
        all: 1

This does the job but I need to do this first and then wrap around 79 as I want. Fine.

Math delimiting

The following code under modifyLineBreaks: does a nice job separating math from text:

1
2
3
4
5
    specialBeginEnd:
        SpecialBeginStartsOnOwnLine: 0
        SpecialBodyStartsOnOwnLine: 0
        SpecialEndStartsOnOwnLine: 0 
        SpecialEndFinishesWithLineBreak: 0

I thought about doing this manually at some point but doing this with a script is way nicer. However, the formatting might not be ideal this way so I still would need to work on this. The main problem is that there might be expressions like $q$-TASEP or length-$N$ which prevent from formatting all math on separate lines. Therefore the inline math can really stay inline.

Also, SpecialEndFinishesWithLineBreak: 2 could’ve been even better but it does not work as intended at all.

Calling the script

Whenever I call the script I want it to overwrite, so use -w option. Since I will be calling it automatically I do not want terminal output, so use -s option. I also want the script to wrap my code and modify other line breaks, so use -m option, too. By default, vim does not automatically wrap anything (which is good), and the gq command wraps everything at the (probably default) length of 79. So I want latexindent to wrap the lines (with inserting new lines) at 79. Sometimes, however, I need to include other local options, so the script should be ran with -l option, too, and the script will include localSettings.yaml to override some of my system-wide defaults.

Note. There might be an issue because vim wraps after indenting and I might want to configure latexindent to wrap after indenting, too, since I want the same behavior everywhere. However, I simply better not use vim indentation anymore with this script.

Since I need to have 3 options of my own while calling the script I will perform this using .indentconfig.yaml and manipulating with it. So I’ve mapped the following hotkeys in the special filetype file /Users/leo/.vim/ftplugin/latex_latexindent.vim (so that these hotkeys only work on files). First, the hotkey for running the script not touching wrapping is Left Control + Right Option:

1
2
inoremap <C-F17> <Esc>:w<CR>:silent !latexindent -s -w -m -l -c="/Users/leo/.vim/latexindent/" %<CR>
nnoremap <C-F17> :w<CR>:silent !latexindent -s -w -m -l -c="/Users/leo/.vim/latexindent/" %<CR>

Second, the hotkey for doing the same plus rewrapping is Left Command + Right Option:

1
2
inoremap <D-F17> <Esc>:w<CR>:silent !{echo "- /Users/leo/.vim/latexindent/latexindent-remove-breaks.yaml" >> ~/.indentconfig.yaml && latexindent -s -w -m -l -c="/Users/leo/.vim/latexindent/" % && cp ~/.indentconfig-base.yaml ~/.indentconfig.yaml && echo "- /Users/leo/.vim/latexindent/latexindent-wrap.yaml" >> ~/.indentconfig.yaml && latexindent -s -w -m -l -c="/Users/leo/.vim/latexindent/" % && cp ~/.indentconfig-base.yaml ~/.indentconfig.yaml ;}<CR>
nnoremap <D-F17> :w<CR>:silent !{echo "- /Users/leo/.vim/latexindent/latexindent-remove-breaks.yaml" >> ~/.indentconfig.yaml && latexindent -s -w -m -l -c="/Users/leo/.vim/latexindent/" % && cp ~/.indentconfig-base.yaml ~/.indentconfig.yaml && echo "- /Users/leo/.vim/latexindent/latexindent-wrap.yaml" >> ~/.indentconfig.yaml && latexindent -s -w -m -l -c="/Users/leo/.vim/latexindent/" % && cp ~/.indentconfig-base.yaml ~/.indentconfig.yaml ;} <CR>

The file is saved before the latexindent script is applied.