Subject: bug#11746: feature request: `isearch-query-replace' should open invisible text

From: Juri Linkov <[email-masked]>
Date: Thu, 14 Feb 2013 21:02:15 +0200

> if you have `search-invisible' non-nil, isearch "opens" invisible
> text.  But when you hit M-% or C-M-% while searching, you loose this
> ability: point will just be put inside invisible areas, and you don't
> see what you're doing.

The recent adaptation of isearch functions to `perform-replace'
makes it easy to implement this now.

Depending on the value of `search-invisible', if `open' then M-% or C-M-%
will open invisible overlays, if `nil' then they will skip invisible text,
and if `t' they will match replacements inside invisible areas like now.

The key point is using a search loop like in `isearch-search' that
either opens invisible overlays or skips them.

BTW, a similar loop could be later added to occur and hi-lock too,
but since in occur and hi-lock it makes no sense to open overlays,
they should just skip invisible areas like in `isearch-lazy-highlight-search'.

The patch below moves the search related part of code from `perform-replace'
to a new function `replace-search', adds a loop like in `isearch-search',
and moves handling of `query-replace-skip-read-only' to this loop.

It's worth to note that `wdired-isearch-filter-read-only' already has
exactly the same condition that checks for read-only-ness, so the
same condition is duplicated for modes that set both a read-only-skipping
isearch filter and `query-replace-skip-read-only', but this is not a problem.

=== modified file 'lisp/replace.el'
--- lisp/replace.el	2013-02-01 23:38:41 +0000
+++ lisp/replace.el	2013-02-14 18:55:14 +0000
@@ -1794,6 +1796,54 @@ (defvar replace-re-search-function nil
 It is called with three arguments, as if it were
 `re-search-forward'.")
 
+(defun replace-search (search-string limit regexp-flag delimited-flag
+				     case-fold-search)
+  "Search the next occurence of SEARCH-STRING to replace."
+  ;; Let-bind global isearch-* variables to values used
+  ;; to search the next replacement.  These let-bindings
+  ;; should be effective both at the time of calling
+  ;; `isearch-search-fun-default' and also at the
+  ;; time of funcalling `search-function'.
+  ;; These isearch-* bindings can't be placed higher
+  ;; outside of this function because then another I-search
+  ;; used after `recursive-edit' might override them.
+  (let* ((isearch-regexp regexp-flag)
+	 (isearch-word delimited-flag)
+	 (isearch-lax-whitespace
+	  replace-lax-whitespace)
+	 (isearch-regexp-lax-whitespace
+	  replace-regexp-lax-whitespace)
+	 (isearch-case-fold-search case-fold-search)
+	 (isearch-adjusted nil)
+	 (isearch-nonincremental t)	; don't use lax word mode
+	 (isearch-forward t)
+	 (search-function
+	  (or (if regexp-flag
+		  replace-re-search-function
+		replace-search-function)
+	      (isearch-search-fun-default)))
+	 (retry t)
+	 (success nil))
+    ;; Use a loop like in `isearch-search'.
+    (while retry
+      (setq success (funcall search-function search-string limit t))
+      ;; Clear RETRY unless the search predicate says
+      ;; to skip this search hit.
+      (if (or (not success)
+	      (and (run-hook-with-args-until-failure
+		    'isearch-filter-predicates
+		    (match-beginning 0) (match-end 0))
+		   (or (eq search-invisible t)
+		       (not (isearch-range-invisible
+			     (match-beginning 0) (match-end 0))))
+		   ;; Optionally ignore matches that have a read-only property.
+		   (or (not query-replace-skip-read-only)
+		       (not (text-property-not-all
+			     (match-beginning 0) (match-end 0)
+			     'read-only nil)))))
+	  (setq retry nil)))
+    success))
+
 (defun perform-replace (from-string replacements
 		        query-flag regexp-flag delimited-flag
 			&optional repeat-count map start end)
@@ -1881,29 +1931,6 @@ (defun perform-replace (from-string repl
 	;; Loop finding occurrences that perhaps should be replaced.
 	(while (and keep-going
 		    (not (or (eobp) (and limit (>= (point) limit))))
-		    ;; Let-bind global isearch-* variables to values used
-		    ;; to search the next replacement.  These let-bindings
-		    ;; should be effective both at the time of calling
-		    ;; `isearch-search-fun-default' and also at the
-		    ;; time of funcalling `search-function'.
-		    ;; These isearch-* bindings can't be placed higher
-		    ;; outside of this loop because then another I-search
-		    ;; used after `recursive-edit' might override them.
-		    (let* ((isearch-regexp regexp-flag)
-			   (isearch-word delimited-flag)
-			   (isearch-lax-whitespace
-			    replace-lax-whitespace)
-			   (isearch-regexp-lax-whitespace
-			    replace-regexp-lax-whitespace)
-			   (isearch-case-fold-search case-fold-search)
-			   (isearch-adjusted nil)
-			   (isearch-nonincremental t) ; don't use lax word mode
-			   (isearch-forward t)
-			   (search-function
-			    (or (if regexp-flag
-				    replace-re-search-function
-				  replace-search-function)
-				(isearch-search-fun-default))))
 		      ;; Use the next match if it is already known;
 		      ;; otherwise, search for a match after moving forward
 		      ;; one char if progress is required.
@@ -1916,8 +1943,9 @@ (defun perform-replace (from-string repl
 				  ;; adjacent match.
 				  (match-again
 				   (and
-				    (funcall search-function search-string
-					     limit t)
+				  (replace-search search-string limit
+						  regexp-flag delimited-flag
+						  case-fold-search)
 				    ;; For speed, use only integers and
 				    ;; reuse the list used last time.
 				    (replace-match-data t real-match-data)))
@@ -1930,13 +1958,12 @@ (defun perform-replace (from-string repl
 				   ;; if the search fails.
 				   (let ((opoint (point)))
 				     (forward-char 1)
-				     (if (funcall
-					  search-function search-string
-					  limit t)
-					 (replace-match-data
-					  t real-match-data)
+				   (if (replace-search search-string limit
+						       regexp-flag delimited-flag
+						       case-fold-search)
+				       (replace-match-data t real-match-data)
 				       (goto-char opoint)
-				       nil)))))))
+				     nil))))))
 
 	  ;; Record whether the match is nonempty, to avoid an infinite loop
 	  ;; repeatedly matching the same empty string.
@@ -1957,13 +1984,6 @@ (defun perform-replace (from-string repl
 			      (let ((match (match-data)))
 				(and (/= (nth 0 match) (nth 1 match))
 				     match))))))
-
-	  ;; Optionally ignore matches that have a read-only property.
-	  (unless (and query-replace-skip-read-only
-		       (text-property-not-all
-			(nth 0 real-match-data) (nth 1 real-match-data)
-			'read-only nil))
-
 	    ;; Calculate the replacement string, if necessary.
 	    (when replacements
 	      (set-match-data real-match-data)
@@ -2168,7 +2188,7 @@ (defun perform-replace (from-string repl
 				 (match-end 0)
 				 (current-buffer))
 			      (match-data t)))
-		      stack)))))
+		    stack))))
 
       (replace-dehighlight))
     (or unread-command-events