3

In a previous question, Make region(s) invisible (not evaluated) to query-replacy and similar commands, I learned about isearch-filter-predicate. Now, I wonder if is there a similar method to make regions invisible to functions like string-match, occur etc.

For example I have this LaTeX code:

\begin{eqnarray}
\begin{matrix}
\newline
\end{matrix}
\end{eqnarray}

I use string-match-p to test if the \newline (\\) string occurs in the LaTeX eqnarray environment. If nil I replace "eqnarray" --> "equation". I also need to "ignore" newlines that occur in sub-enviroments like matrix, so I thought I could make the regions of these sub-enviroments "invisible" to the string-match-p function.

Is there a way to do it? Alternatively I thought I could write the isearch-filter-predicate combined with the search-forward-regexp function to test for the occurrence of my target string in the region.

This is the code I wrote:

(defun skip-if-my-ignore (beg end)
  "Return nil if 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 test-invisibility ()
  (interactive)
  (save-excursion
    (let* ((a (copy-marker (point-min)))

       (SUB_ENVIRONMENTS (regexp-opt '(
                       "array"
                       "cases"
                       "pmatrix"
                       "matrix"
                       ))))

      (unwind-protect
      (progn
        (goto-char a)
        (while (search-forward-regexp (concat
                       "\\\\begin{"
                       "\\(" SUB_ENVIRONMENTS "\\)"
                       "}")
                      nil t)
          (save-excursion
        (let* ((b (copy-marker (match-beginning 0)))
               (SUB_ENV (match-string 1))
               (e (copy-marker (search-forward-regexp
                    (concat "\\\\end{" SUB_ENV "}") nil t))))

          (put-text-property b e 'my-ignore t)

          (save-restriction
            (narrow-to-region b e)

            (let ((isearch-filter-predicate  'skip-if-my-ignore))

              ;; should not ask for replacements (in sub-environments)
              (perform-replace "\\\\newline" "MATCHED" t t nil 1 nil b e) 

              (if (string-match-p "\\\\newline" (buffer-substring b e))
              (read-string "string-match-p matched the string. I don't want this!")

            (read-string "string-match-p didn't match the string. I WANT THIS!"))

              (if (search-forward-regexp "\\\\newline" e t)
              (read-string "search-forward-regexp matched the string. I don't want this!")

            (read-string "search-forward-regexp didn't match the string. I WANT THIS!"))

              ;; should not find occurrences in the sub-environments:
              (occur "\\\\newline" 1))))))))
    ;; *UNWINDFORMS
    (remove-list-of-text-properties (point-min) (point-max) '(my-ignore))))))
Gabriele Nicolardi
  • 1,199
  • 8
  • 17
  • 1. You could simplify the code you present here, to make it easier. 2. Why would you expect `string-match(-p)` or `occur` to be sensitive to `isearch-filter-predicate`? They are not, nor are functions such as `re-search-forward` (used by `occur-1`). Isearch and `perform-replace` are sensitive to it. – Drew Feb 13 '19 at 00:14
  • You can check for your text property during your scan of the buffer (your `test...`). Something like that is what you'll need to do. You could define a function that does only that and use that in other code. See `next-single-char-property-change`, for a start. – Drew Feb 13 '19 at 00:18
  • @Drew I know I can use an alternative method to test for text properties on the region. I was only wondering if there was a system to hide some portions of the buffer to `string-match` and similar functions. This would make my job easier. – Gabriele Nicolardi Feb 13 '19 at 07:02
  • Not with `isearch-filter-predicate`, to my knowledge anyway. But perhaps someone will post a relevant and helpful answer for you. – Drew Feb 13 '19 at 16:28
  • You can modify the buffer temporarily for the execution of a function [with `cmdbufmod`](https://emacs.stackexchange.com/a/41924/2370). You could look for an `eqnarray` environment, restrict the buffer to that region, delete the sub-environments in the entries of `BUFMOD-LIST` of `cmdbufmod`, run your command, and leave `cmdbufmod` restoring the deleted sub-environments. – Tobias Feb 15 '19 at 14:45
  • @Tobias Thank you for you comment. Up to now I have used a strategy similar to the one you described. This strategy has the side effect that an `undo` alters the contents of the buffer. I'd like to avoid this problem. Indeed I'd like to operate only on text properties instead on text. – Gabriele Nicolardi Feb 15 '19 at 18:19
  • @Drew, I would appreciate it very much if you could take a look at my own answer. I was thinking of sending it as a patch or as a feature request. – Gabriele Nicolardi Mar 11 '19 at 22:01
  • You should always feel free to use `M-x report-emacs-bug` to make a bug report or an enhancement request (e.g. make non-interactive search functions respect `isearch-filter-predicate` or whatever). But someone might give a good reason why they intended it only for interactive search (dunno). – Drew Mar 11 '19 at 22:30
  • Didn't notice anything obviously amiss with your code. I assume you tested with the various optional args etc. A few of the functions have almost identical code - dunno whether you want to factor out the common part. If you submit your code as a patch in a bug report the Emacs developers will anyway take a close look. If they decide that `isearch-filter-predicate` should be respected by such functions then they might want to make the change in C, where some of the functions are defined now. – Drew Mar 11 '19 at 22:37

1 Answers1

0

I unexpectedly found that also how-many, search-forward, re-search-forward and their "backward" versions are not sensitive to isearch-filter-predicate. So I wrote a modified version of these functions that are sensitive to isearch-filter-predicate:

(defun search-forward-ifp (STRING &optional BOUND NOERROR COUNT)
  "Modified version of `search-forward' that filters (skips)
matches according to `isearch-filter-predicate'."

  (let ((POINT (point)))
    (catch 'filtered
      (while (search-forward STRING BOUND NOERROR COUNT)
        (let ((B (match-beginning 0))
              (E (match-end 0)))
          (when (funcall isearch-filter-predicate B E)
            (throw 'filtered (point)))))
      (goto-char POINT)
      nil)))

(defun search-backward-ifp (STRING &optional BOUND NOERROR COUNT)
  "Modified version of `search-backward' that filters (skips)
matches according to `isearch-filter-predicate'."

  (let ((POINT (point)))
    (catch 'filtered
      (while (search-backward STRING BOUND NOERROR COUNT)
        (let ((B (match-beginning 0))
              (E (match-end 0)))
          (when (funcall isearch-filter-predicate B E)
            (throw 'filtered (point)))))
      (goto-char POINT)
      nil)))

(defun re-search-forward-ifp (REGEXP &optional BOUND NOERROR COUNT)
  "Modified version of `search-forward-regexp' that
filters (skips) matches according to `isearch-filter-predicate'."

  (let ((POINT (point)))
    (catch 'filtered
      (while (search-forward-regexp REGEXP BOUND NOERROR COUNT)
        (let ((B (match-beginning 0))
              (E (match-end 0)))
          (when (funcall isearch-filter-predicate B E)
            (throw 'filtered (point)))))
      (goto-char POINT)
      nil)))
(defalias 'search-forward-regexp-ifp 're-search-forward-ifp)

(defun re-search-backward-ifp (REGEXP &optional BOUND NOERROR COUNT)
  "Modified version of `search-backward-regexp' that
filters (skips) matches according to `isearch-filter-predicate'."

  (let ((POINT (point)))
    (catch 'filtered
      (while (search-backward-regexp REGEXP BOUND NOERROR COUNT)
        (let ((B (match-beginning 0))
              (E (match-end 0)))
          (when (funcall isearch-filter-predicate B E)
            (throw 'filtered (point)))))
      (goto-char POINT)
      nil)))
(defalias 'search-backward-regexp-ifp 're-search-backward-ifp)

(defun how-many-ifp (regexp &optional rstart rend interactive)
  "Modified version of `how-many' that filters (skips) matches
according to `isearch-filter-predicate'."

  (interactive
   (keep-lines-read-args "How many matches for regexp"))
  (save-excursion
    (if rstart
        (if rend
            (progn
              (goto-char (min rstart rend))
              (setq rend (max rstart rend)))
          (goto-char rstart)
          (setq rend (point-max)))
      (if (and interactive (use-region-p))
          (setq rstart (region-beginning)
                rend (region-end))
        (setq rstart (point)
              rend (point-max)))
      (goto-char rstart))
    (let ((count 0)
          opoint
          (case-fold-search
           (if (and case-fold-search search-upper-case)
               (isearch-no-upper-case-p regexp t)
             case-fold-search)))
      (while (and (< (point) rend)
                  (progn (setq opoint (point))
                         (re-search-forward-ifp regexp rend t)))
        (if (= opoint (point))
            (forward-char 1)
          (setq count (1+ count))))
      (when interactive (message "%d occurrence%s"
                                 count
                                 (if (= count 1) "" "s")))
      count)))
(defalias 'count-matches-ifp 'how-many-ifp)

I think that something similar could be done with the occur function but I haven't tried it yet.

Gabriele Nicolardi
  • 1,199
  • 8
  • 17