Latex tweaks (auto-fill, breaking, align-environment, escaping underscores)

Latex is a nice language with an abominable syntax. There's some tweaks that can make writing it a bit easier, and I've collected most of those in this page. All of these require AUCTEX, so if you haven't set it up, now would be a good time.

Inhibiting auto-fill

Auto-fill is nice. Filling to 80 columns (as god intended) makes code or any other text easier to read.

However, it's not so nice for large latex tables that you want to align properly (perhaps using align). This is why I hacked together some elisp to inhibit auto-fill when the cursor is in a table (or any other environment that doesn't need to be auto-filled, according to your whims.

Defining non-auto-fillable environments

(defcustom LaTeX-inhibited-auto-fill-environments
  '("tabular" "tikzpicture") "For which LaTeX environments not to run auto-fill.")

The variable LaTeX-inhibited-auto-fill-environments contains a list of environments you want to inhibit auto-fill in. I've also added the tikzpicture environment, as lining up your TikZ P's and Q's is quite a bit easier without auto-fill interfering.

Defining a new auto-fill function

This function decides whether we are currently in an environment that is on our "auto-fill forbidden" list, and only does auto-filling if our environment is not on the "inhibited" list.

(defun LaTeX-limited-auto-fill ()
  (let ((environment (LaTeX-current-environment)))
    (when (not (member environment LaTeX-inhibited-auto-fill-environments))
      (do-auto-fill))))

Enabling our new auto-fill function

Finally, we need to replace the default auto-fill function with our new one. This can be done by adding a mode hook (following code), or enabling all the tweaks at once (last section). Care must be taken to append to the hook list (last t argument), because otherwise, auto-fill-mode will overwrite our custom auto-fill function.

(add-hook 'LaTeX-mode-hook
          (lambda () (setq auto-fill-function #'LaTeX-limited-auto-fill)) t)

Inhibit breaking on non-breaking space

Emacs has some very nice line breaking code that works correctly in most programming modes and, most of the time, in LaTeX buffers as well. We're going to improve this by forbidding line breaking on a non-breaking space (=\ =).

Fortunately, Emacs is widely engineered to be customizable. In this case, it's a simple matter to define a function that prohibits line-breaking from taking place on a non-breaking space.

Define a predicate function

The variable fill-nobreak-predicate contains a list of functions that can tell auto-fill mode to not break the line at this point. Each of the predicates is called in turn. If one predicate returns t, the line is not broken at this point.

Our predicate function therefore looks like this:

(defun LaTeX-dont-break-on-nbsp ()
  (and (eq major-mode 'latex-mode)
       (eq (char-before (- (point) 1)) ?\\)))

Since the function is global, we need to check for Latex mode as well. Then it's a simple matter to check whether there's a backspace before the space, and return true if there is.

Enabling the predicate function

We'll just enable the function globally. We could also enable it only in latex mode, but I don't think that's worth the trouble.

(add-to-list 'fill-nobreak-predicate #'LaTeX-dont-break-on-nbsp)

Aligning the whole environment

This is a fun one. Remember creating your last table in latex? Remember going through the table looking for missing ampersands? Well, no more. Enter LaTeX-align-environment!

What it does is collapse your table by removing extraneous whitespace from cells, and then align all ampersands.

This allows you to quickly find missing or superfluous ampersands, making writing a table quicker and easier.

First, we define a helper function to collapse a table.

Collapsing the table

A latex table needs to contain spaces to be aligned. However, when aligning, we want to remove all those extra spaces that we've added for alignment. A simple rule is that we want one space after the ampersand, and then one space after the last non-space character in the cell.

A properly collapsed table:

\begin{tabular}{lrr}
  Left & Right & Right long \\
  Left long & Right long & Right \\
\end{tabular}

This is compact, but doesn't really allow you to quickly scan across cells to find missing ampersands. An aligned table, however, does:

\begin{tabular}{lrr}
  Left      & Right      & Right long \\
  Left long & Right long & Right      \\
\end{tabular}

As you can see, the table is aligned, and we can easily see which column a value is part of. We define a function that can do this by running a regex over our table.

(defun LaTeX-collapse-table ()
  (interactive)
  (save-excursion
    (LaTeX-mark-environment)
    (while (re-search-forward "[[:space:]]+\\(&\\|\\\\\\\\\\)" (region-end) t)
      (replace-match " \\1"))))

This marks the current environment, and replaces all spaces before ampersands or newlines (double backslash) with a single space. We don't have lookahead in Elisp, so we need to save our last character (ampersand or double backslash) in a group. In a sane regex syntax, the expression would be "\s+(&|\\\\)", but Emacs' regexes are… special.

Aligning the environment

Aligning a table that's collapsed is easy. Just mark the environment using LaTeX-mark-environment, and align it using align, as done by the following function.

(defun LaTeX-align-environment (arg)
  (interactive "P")
  (if arg
      (LaTeX-collapse-table)
    (save-excursion
      (LaTeX-mark-environment)
      (align (region-beginning) (region-end)))))

We're also calling our collapse function on a prefix argument, as this lends itself nicely to following workflow:

  1. Collapse table
  2. Edit data
  3. Align table

Enable key

Finally, either do it all at once, or bind it to a key directly.

(add-hook 'latex-mode-hook
          (lambda () (local-set-key (kbd "C-c f") #'LaTeX-align-environment)))

Escaping underscores

The underscore is an annoying beast in TeX. Typing it anywhere except in math mode will give you an error, unless you escape it with a backslash. We can do that automatically with the following quick fix!

(defun LaTeX-underscore-maybe (arg)
  (interactive "p")
  (if (eq last-command 'LaTeX-underscore-maybe)
      (progn
        (delete-backward-char 2)
        (self-insert-command 1))
    (if (or (or (> 1 arg) (texmathp)))
        (self-insert-command 1)
      (insert "\\_"))))

This inserts an escaped underscore if not in math mode, and an unescaped one if in math mode. I've also added a convenience feature here: If you want an unescaped underscore in regular mode, simply enter two consecutive underscores. This will replace the escaped underscore with an unescaped one.

Finally, enable it (or do it all at once in the next section).

(add-hook 'latex-mode-hook (lambda () (local-set-key (kbd "_") #'LaTeX-underscore-maybe)))

Enabling everything

Wow, that's a lotta tweaks! If you want to enable all of these (as recommended), define a new function that does all the things we've done in λs in the previous section.

Lambda functions shouldn't be used with add-hook anyway, because they're not easy to remove from the hook list, whereas "proper" named functions can be removed with remove-hook.

(add-to-list 'fill-nobreak-predicate #'LaTeX-dont-break-on-nbsp)

(defun LaTeX-init-tweaks ()
  (local-set-key (kbd "_" #'LaTeX-underscore-maybe))
  (local-set-key (kbd "C-c f") #'LaTeX-align-environment)
  (setq auto-fill-function #'LaTeX-limited-auto-fill))

(add-to-list 'LaTeX-mode-hook '#LaTeX-init-tweaks)

Done, and done.

Finally

As always, if there's any questions, don't hesitate to contact me!

Date: 2017-09-05

Author: Jan Seeger <blogcomments@thenybble.de>

Email: sitecomments@thenybble.de

Validate