23

I'm writing a document where I had to change all instances of the variable i to x and the document is already considerably long. Is there a editor that will allow me to do this, or in general, replace text inside math environments?

8 Answers8

23

emacs can do this sort of thing fairly easily

(defun change-mathvar (a b)
  (interactive "sfrom: \nsto: ")
  (beginning-of-buffer)
  (while (re-search-forward
      "\\(\\\\(\\|\\\\\\[\\|[^\\\\]\$\$?\\|\\\\begin{equation}\\|\\\\begin{align}\\)" nil 1)
    (query-replace-regexp a  b t  (point) 
              (progn (re-search-forward 
                  "\\(\\\\)\\|\\\\\\]\\|[^\\\\]\$\$?\\|\\\\end{equation}\\|\\\\end{align}\\)" nil 1) (point)))))

this looks for $ \( \[ \begin{equation} \begin{align} as math-start. Other environments can be added.

Starting from a document such as

\documentclass{article}

\begin{document}


i   i   aib  


\[i   i   aib  \]


i   i   aib  


\begin{equation}
i   i   aib
\end{equation}


\end{document}

then executing M-x change-mathvar the editor will prompt for the old and new names then do a query-replace of the variable names to produce:

\documentclass{article}

\begin{document}


i   i   aib  


\[x   x   aib  \]


i   i   aib  


\begin{equation}
x   x   aib
\end{equation}


\end{document}

Note it hasn't changed anything out of math and it only changes i where it appears as a complete word, not aib. If you want aib to change as well change the t in the code to nil to make a non-delimited match.

Sean Allred
  • 27,421
David Carlisle
  • 757,742
  • 1
    I like this, but one should be aware that it fails on unusual nested constructs such as \( \sum_\text{\( p \) such that \( 2p+1 \) is prime} i^p \) when wishing to change p to q. – Andrew Swann Jul 31 '12 at 15:21
  • 1
    Yes a tradeoff between being smarter and being more complicated. In particular the fact that it doesn't choose the closing delimiter based on which opening delimiter it found makes it easier to trip it up in this way, but simplifies the emacs lisp quite a bit. When doing this though I usually try to make sure the outer loop is "good enough" to cut out the main blocks, and then rely on the fact that the inner-loop is a query-replace so you get to choose and weed out bad cases. (which is why I'd rather do it in emacs than construct a sed expression and edit the whole file without intervention – David Carlisle Jul 31 '12 at 15:26
  • 1
    I completely agree. Note however in the example I gave, the emacs function only detects the first math p; so you don't get the chance to query replace the other math p's even on a second run. – Andrew Swann Jul 31 '12 at 15:45
  • yes to make that work you'd need a version that found the outer environment (say \begin{equation} and then query-replaced all the way to a \end{equation} the version I had would stop at the first \) – David Carlisle Jul 31 '12 at 15:55
  • @DavidCarlisle To get a more robust code (with only false positives, no false negatives), there is no need to distinguish the various opening or closing delimiters: the regexp in the (progn ...) construction should count opening and closing delimiters and stop when it finds one extra close. How hard would it be? – Bruno Le Floch Aug 12 '13 at 18:12
  • @BrunoLeFloch not particularly hard but probably quite a bit more code as regexp can't count so you'd have to loop through each open or close delimiter one at a time and add +/- 1 depending. – David Carlisle Aug 12 '13 at 18:27
  • @DavidCarlisle Of course, genuine regular expressions cannot count. Are emacs regexps just regular expressions, or are they powerful enough to match nested constructs? Perl regexes can. – Bruno Le Floch Aug 13 '13 at 02:53
  • I've never seen it done but I think it may be possible to hook into whatever is being used for syntax highlighting (setting character properties etc): then assuming that your auctex mode or whatever has correctly understood what is math and what is not, the search-and-replace could go over the same already-determined-as-math text, rather than doing a regex from scratch. Edit: Ah I see this is done by Pavel's answer below, and also here. – ShreevatsaR Jul 12 '17 at 08:24
13

My solution using emacs (>=24) and auctex:

(defun latex-replace-in-math ()
"Call `query-replace-regexp' with `isearch-filter-predicate' set to only match inside LaTeX math environments."
(interactive)
(let ((isearch-filter-predicate
(lambda (BEG END)
(save-excursion (save-match-data (goto-char BEG) (texmathp)))))
(case-fold-search nil))
(call-interactively 'query-replace-regexp)))

This uses texmathp to detect math environments. Undefining case-fold-search has the effect of making the search case sensitive; this usually makes sense for variable symbols.

Usual regular expressions can be used, e.g. searching for \<i\> instead of just i avoids changing \sin to \sxn.

pavel
  • 861
8

WinEdt is enough:

  1. In the "Replace" dialog box, choose the check box "Whole words only" and "Regular expressions".

  2. Search for i\E{isMath} and Replace with x.

Then, it works. However, for expression like _i, it will also change to _x which maybe not that you want.

You can also replace text not in math environment by using i\e{isMath}.

I find this trick by searching "Regular Expressions" in the diaglog "WinEdt help". Maybe you find more useful tricks in the help.

Werner
  • 603,163
4

I once wrote a perl script to do just that. It's called MathGrep and can be obtained from https://github.com/loopspace/mathgrep. The biggest caveat is that it doesn't recognise dollars (see Are \( and \) preferable to dollar signs for math mode?), but then I also wrote a script to convert all dollars to \(...\) and \[...\] as well which is at https://github.com/loopspace/debuck.

I'm struggling to think of a use-case where this would beat David's Emacs script though ...

Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
3

What about to use TeX itself for solving this task? Write the following line at beginning of your document:

\mathcode`i=\mathcode`x
wipet
  • 74,238
2

Vim

In vim with the vimtex plugin,

:%s/beta/\=vimtex#syntax#in_mathzone() ? 'alpha' : submatch(0)/g

will replace beta by alpha only in math zones. The math zones are defined by the vimtex plugin and recognize \begin{equation}, \begin{align}}, \[, \( and $, $$ as you would expect in the following example latex document

\documentclass{amsart}

\begin{document}

This beta won't be replaced. However this one, $\beta + \gamma$ will, so will (\beta + \gamma) and $$ \beta + \gamma, $$ [ \beta + \gamma, ] \begin{equation} \beta + \gamma, \end{equation} and \begin{align} \beta + \gamma. \end{align}

\end{document}

A bit of explanation. Vim substitute command lets you use an expression instead of a string as the replacement with \=. This is documented for example at https://vim.fandom.com/wiki/Using_an_expression_in_substitute_command. The expression is evaluated at each match (in our example, at each beta in the text file). Then the vimtex plugin provides a function vimtex#syntax#in_mathzone() that checks if the current cursor is in math mode. If that's the case, we return alpha, and otherwise submatch(0) will replace the match by itself and no change will be made.

A caveat is if you would like to use the c flag with many matches. For instance

:%s/M/\=vimtex#syntax#in_mathzone() ? '\mathbf{M}' : submatch(0)/gc

will ask you to confirm the replacement not only in math mode, but also at every occurrences of M in the document including outside math mode, even though confirming the replacement outside math mode will not perform anything.

jlewk
  • 183
1

TeXstudio has a build-in search that allows to search only in certain areas, such as math environment, commands, etc. There are a couple of buttons to the right of the search field, allowing you to customize the search.

If you need more complicated searches, you can also use regular expressions.

Even though the questions has been answered, I add this answer for future reference, for people that like to use TeXstudio.

0

I have reviewed the responses suggesting Emacs as the recommended editor for this type of operation. I would like to propose a solution that, in my opinion, is a bit more comprehensive and flexible.

I have written a function to temporarily set the value of isearch-filter-predicate (it works with query-replace* functions too):

;; From AUCTeX, but it works as a standalone too:
(require 'texmathp)

(defvar-local ifp-predicates-list '(skip-maths keep-maths) "List of isearch filter predicates")

(defun skip-maths (beg end) "Return nil if some text BEG to END is in a LaTeX maths environment or in in-line maths." (catch 'skip-region (let ((pos beg)) (save-excursion (save-match-data (goto-char pos) (while (< pos end) (when (texmathp) (throw 'skip-region nil)) (goto-char (setq pos (1+ pos))))) t))))

(defun keep-maths (beg end) "Return t if some text BEG to END is NOT in a LaTeX maths environment or in in-line maths." (catch 'skip-region (let ((pos beg)) (save-excursion (save-match-data (goto-char pos) (while (< pos end) (unless (texmathp) (throw 'skip-region nil)) (goto-char (setq pos (1+ pos))))) t))))

(defun with-temp-isearch-filter-predicate-test ()
  "Temporarily assigns a predicate to the variable `isearch-filter-predicate`
in order to make portions of the buffer visible/invisible to search and replacement functions
based on specific criteria."
  (interactive)
  (save-excursion
    (let* ((IFP_DEFAULT isearch-filter-predicate)
           ;; https://emacs.stackexchange.com/q/80307/15606 :
           (set-message-function nil)
           (IFP (completing-read
                 "Specify which predicate you want to activate [TAB]: "
                 ifp-predicates-list
                 nil
                 t
                 nil
                 nil
                 nil))
           (isearch-filter-predicate (intern-soft IFP))
           ;; Non-nil means to allow minibuffer commands while in the minibuffer:
           (enable-recursive-minibuffers t))
      (read-string
       (format
        "The current value assigned to the variable `isearch-filter-predicate' is `%s'.
Press ENTER to restore the default value (`%s') at the end of the operations: "
        IFP
        IFP_DEFAULT)))))

enter image description here

I'm working on a function to combine "multiple" predicates at the same time.

Gabriele
  • 1,815