5

(query-replace "foo" "bar") shows you all occurrences of foo and offers to replace them by bar.

I would love to have a version of query-replace that only picks the occurrences of foo that are "in some context" (to be defined). At the moment, I'm thinking of LaTeX files with the context being "any part that is in math style". Or any part not in math style. These parts are already highlighted by Emacs' latex-mode so it can recognize them.

query-replace can already perform a bit of context checking since its DELIMITED argument allows restricting to occurrences surrounded by word boundaries, but this is a simple special case that can be implemented with regular expressions, e.g., looking for \<foo\>.

My question: do you know of an extension of query-replace in some package somewhere that can do the replace-only-in-math-style trick I need? Or of something similar enough that would be a good start for tweaking and hacking around?

5
  • 2
    I don't know any such tool, but if you want to write it you may want to give a look at texmathp.el (it's bundled into AUCTeX but doesn't require it to work), which provides you the function texmathp to check whether point is inside math mode.
    – giordano
    Nov 8, 2015 at 14:16
  • Thanks a million. I've followed your advice and looked at texmathp. It seems that I just have to make a copy of the original query-replace and insert a (if (texmathp) ..) at the right place. I'll try that and report on the outcome.
    – phs
    Nov 8, 2015 at 14:51
  • Now that I know about the isearch-filter-predicate variable I have set it to something that calls texmathp. It is annoying that they do not have the same interface: isearch-filer-predicate wants a (beg end) region argument, texmathp looks at point in buffer. I'd suggest just allowing an optional pos argument to texmathp.
    – phs
    Nov 8, 2015 at 20:43
  • @phs Did you manage to bind "isearch-filter-predicate to something that calls texmathp"? I am also interested in this approach. Mar 4, 2019 at 22:08
  • @Gabriele. Yes, see my code at http://www.lsv.fr/~phs/query-replace-in-tex-math-only.el
    – phs
    Mar 5, 2019 at 18:03

2 Answers 2

1

Yes. If you use libraries Isearch+ and Zones (including optional library isearch-prop.el), and you set option isearchp-query-replace-zones-flag. to non-nil, then replacement commands such as query-replace are limited to the buffer zones you define.

This works by binding isearch-filter-predicate to a function that recognizes the zones.

(Isearch works the same way - you can limit searching to defined zones. And if you use Bookmark+ then you can bookmark a set of zones.)

You can also query-replace (or search) the complement of a set of zones. Toggle this behavior using C-M-~ during Isearch.

1
  • Thanks for the info about the libraries and the explanations. For the time I am happy with just binding isearch-filter-predicate to something that calls texmathp but I understand that your solution is more generic.
    – phs
    Nov 8, 2015 at 20:46
1

Although this question is a few years old, I think I might be able to contribute with one more answer. Below is the code for a function called math-hilite which hides normal text and highlights everything in math style. Moreover it sets a read-only property on hidden text and tells isearch-filter-predicate to ignore hidden text so that any further search or replacement is done only within math text.

With prefix argument reverse, math is hidden instead.

After you are done searching and replacing, call math-hilite once more to bring your buffer back to its normal state.

By redefining the variable envir-bdry, all sorts of other non-TeX applications are possible.

(require 'cl-lib)

(defun next-math-environment (start)
  "Assuming start is NOT inside a math environment, we return a
  list (b1 a1 a2 b2) with information regarding the next math
  environment after start: (buffer-substring a1 a2) is the
  environment itself, and (buffer-substring b1 b2) is the
  environment including the tokens marking the environment.
  If a math environment is malformed, that is, it
  extends beyond the end of the paragraph, then we report the end
  of the paragraph in both a2 and b2.  This function could be
  defined with texmathp, but it becomes much slower."
  (unless (boundp 'envir-bdry)
    (setq envir-bdry
      `(
        (,(regexp-quote "$$")                  . ,(regexp-quote "$$"))
        (,(regexp-quote "$")                   . ,(regexp-quote "$"))
        (,(regexp-quote "\\[")                 . ,(regexp-quote "\\]"))
        (,(regexp-quote "\\(")                 . ,(regexp-quote "\\)"))
        ("\\\\begin *{math}"                   . "\\\\end *{math}")
        ("\\\\begin *{align\\*?}"              . "\\\\end *{align\\*?}")
        ("\\\\begin *{gather\\*?}"             . "\\\\end *{gather\\*?}")
        ("\\\\begin *{equation}"               . "\\\\end *{equation}")
        ))
    (setq bdry-regexp (mapconcat (lambda (x) (format "\\(%s\\)" (car x))) envir-bdry "\\|")))
  (save-excursion (let (s paragraph-end
        (paragraph-start "\n[[:space:]]*\n"))
      (goto-char start)
      (when (search-forward-regexp bdry-regexp nil t)                                        ;; Search for the opening of a math environment
        (setq
          s (cl-loop for i from 1 if (match-string i) return (cdr (nth (1- i) envir-bdry)))  ;; s is the corresponding closing string
          paragraph-end (save-match-data (save-excursion (forward-paragraph) (point))))      ;; paragraph-end will bound the search for the closing string
        (append
            (list (match-beginning 0) (match-end 0))                                         ;; First part of the output
            (if (search-forward-regexp s paragraph-end t)                                    ;; Search for the closing string
              (list (match-beginning 0) (match-end 0))                                       ;; Last part of the output if closing string is found
              (list paragraph-end paragraph-end)))))))                                       ;; Malformed math environment. Cannot find end of environment whithin paragraph.
                                                                                             ;;   so return paragraph-end in last 2 slots

(defun math-hilite (&optional reverse)
  "Hide non-math TeX for purpose of searching and replacing,
  putting a 'read-only property on hiddent text.  If reverse is
  non-nil then hide math instead.  Call it a second time to cancel its effect"
  (interactive "P")
  (unless (boundp 'math-hid) (setq-local math-hid nil))                                     ;; This variable informs the state of the buffer
  (with-silent-modifications                                                                ;; Don't consider changing text properties as modifications
    (if (null math-hid)                                                                     ;; Buffer state is not altered.  Hide what must be hidden
      (let (nme
          text-state math-state
          (start (point-min))
          (hid (list 'face (cons 'foreground-color "grey20") 'read-only t))                 ;; Properties for hidden stuff
          (div (list 'face (cons 'foreground-color "salmon") 'read-only t))                 ;; Properties for separators
          )
        (setq-local
          math-hid t                                                                        ;; Set buffer as altered
          isearch-filter-predicate (lambda (x y) (null (get-text-property x 'read-only))))  ;; Set predicate for later search operations
        (if (null reverse )
          (setq text-state hid math-state nil)                                              ;; Hide normal text and show math (default)
          (setq text-state nil math-state hid)                                              ;; Hide math and show normal text
          )
        (font-lock-mode -1)
        (set-text-properties (point-min) (point-max) text-state)                            ;; Set whole buffer with text-state
        (while (setq nme (next-math-environment start))
          (set-text-properties (nth 0 nme) (nth 1 nme) div)                                 ;; Set opening boundary with div properties
          (set-text-properties (nth 1 nme) (nth 2 nme) math-state)                          ;; Set math environment with math-state
          (set-text-properties (nth 2 nme) (nth 3 nme) div)                                 ;; Set closing boundary with div properties
          (let ((stk (nth (if reverse 3 1) nme)))
            (put-text-property (1- stk) stk 'rear-nonsticky t))                             ;; Fix sticky issue
          (setq start (nth 3 nme)))                                                         ;; Get ready for seraching next math environment
        (save-excursion
          (goto-char (point-min))
          (while (search-forward-regexp "%" nil t)
            (set-text-properties (match-beginning 0) (line-end-position) hid))))            ;; Hide comments
      (set-text-properties (point-min) (point-max) nil)                                     ;; ELSE, when math-hid is t just reset everything
      (font-lock-mode 1)
      (setq-local isearch-filter-predicate 'isearch-filter-visible)
      (setq-local math-hid nil)
      )))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.