While editing a latex document it occasionally happens that I need to transform inline math (i.e. math between $ ... $ or \( ... \) delimiters) into display math (I only use align, align*, displaymath or equation). For example this often happens after compiling and noticing that some equation or definition looks ugly inline or causes an overfull hbox. And we all know must not have overfull hboxes in ones document. Otherwise unimaginably horrible things happen. Another use case is when I want to change \[ ... \] into, say align or equation environment when I need to refer to it later on.

Anyhow, I usually use AUCTeX‘s C-c C-e shortcut which asks for an environment and inserts it. An annoyance with the default behaviour is that it does not remove the old delimiters and therefore I need to manually remove them. Luckily this behaviour is not difficult to fix with a little bit of elisp.

What I would like to happen is mark the whole expression and execute C-c C-e. This should insert the desired environment and clean up any of the old math delimiters that might have been there.

Now let us analyze how we would do this. First we find out that C-c C-e shortcut executes the LaTeX-environment command, which takes care of asking the user for the environment name and is otherwise doing mostly bookkeeping. The function that actually does the insertion is the function LaTeX-environment-menu. It takes one argument, which is the environment name, and inserts \begin{env} \end{env} either around point or around the region, if it is selected.

So what we shall do is we shall advise the LaTeX-environment-menu to clean up math delimiters before inserting the environment, in case a region is selected.

We need some scaffolding first. These two auxiliary functions jump to the first/last non-whitespace character in a region.

(defun first-nsp-after (p)
  "Go up to first non-whitespace character *after* the given position."q
  (goto-char p)
  (skip-chars-forward " \n\r"))

(defun first-nsp-before (p)
  "Go to just after the first non-whitespace char *before* the given position."
  (goto-char p)
  (skip-chars-backward " \n\r"))

The function that does the main work is the function latex/remove-math-delimiters.

(defun latex/remove-math-delimiters (beg end)
  "Remove delimiters $ ... $, \[ ... \] and/or \( \) in an active
region (if called interactively, otherwise provide a region."
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (first-nsp-after (point-min))
      (cond ((equal (char-after) (string-to-char "$")) (delete-char 1))
            ((looking-at-p "\\\\\\[") (delete-char 2))
            ((looking-at-p "\\\\(") (delete-char 2)))
      
      (first-nsp-before (point-max))
      ;; if the last char is a comma or full stop try skipping it and clearing $
      ;; and \] before it
      ;; This is useful when the formula that was inlined and needs to be displayed
      ;; was at the end of the sentence or clause.
      (when (member (char-to-string (char-before)) '("." ","))
        (backward-char))
      (cond ((equal (char-before) (string-to-char "$")) (delete-char -1))
            ((looking-back "\\\\\\]") (delete-char -2))
            ((looking-back "\\\\)") (delete-char -2))))))

The function is pretty straightforward. All the modifications are wrapped in save-excursion and save-restriction macros so that the point and the restriction (the visible portion of the buffer) is restored to what it was before we did the modifications.

Inside we first narrow the area we have to work with to the region we are given. Then we find the first non-blank character and if at that place we are looking at the start of a math environment we remove it. Then we do the same for the ending.

Finally, all of this is tied together by advising the function LaTeX-environment-menu that actually inserts \begin{...} ... \end{...} to call the function latex/remove-math-delimiters before inserting environment delimiters. This is done as follows.

(advice-add 'LaTeX-environment-menu :before
            (lambda (env)
              (when (and (use-region-p)
                         (or (member env (list "align"
                                               "align*"
                                               "displaymath"
                                               "equation"
                                               "equation*"))))
                (let ((deactivate-mark nil)) ;; in order to prevent deactivation
                                             ;; of transient-mark-mode. Relies
                                             ;; essentially on the fact that
                                             ;; `deactivate-mark' is dynamically
                                             ;; bound.
                  (latex/remove-math-delimiters (region-beginning) (region-end))))))

Adding a before advice to a function f replaces the function f with the sequencing of f and the function ϕ given in the advice: first comes ϕ is called and then f.

In our case the function ϕ given first checks whether the region is active (with use-region-p function) and whether we are inserting an environment that is one of the math environments where we want to remove math delimiters. If it is then it calls latex/remove-math-deilmiters on the region.

And that’s it. Now C-c C-e cleans up the old delimiters when it makes sense to do so. Wasn’t that easy?

Here is an example of the improved environment insertion in action.

Improved environment insertion

Now, some smart aleck will surely point out that this does not work correctly in all cases. For instance, if we now call LaTeX-environment when the selected region is \$ then we will end up with, for example, \begin{equation} \ \end{equation} which is not what we want. This problem can easily be fixed by making latex/remove-math-delimiters smarter, but since this case never occurs in practice for me I do not care. Especially since it is probably impossible to make it correct, because in TeX one can just redefine $ to not have anything to do with entering math-mode at all.

The point is to streamline the common operations and my modification achieves just that.