6

I'd like to replace all occurrences of the letter 'n' that occur inside any LaTeX equation, with the letter 'N'. It can be assumed that the LaTeX equations in the document are of the form $...$

Will accept also solutions in perl or any other language/tool/application readily available on ubuntu or Windows.

Evan Aad
  • 5,699
  • 6
  • 25
  • 36

3 Answers3

8

My solution is inspired by Tobias but uses isearch-filter-predicate for filtering instead of modifying the internals of query-replace-regexp. This needs at least Emacs 24.

Also, I only use case-sensitive search and replace for math, so I have set (case-fold-search nil) in the function below.

(defun latex-replace-in-math ()
"Call `query-replace-regexp' with `isearch-filter-predicate' set to filter out matches outside 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)))
pavel
  • 81
  • 1
  • 3
  • For replacing `n -> N`, first enter `\`, then `N` as noted in @Tobias's answer. It works great on spacemacs 0200.9 with emacs 25.3.2. Thanks a lot, this will save me a lot of time when changing the notation for a review of my paper – Fred Schoen Jan 29 '18 at 09:06
6

In emacs we can exploit the hilighting of auctex to replace the string in all math environments. (I know that this deviates from the question. But, maybe this is even more useful.) After running the code below press M-x latex-replace-in-math and input your source regular expression such as \<n\> and afterwards input your replacement string such as N. If you kept spaces between variable names then \<n\> is better than just n else also \sin will be replaced by \siN which is probably not what you want. But, this is also not so much of a problem with the code below since it query-replaces and you can skip unwanted replacements pressing `n´.

Note, that if you want to replace case-sensitive you should deactivate Options → Ignore case for search.

The user cgogolin gave me a fresh idea with his answer.

My new preferred solution is:

(fset 'latex-replace-in-math
      `(lambda (regexp to-string &optional delimited start end backward &rest _ignore)
     "Like `query-replace-regexp' but only replaces in LaTeX-math environments."
     ,(interactive-form 'query-replace-regexp)
     (let ((replace-re-search-function (lambda (regexp bound noerror)
                         (catch :found
                           (while (let ((ret (re-search-forward regexp bound noerror)))
                            (when (save-match-data (texmathp)) (throw :found ret))
                            ret))))))
       (query-replace-regexp regexp to-string delimited start end backward))))

The former more complicated version was:

(defun latex-in-math (pos)
  "Check whether pos is in math environment."
  (let ((face (plist-get (text-properties-at pos) 'face)))
    (or (eq face 'font-latex-math-face)
    (and (listp face)
         (memq 'font-latex-math-face face)))))

(defun latex-next-math-change (&optional bound stay)
  "Starting at point search for next beginning of math environment.
Place point there if STAY is nil and return point.
Else leave point where it was and return nil."
  (let ((b (point))
    (inMathB (latex-in-math (point)))
    inMath)
    (catch :found
      (while (setq b (next-single-property-change b 'face nil bound))
    (setq inMath (latex-in-math b))
    (when (or (and inMathB (null inMath))
          (and (null inMathB) inMath))
      (unless stay (goto-char b))
      (throw :found b))))))

(defun latex-replace-in-math (src tgt &optional bound)
  "In all math-environments replace SRC with TGT."
  (interactive (list (read-regexp "Source regular expression:")
             (read-string "Target string:")))
  (save-excursion
    (font-lock-fontify-region (point) (point-max)))
  (catch 'quit
    (let (b e repl)
      (when (latex-in-math (point))
    (error "point-min should not be in math environment"))
      (while (setq b (latex-next-math-change bound))
    (goto-char b)
    (while (search-forward-regexp src (latex-next-math-change bound t) 'noErr)
      (unless (eq repl 'all)
        (let ((ol (make-overlay (match-beginning 0) (match-end 0))))
          (overlay-put ol 'face 'query-replace)
          (while (null 
              (eval (cdr (assq (read-char "y: replace, n: don't, q: quit, !: replace all" '(?y ?n ?q ?!))
                       '((?y . (setq repl t))
                     (?n . (setq repl 'no))
                     (?q . (setq repl 'quit))
                     (?! . (setq repl 'all))))))))
          (delete-overlay ol))
        (when (eq repl 'quit)
          (throw 'quit nil)))
      (unless (eq repl 'no)
        (replace-match tgt)))))))
Tobias
  • 5,038
  • 1
  • 18
  • 39
  • Thank you. I've given you an up vote, but i won't mark your answer as The Answer, since it's too complicated for me to understand (due to my own unfamiliarity with Lisp). I think i'm gonna stick with a brute force approach and do a manual find-and-replace. – Evan Aad Nov 08 '13 at 06:39
  • There is really nothing to understand. Just copy the stuff to your configuration file (most probably `.emacs` in your home-directory). The stuff is then run at startup of emacs. After that you have the new command `latex-replace-in-math` available. You can call this command by the keystroke M-x then input in the minibufer 'latex-replace-in-math`. Then emacs asks you for the regular expression to replace and the substution string. Some of its advantages are: all math environments are searched. Comments are not touched (maybe you have a comment `% was $n$`. You would not want to replace that. – Tobias Nov 08 '13 at 08:10
  • Note also, that the command `latex-replace-in-math` is to be run in `latex-mode` from auctex. But this is standard if you open LaTeX-files. – Tobias Nov 08 '13 at 08:13
  • Okay, I have to correct myself. I just tested whether comments keep untouched. It is not the case. Seemingly, the font-lock of auctex is not clever enough to skip comments. But, I will add it optionally directly to `latex-replace-in-math`. It is no problem. – Tobias Nov 08 '13 at 08:19
  • No, the problem was not with auctex it was an update problem with my code. (When `latex-replace-in-math` run the commented region was not yet properly formatted.) I corrected this problem. Please, update. (Just try it and enjoy. If you do not like it you can remove the code from your configuration.) – Tobias Nov 08 '13 at 08:44
  • I think the approach `latex-replace-in-math` is definitively better than a regular expression. You certainly do not really want to replace all `n` with `N` (e.g. the `\sin`-problem). Therefore, you need `query-replace-regexp`. Furthermore, you do not want to replace only the first occurence within one formula. Therefore, you cannot rely on the leading `$` after point. But then you cannot differentiate between inside and outside of embedded math formulas anymore. Thus, we end up checking the full text. There, you will probably get thousends of `n`. – Tobias Nov 08 '13 at 11:30
  • For me it seems to work correctly only until the first accepted replace. After that it continues to replace only outside of math environments if the target string contains a \ (for example t -> \\param) or stops all together with the error `Invalid search bound (wrong side of point)` if the target doesn't contain a \ (for example t -> param). This is on GNU Emacs 24.4.1. The problem seems to occur only if the target has more than one/a different number of characters than the search reg. exp. – cgogolin Jul 30 '15 at 09:37
  • @cgogolin Thanks for the bug-report. Hopefully, the problem is fixed now. Could you try again? – Tobias Jul 30 '15 at 13:36
  • Yes, seems to work, but the new approach is far more elegant. – cgogolin Jul 30 '15 at 17:50
  • I'm on spacemacs 25.1 and get this error upon entering the expression and the replacement: `funcall-interactively: Wrong number of arguments: ` – Fred Schoen Jan 29 '18 at 08:59
  • Could you please update your answer. I get the error `funcall-interactively: Wrong number of arguments`. I have GNU Emacs 26.1. – Name Sep 12 '19 at 14:32
  • @Name Please test the modified version. – Tobias Sep 12 '19 at 21:29
  • @Tobias Thank you. I confirm that your solution now works. As many times ago I had upvoted your answer, I cannot upvote it again.There is a question on the a similar subject in https://emacs.stackexchange.com/q/52636/. In case that you are interested to post your answer also there, I would be glad to upvote you answer. I think it would be helpful to emacs stackexchange community there. – Name Sep 14 '19 at 14:55
2

After encountering the problems described in my comment to Tobias' answer, I solved this problem by adding two functions query-replace-tex-math-regexp and perform-replace-tex-math to my .emacs that work as follows:

The function query-replace-tex-math-regexp is essentially just a verbatim copy of query-replace-regexp, just that it calls perform-replace-tex-math instead of perform-replace in the last line and perform-replace-tex-math is an only slightly modified version of perform-replace with the block

;; filter out matches not in math mode.
((not (texmathp))
 (setq skip-filtered-count (1+ skip-filtered-count)))

added just before the line

;; Optionally ignore invisible matches.

texmathp is defined in AucTeX and tests whether the cursor is in a math environment, as I learned from here.

(The code for the original functions, which are licensed under the GNU General Public License, can be found here.)

The full code in my .emacs looks as follows:

(defun query-replace-tex-math-regexp (regexp to-string &optional delimited start end backward)
  "Replace some things after point matching REGEXP with TO-STRING, but only in AucTeX math mode.
As each match is found, the user must type a character saying
what to do with it.  For directions, type \\[help-command] at that time.

In Transient Mark mode, if the mark is active, operate on the contents
of the region.  Otherwise, operate from point to the end of the buffer.

Use \\<minibuffer-local-map>\\[next-history-element] \
to pull the last incremental search regexp to the minibuffer
that reads REGEXP, or invoke replacements from
incremental search with a key sequence like `C-M-s C-M-s C-M-%'
to use its current search regexp as the regexp to replace.

Matching is independent of case if `case-fold-search' is non-nil and
REGEXP has no uppercase letters.  Replacement transfers the case
pattern of the old text to the new text, if `case-replace' and
`case-fold-search' are non-nil and REGEXP has no uppercase letters.
\(Transferring the case pattern means that if the old text matched is
all caps, or capitalized, then its replacement is upcased or
capitalized.)

Ignore read-only matches if `query-replace-skip-read-only' is non-nil,
ignore hidden matches if `search-invisible' is nil, and ignore more
matches using `isearch-filter-predicate'.

If `replace-regexp-lax-whitespace' is non-nil, a space or spaces in the regexp
to be replaced will match a sequence of whitespace chars defined by the
regexp in `search-whitespace-regexp'.

Third arg DELIMITED (prefix arg if interactive), if non-nil, means replace
only matches surrounded by word boundaries.  A negative prefix arg means
replace backward.

Fourth and fifth arg START and END specify the region to operate on.

In TO-STRING, `\\&' stands for whatever matched the whole of REGEXP,
and `\\=\\N' (where N is a digit) stands for
whatever what matched the Nth `\\(...\\)' in REGEXP.
`\\?' lets you edit the replacement text in the minibuffer
at the given position for each replacement.

In interactive calls, the replacement text can contain `\\,'
followed by a Lisp expression.  Each
replacement evaluates that expression to compute the replacement
string.  Inside of that expression, `\\&' is a string denoting the
whole match as a string, `\\N' for a partial match, `\\#&' and `\\#N'
for the whole or a partial match converted to a number with
`string-to-number', and `\\#' itself for the number of replacements
done so far (starting with zero).

If the replacement expression is a symbol, write a space after it
to terminate it.  One space there, if any, will be discarded.

When using those Lisp features interactively in the replacement
text, TO-STRING is actually made a list instead of a string.
Use \\[repeat-complex-command] after this command for details."
  (interactive
   (let ((common
      (query-replace-read-args
       (concat "Query replace"
           (if current-prefix-arg
               (if (eq current-prefix-arg '-) " backward" " word")
             "")
           " regexp"
           (if (and transient-mark-mode mark-active) " in region" ""))
       t)))
     (list (nth 0 common) (nth 1 common) (nth 2 common)
       ;; These are done separately here
       ;; so that command-history will record these expressions
       ;; rather than the values they had this time.
       (if (and transient-mark-mode mark-active)
           (region-beginning))
       (if (and transient-mark-mode mark-active)
           (region-end))
       (nth 3 common))))
  (perform-replace-tex-math regexp to-string t t delimited nil nil start end backward))





(defun perform-replace-tex-math (from-string replacements
                query-flag regexp-flag delimited-flag
            &optional repeat-count map start end backward)
  "Subroutine of `query-replace'.  Its complexity handles interactive queries.
Don't use this in your own program unless you want to query and set the mark
just as `query-replace' does.  Instead, write a simple loop like this:

  (while (re-search-forward \"foo[ \\t]+bar\" nil t)
    (replace-match \"foobar\" nil nil))

which will run faster and probably do exactly what you want.  Please
see the documentation of `replace-match' to find out how to simulate
`case-replace'.

This function returns nil if and only if there were no matches to
make, or the user didn't cancel the call."
  (or map (setq map query-replace-map))
  (and query-flag minibuffer-auto-raise
       (raise-frame (window-frame (minibuffer-window))))
  (let* ((case-fold-search
      (if (and case-fold-search search-upper-case)
          (isearch-no-upper-case-p from-string regexp-flag)
        case-fold-search))
         (nocasify (not (and case-replace case-fold-search)))
         (literal (or (not regexp-flag) (eq regexp-flag 'literal)))
         (search-string from-string)
         (real-match-data nil)       ; The match data for the current match.
         (next-replacement nil)
         ;; This is non-nil if we know there is nothing for the user
         ;; to edit in the replacement.
         (noedit nil)
         (keep-going t)
         (stack nil)
         (replace-count 0)
         (skip-read-only-count 0)
         (skip-filtered-count 0)
         (skip-invisible-count 0)
         (nonempty-match nil)
     (multi-buffer nil)
     (recenter-last-op nil) ; Start cycling order with initial position.

         ;; If non-nil, it is marker saying where in the buffer to stop.
         (limit nil)

         ;; Data for the next match.  If a cons, it has the same format as
         ;; (match-data); otherwise it is t if a match is possible at point.
         (match-again t)

         (message
          (if query-flag
              (apply 'propertize
                     (substitute-command-keys
                      "Query replacing %s with %s: (\\<query-replace-map>\\[help] for help) ")
                     minibuffer-prompt-properties))))

    ;; If region is active, in Transient Mark mode, operate on region.
    (if backward
    (when end
      (setq limit (copy-marker (min start end)))
      (goto-char (max start end))
      (deactivate-mark))
      (when start
    (setq limit (copy-marker (max start end)))
    (goto-char (min start end))
    (deactivate-mark)))

    ;; If last typed key in previous call of multi-buffer perform-replace
    ;; was `automatic-all', don't ask more questions in next files
    (when (eq (lookup-key map (vector last-input-event)) 'automatic-all)
      (setq query-flag nil multi-buffer t))

    ;; REPLACEMENTS is either a string, a list of strings, or a cons cell
    ;; containing a function and its first argument.  The function is
    ;; called to generate each replacement like this:
    ;;   (funcall (car replacements) (cdr replacements) replace-count)
    ;; It must return a string.
    (cond
     ((stringp replacements)
      (setq next-replacement replacements
            replacements     nil))
     ((stringp (car replacements)) ; If it isn't a string, it must be a cons
      (or repeat-count (setq repeat-count 1))
      (setq replacements (cons 'replace-loop-through-replacements
                               (vector repeat-count repeat-count
                                       replacements replacements)))))

    (when query-replace-lazy-highlight
      (setq isearch-lazy-highlight-last-string nil))

    (push-mark)
    (undo-boundary)
    (unwind-protect
    ;; Loop finding occurrences that perhaps should be replaced.
    (while (and keep-going
            (if backward
            (not (or (bobp) (and limit (<= (point) limit))))
              (not (or (eobp) (and limit (>= (point) limit)))))
            ;; Use the next match if it is already known;
            ;; otherwise, search for a match after moving forward
            ;; one char if progress is required.
            (setq real-match-data
              (cond ((consp match-again)
                 (goto-char (if backward
                        (nth 0 match-again)
                          (nth 1 match-again)))
                 (replace-match-data
                  t real-match-data match-again))
                ;; MATCH-AGAIN non-nil means accept an
                ;; adjacent match.
                (match-again
                 (and
                  (replace-search search-string limit
                          regexp-flag delimited-flag
                          case-fold-search backward)
                  ;; For speed, use only integers and
                  ;; reuse the list used last time.
                  (replace-match-data t real-match-data)))
                ((and (if backward
                      (> (1- (point)) (point-min))
                    (< (1+ (point)) (point-max)))
                      (or (null limit)
                      (if backward
                          (> (1- (point)) limit)
                        (< (1+ (point)) limit))))
                 ;; If not accepting adjacent matches,
                 ;; move one char to the right before
                 ;; searching again.  Undo the motion
                 ;; if the search fails.
                 (let ((opoint (point)))
                   (forward-char (if backward -1 1))
                   (if (replace-search search-string limit
                               regexp-flag delimited-flag
                               case-fold-search backward)
                       (replace-match-data
                    t real-match-data)
                     (goto-char opoint)
                     nil))))))

      ;; Record whether the match is nonempty, to avoid an infinite loop
      ;; repeatedly matching the same empty string.
      (setq nonempty-match
        (/= (nth 0 real-match-data) (nth 1 real-match-data)))

      ;; If the match is empty, record that the next one can't be
      ;; adjacent.

      ;; Otherwise, if matching a regular expression, do the next
      ;; match now, since the replacement for this match may
      ;; affect whether the next match is adjacent to this one.
      ;; If that match is empty, don't use it.
      (setq match-again
        (and nonempty-match
             (or (not regexp-flag)
             (and (if backward
                  (looking-back search-string)
                (looking-at search-string))
                  (let ((match (match-data)))
                (and (/= (nth 0 match) (nth 1 match))
                     match))))))

      (cond
       ;; Optionally ignore matches that have a read-only property.
       ((not (or (not query-replace-skip-read-only)
             (not (text-property-not-all
               (nth 0 real-match-data) (nth 1 real-match-data)
               'read-only nil))))
        (setq skip-read-only-count (1+ skip-read-only-count)))
       ;; Optionally filter out matches.
       ((not (funcall isearch-filter-predicate
                          (nth 0 real-match-data) (nth 1 real-match-data)))
        (setq skip-filtered-count (1+ skip-filtered-count)))
       ;; filter out matches not in math mode.
       ((not (texmathp))
        (setq skip-filtered-count (1+ skip-filtered-count)))
       ;; Optionally ignore invisible matches.
       ((not (or (eq search-invisible t)
             ;; Don't open overlays for automatic replacements.
             (and (not query-flag) search-invisible)
             ;; Open hidden overlays for interactive replacements.
             (not (isearch-range-invisible
               (nth 0 real-match-data) (nth 1 real-match-data)))))
        (setq skip-invisible-count (1+ skip-invisible-count)))
       (t
        ;; Calculate the replacement string, if necessary.
        (when replacements
          (set-match-data real-match-data)
          (setq next-replacement
            (funcall (car replacements) (cdr replacements)
                 replace-count)))
        (if (not query-flag)
        (progn
          (unless (or literal noedit)
            (replace-highlight
             (nth 0 real-match-data) (nth 1 real-match-data)
             start end search-string
             regexp-flag delimited-flag case-fold-search backward))
          (setq noedit
            (replace-match-maybe-edit
             next-replacement nocasify literal
             noedit real-match-data backward)
            replace-count (1+ replace-count)))
          (undo-boundary)
          (let (done replaced key def)
        ;; Loop reading commands until one of them sets done,
        ;; which means it has finished handling this
        ;; occurrence.  Any command that sets `done' should
        ;; leave behind proper match data for the stack.
        ;; Commands not setting `done' need to adjust
        ;; `real-match-data'.
        (while (not done)
          (set-match-data real-match-data)
          (replace-highlight
           (match-beginning 0) (match-end 0)
           start end search-string
           regexp-flag delimited-flag case-fold-search backward)
          ;; Bind message-log-max so we don't fill up the message log
          ;; with a bunch of identical messages.
          (let ((message-log-max nil)
            (replacement-presentation
             (if query-replace-show-replacement
                 (save-match-data
                   (set-match-data real-match-data)
                   (match-substitute-replacement next-replacement
                                 nocasify literal))
               next-replacement)))
            (message message
                             (query-replace-descr from-string)
                             (query-replace-descr replacement-presentation)))
          (setq key (read-event))
          ;; Necessary in case something happens during read-event
          ;; that clobbers the match data.
          (set-match-data real-match-data)
          (setq key (vector key))
          (setq def (lookup-key map key))
          ;; Restore the match data while we process the command.
          (cond ((eq def 'help)
             (with-output-to-temp-buffer "*Help*"
               (princ
                (concat "Query replacing "
                    (if delimited-flag
                    (or (and (symbolp delimited-flag)
                         (get delimited-flag 'isearch-message-prefix))
                        "word ") "")
                    (if regexp-flag "regexp " "")
                    (if backward "backward " "")
                    from-string " with "
                    next-replacement ".\n\n"
                    (substitute-command-keys
                     query-replace-help)))
               (with-current-buffer standard-output
                 (help-mode))))
            ((eq def 'exit)
             (setq keep-going nil)
             (setq done t))
            ((eq def 'exit-current)
             (setq multi-buffer t keep-going nil done t))
            ((eq def 'backup)
             (if stack
                 (let ((elt (pop stack)))
                   (goto-char (nth 0 elt))
                   (setq replaced (nth 1 elt)
                     real-match-data
                     (replace-match-data
                      t real-match-data
                      (nth 2 elt))))
               (message "No previous match")
               (ding 'no-terminate)
               (sit-for 1)))
            ((eq def 'act)
             (or replaced
                 (setq noedit
                   (replace-match-maybe-edit
                    next-replacement nocasify literal
                    noedit real-match-data backward)
                   replace-count (1+ replace-count)))
             (setq done t replaced t))
            ((eq def 'act-and-exit)
             (or replaced
                 (setq noedit
                   (replace-match-maybe-edit
                    next-replacement nocasify literal
                    noedit real-match-data backward)
                   replace-count (1+ replace-count)))
             (setq keep-going nil)
             (setq done t replaced t))
            ((eq def 'act-and-show)
             (if (not replaced)
                 (setq noedit
                   (replace-match-maybe-edit
                    next-replacement nocasify literal
                    noedit real-match-data backward)
                   replace-count (1+ replace-count)
                   real-match-data (replace-match-data
                            t real-match-data)
                   replaced t)))
            ((or (eq def 'automatic) (eq def 'automatic-all))
             (or replaced
                 (setq noedit
                   (replace-match-maybe-edit
                    next-replacement nocasify literal
                    noedit real-match-data backward)
                   replace-count (1+ replace-count)))
             (setq done t query-flag nil replaced t)
             (if (eq def 'automatic-all) (setq multi-buffer t)))
            ((eq def 'skip)
             (setq done t))
            ((eq def 'recenter)
             ;; `this-command' has the value `query-replace',
             ;; so we need to bind it to `recenter-top-bottom'
             ;; to allow it to detect a sequence of `C-l'.
             (let ((this-command 'recenter-top-bottom)
                   (last-command 'recenter-top-bottom))
               (recenter-top-bottom)))
            ((eq def 'edit)
             (let ((opos (point-marker)))
               (setq real-match-data (replace-match-data
                          nil real-match-data
                          real-match-data))
               (goto-char (match-beginning 0))
               (save-excursion
                 (save-window-excursion
                   (recursive-edit)))
               (goto-char opos)
               (set-marker opos nil))
             ;; Before we make the replacement,
             ;; decide whether the search string
             ;; can match again just after this match.
             (if (and regexp-flag nonempty-match)
                 (setq match-again (and (looking-at search-string)
                            (match-data)))))
            ;; Edit replacement.
            ((eq def 'edit-replacement)
             (setq real-match-data (replace-match-data
                        nil real-match-data
                        real-match-data)
                   next-replacement
                   (read-string "Edit replacement string: "
                                            next-replacement)
                   noedit nil)
             (if replaced
                 (set-match-data real-match-data)
               (setq noedit
                 (replace-match-maybe-edit
                  next-replacement nocasify literal noedit
                  real-match-data backward)
                 replaced t))
             (setq done t))

            ((eq def 'delete-and-edit)
             (replace-match "" t t)
             (setq real-match-data (replace-match-data
                        nil real-match-data))
             (replace-dehighlight)
             (save-excursion (recursive-edit))
             (setq replaced t))
            ;; Note: we do not need to treat `exit-prefix'
            ;; specially here, since we reread
            ;; any unrecognized character.
            (t
             (setq this-command 'mode-exited)
             (setq keep-going nil)
             (setq unread-command-events
                   (append (listify-key-sequence key)
                       unread-command-events))
             (setq done t)))
          (when query-replace-lazy-highlight
            ;; Force lazy rehighlighting only after replacements.
            (if (not (memq def '(skip backup)))
            (setq isearch-lazy-highlight-last-string nil)))
          (unless (eq def 'recenter)
            ;; Reset recenter cycling order to initial position.
            (setq recenter-last-op nil)))
        ;; Record previous position for ^ when we move on.
        ;; Change markers to numbers in the match data
        ;; since lots of markers slow down editing.
        (push (list (point) replaced
;;;  If the replacement has already happened, all we need is the
;;;  current match start and end.  We could get this with a trivial
;;;  match like
;;;  (save-excursion (goto-char (match-beginning 0))
;;;          (search-forward (match-string 0))
;;;                  (match-data t))
;;;  if we really wanted to avoid manually constructing match data.
;;;  Adding current-buffer is necessary so that match-data calls can
;;;  return markers which are appropriate for editing.
                (if replaced
                (list
                 (match-beginning 0)
                 (match-end 0)
                 (current-buffer))
                  (match-data t)))
              stack))))))

      (replace-dehighlight))
    (or unread-command-events
    (message "Replaced %d occurrence%s%s"
         replace-count
         (if (= replace-count 1) "" "s")
         (if (> (+ skip-read-only-count
               skip-filtered-count
               skip-invisible-count) 0)
             (format " (skipped %s)"
                 (mapconcat
                  'identity
                  (delq nil (list
                     (if (> skip-read-only-count 0)
                         (format "%s read-only"
                             skip-read-only-count))
                     (if (> skip-invisible-count 0)
                         (format "%s invisible"
                             skip-invisible-count))
                     (if (> skip-filtered-count 0)
                         (format "%s filtered out"
                             skip-filtered-count))))
                  ", "))
           "")))
    (or (and keep-going stack) multi-buffer)))
cgogolin
  • 960
  • 1
  • 10
  • 22
  • 1
    Using `(texmathp)` is good (+1). But, instead of re-defining all the `query-replace` stuff you can set the variable `replace-re-search-function`. See the new part of my answer. – Tobias Jul 30 '15 at 14:48
  • Great, that seems to be the best approach. I am leaving my answer here because someone wanting to do some more general modifications of `query-replace` might still find it useful. Thanks! – cgogolin Jul 30 '15 at 17:35