3

Is there a way to make some regions of the buffer invisible (I mean not evaluated by) to query-replace and similar commands?

Let me better explain with an example. Let's take a piece of LaTeX code

... in the following equation ,
\begin{equation}
x + y = z ,
\end{equation}

I need to replace the first " ," with "," (deleting the extra space) but the second " ," string, in the equation ambient it's not a problem and I'd like my query-replace to ignore it.

I figure out I could use a loop to assign some text properties to the regions I need to ignore but I've got no idea if that kind of text properties exist.

I'm also open to any kind of suggestion.

Workaround. Following Drew's answer I wrote this code that seems to work:

(defun skip-if-my-ignore (beg end)
  "Return nil iff some text BEG to END has non-`nil' property `my-ignore'."
  (catch 'skip-if-my-ignore
    (let ((pos  beg))
      (while (< pos end)
        (when (get-text-property pos 'my-ignore) (throw 'skip-if-my-ignore nil))
        (setq pos  (1+ pos))))
    t))

(defun query-replace-skipping-math-env ()
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (search-forward-regexp "\\\\begin{equation}" nil t)
      (save-excursion
        (let ((b (copy-marker (match-beginning 0)))
              (e (copy-marker (search-forward-regexp "\\\\end{equation}" nil t ))))    
          (put-text-property b e 'my-ignore t))))
        (let ((isearch-filter-predicate  'skip-if-my-ignore))
          (perform-replace " ," "," t nil nil 1 nil (point-min) (point-max)))
        (remove-list-of-text-properties (point-min) (point-max) '(my-ignore))))

I tested it on:

... in the following equation ,
\begin{equation}
x + y = z ,
\end{equation}

... also in the following equation ,
\begin{equation}
2x + 2y = 2z ,
\end{equation}
2
  • Not sure if I understand your question., query-replace already lets you decide whether to replace each one, or do you think it's inconvenient?
    – xuchunyang
    Commented Jan 21, 2019 at 6:12
  • @xuchunyang I want query-replace to ignore some portions of the text. I need it to save time, avoiding to skip manually some queries.
    – Gabriele
    Commented Jan 21, 2019 at 11:22

2 Answers 2

3

Yes. Set variable isearch-filter-predicate to a function that returns nil for the text that you want to ignore for search and query-replace.

(Set it back to its default value of isearch-filter-visible when you're done. Alternatively, define your own search or query-replace function that binds the variable and then invokes regular Isearch or query-replace. That way it will be restored when your function is done.)

For example, you can put a text property my-ignore on some text, giving it a non-nil value. Then this function returns nil for what would normally be search hits on such text, so they are ignored for search and query-replace:

(defun skip-if-my-ignore (beg end)
  "Return nil iff some text BEG to END has non-`nil' property `my-ignore'."
  (catch 'skip-if-my-ignore
    (let ((pos  beg))
      (while (< pos end)
        (when (get-text-property pos 'my-ignore) (throw 'skip-if-my-ignore nil))
        (setq pos  (1+ pos))))
    t))

;; Make Isearch and query-replace ignore text with non-`nil' property `my-ignore'.
(setq isearch-filter-predicate  'skip-if-my-ignore)

But if you know how to identify the text you want to ignore, in order to put a text property on those parts, then you could just put that identification code into the filter predicate and not bother adding the text property. IOW, you can let the predicate check directly whether the text from BEG to END should be ignored.


There unfortunately is no Emacs doc for using isearch-filter-predicate in the Elisp manual (or the Emacs manual). But the doc string of that variable at least tells you this:

isearch-filter-predicate is a variable defined in isearch.el.

Its value is isearch-filter-visible

This variable can be risky when used as a file-local variable.

Documentation:

Predicate that filters the search hits that would normally be available.

Search hits that dissatisfy the predicate are skipped. The function has two arguments: the positions of start and end of text matched by the search. If this function returns nil, continue searching without stopping at this match.

If you use add-function to modify this variable, you can use the isearch-message-prefix advice property to specify the prefix string displayed in the search message.

That tells you (a bit obscurely) that a potential search hit is excluded (ignored) if the predicate returns nil.

BEG and END are the limits of the current (potential) search hit. The potential hit is excluded (ignored) if the predicate returns nil. In our case we presumably want it to return nil if any of the text in the potential hit has the text property.

6
  • Honestly I did not completely understand your anwer (I mean the (beg end) stuff) but I tried to apply it in my workaround. I updated my question with the code I wrote. It seems to work. Could you please confirm I've done it the right way?
    – Gabriele
    Commented Jan 20, 2019 at 22:23
  • Yes, looks good. I added the doc string of isearch-filter-predicate, which explains that args BEG and END of the function are the limits of the current (potential) search hit. The potential hit is excluded (ignored) if the predicate returns nil. In our case we presumably want it to return nil if any of the text in the potential hit has the text property.
    – Drew
    Commented Jan 20, 2019 at 23:16
  • I found that the read-only property combined with the query-replace-skip-read-only variable also works for this purpose.
    – Gabriele
    Commented Feb 26, 2019 at 23:35
  • You can provide that as an additional answer. Comments can be deleted at any time. Having that alternative answer can help readers who have the same question. (You can also accept your own answer if you prefer it. You can change which is the accepted answer at any time.)
    – Drew
    Commented Feb 26, 2019 at 23:51
  • Comment from Matteo Gamboz: "This should be a comment to @Drew answer but I don't have enough reputation to comment... You might want to use text-property-any in the predicate function. (text-property-any START END PROPERTY VALUE &optional OBJECT) Check text from START to END for property PROPERTY equaling VALUE. If so, return the position of the first character whose property PROPERTY is eq to VALUE. Otherwise return nil."
    – Drew
    Commented Oct 4, 2019 at 13:58
0

I found that the read-only text property combined with the query-replace-skip-read-only variable also works for this purpose. Here's a simplified example of my code:

(defun query-replace-skipping-math-env ()
  (interactive)
  (save-excursion
    (let ((query-replace-skip-read-only t))

      (goto-char (point-min))
      (unwind-protect
      (with-silent-modifications
        (progn
        (while (search-forward-regexp "\\\\begin{equation}" nil t)
          (save-excursion
        (let ((b (match-beginning 0))
              (e (search-forward-regexp "\\\\end{equation}" nil t)))

          (put-text-property b e 'read-only t)))))

    ;; *REPLACEMENTS*       
    (perform-replace " ," 
             "," t nil nil 1 nil (point-min) (point-max)))

    ;; *UNWINDFORMS*:
    (let ((inhibit-read-only t))
      (with-silent-modifications
        (remove-list-of-text-properties (point-min) (point-max) '(read-only))))))))

This code has the advantage of using only built-in functions and macros.

Your Answer

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

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