bug-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

bug#14979: 24.3; Feature Request: query-replace-backward


From: Juri Linkov
Subject: bug#14979: 24.3; Feature Request: query-replace-backward
Date: Tue, 17 Dec 2013 21:35:32 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3.50 (x86_64-pc-linux-gnu)

> Typing M-- C-M-% is fewer keystrokes than pressing C-r a few times then
> C-M-%.  It is also fewer keystrokes than setting the mark, then moving
> through the document to find a proper starting point and finally pressing
> C-M-%.
>
> The best solution would be if the functions query-replace and
> query-replace-regexp are changed so that they step backwards when
> receiving a negative argument.

The following patch implements replace backward with this UI:

M-- M-%         - replace string backward
M-- C-M-%       - replace regexp backward
M-s w M-- M-%   - replace words backward
M-s _ M-- M-%   - replace symbols backward

=== modified file 'lisp/replace.el'
--- lisp/replace.el     2013-11-30 08:42:28 +0000
+++ lisp/replace.el     2013-12-17 19:34:11 +0000
@@ -226,9 +226,11 @@ (defun query-replace-read-args (prompt r
   (let* ((from (query-replace-read-from prompt regexp-flag))
         (to (if (consp from) (prog1 (cdr from) (setq from (car from)))
               (query-replace-read-to from prompt regexp-flag))))
-    (list from to current-prefix-arg)))
+    (list from to
+         (and current-prefix-arg (not (eq current-prefix-arg '-)))
+         (and current-prefix-arg (eq current-prefix-arg '-)))))
 
-(defun query-replace (from-string to-string &optional delimited start end)
+(defun query-replace (from-string to-string &optional delimited start end 
backward)
   "Replace some occurrences of FROM-STRING with TO-STRING.
 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.
@@ -259,7 +261,9 @@ (defun query-replace (from-string to-str
 regexp in `search-whitespace-regexp'.
 
 Third arg DELIMITED (prefix arg if interactive), if non-nil, means replace
-only matches surrounded by word boundaries.
+only matches surrounded by word boundaries.  The negative prefix arg `-'
+means replacing backward.
+
 Fourth and fifth arg START and END specify the region to operate on.
 
 To customize possible responses, change the \"bindings\" in 
`query-replace-map'."
@@ -267,7 +271,9 @@ (defun query-replace (from-string to-str
    (let ((common
          (query-replace-read-args
           (concat "Query replace"
-                  (if current-prefix-arg " word" "")
+                  (if current-prefix-arg
+                      (if (eq current-prefix-arg '-) " backward" " word")
+                    "")
                   (if (and transient-mark-mode mark-active) " in region" ""))
           nil)))
      (list (nth 0 common) (nth 1 common) (nth 2 common)
@@ -277,12 +283,13 @@ (defun query-replace (from-string to-str
           (if (and transient-mark-mode mark-active)
               (region-beginning))
           (if (and transient-mark-mode mark-active)
-              (region-end)))))
-  (perform-replace from-string to-string t nil delimited nil nil start end))
+              (region-end))
+          (nth 3 common))))
+  (perform-replace from-string to-string t nil delimited nil nil start end 
backward))
 
 (define-key esc-map "%" 'query-replace)
 
-(defun query-replace-regexp (regexp to-string &optional delimited start end)
+(defun query-replace-regexp (regexp to-string &optional delimited start end 
backward)
   "Replace some things after point matching REGEXP with TO-STRING.
 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.
@@ -313,7 +320,9 @@ (defun query-replace-regexp (regexp to-s
 regexp in `search-whitespace-regexp'.
 
 Third arg DELIMITED (prefix arg if interactive), if non-nil, means replace
-only matches surrounded by word boundaries.
+only matches surrounded by word boundaries.  The negative prefix arg `-'
+means replacing 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,
@@ -341,7 +350,9 @@ (defun query-replace-regexp (regexp to-s
    (let ((common
          (query-replace-read-args
           (concat "Query replace"
-                  (if current-prefix-arg " word" "")
+                  (if current-prefix-arg
+                      (if (eq current-prefix-arg '-) " backward" " word")
+                    "")
                   " regexp"
                   (if (and transient-mark-mode mark-active) " in region" ""))
           t)))
@@ -352,8 +363,9 @@ (defun query-replace-regexp (regexp to-s
           (if (and transient-mark-mode mark-active)
               (region-beginning))
           (if (and transient-mark-mode mark-active)
-              (region-end)))))
-  (perform-replace regexp to-string t t delimited nil nil start end))
+              (region-end))
+          (nth 3 common))))
+  (perform-replace regexp to-string t t delimited nil nil start end backward))
 
 (define-key esc-map [?\C-%] 'query-replace-regexp)
 
@@ -475,7 +487,7 @@ (defun map-query-replace-regexp (regexp
                to-strings ""))))
     (perform-replace regexp replacements t t nil n nil start end)))
 
-(defun replace-string (from-string to-string &optional delimited start end)
+(defun replace-string (from-string to-string &optional delimited start end 
backward)
   "Replace occurrences of FROM-STRING with TO-STRING.
 Preserve case in each match if `case-replace' and `case-fold-search'
 are non-nil and FROM-STRING has no uppercase letters.
@@ -491,7 +503,8 @@ (defun replace-string (from-string to-st
 regexp in `search-whitespace-regexp'.
 
 Third arg DELIMITED (prefix arg if interactive), if non-nil, means replace
-only matches surrounded by word boundaries.
+only matches surrounded by word boundaries.  The negative prefix arg `-'
+means replacing backward.
 
 Operates on the region between START and END (if both are nil, from point
 to the end of the buffer).  Interactively, if Transient Mark mode is
@@ -513,7 +526,9 @@ (defun replace-string (from-string to-st
    (let ((common
          (query-replace-read-args
           (concat "Replace"
-                  (if current-prefix-arg " word" "")
+                  (if current-prefix-arg
+                      (if (eq current-prefix-arg '-) " backward" " word")
+                    "")
                   " string"
                   (if (and transient-mark-mode mark-active) " in region" ""))
           nil)))
@@ -521,12 +536,13 @@ (defun replace-string (from-string to-st
           (if (and transient-mark-mode mark-active)
               (region-beginning))
           (if (and transient-mark-mode mark-active)
-              (region-end)))))
-  (perform-replace from-string to-string nil nil delimited nil nil start end))
+              (region-end))
+          (nth 3 common))))
+  (perform-replace from-string to-string nil nil delimited nil nil start end 
backward))
 (put 'replace-string 'interactive-only
      "use `search-forward' and `replace-match' instead.")
 
-(defun replace-regexp (regexp to-string &optional delimited start end)
+(defun replace-regexp (regexp to-string &optional delimited start end backward)
   "Replace things after point matching REGEXP with TO-STRING.
 Preserve case in each match if `case-replace' and `case-fold-search'
 are non-nil and REGEXP has no uppercase letters.
@@ -543,7 +559,9 @@ (defun replace-regexp (regexp to-string
 of the region.  Otherwise, operate from point to the end of the buffer.
 
 Third arg DELIMITED (prefix arg if interactive), if non-nil, means replace
-only matches surrounded by word boundaries.
+only matches surrounded by word boundaries.  The negative prefix arg `-'
+means replacing 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,
@@ -582,7 +600,9 @@ (defun replace-regexp (regexp to-string
    (let ((common
          (query-replace-read-args
           (concat "Replace"
-                  (if current-prefix-arg " word" "")
+                  (if current-prefix-arg
+                      (if (eq current-prefix-arg '-) " backward" " word")
+                    "")
                   " regexp"
                   (if (and transient-mark-mode mark-active) " in region" ""))
           t)))
@@ -590,8 +610,9 @@ (defun replace-regexp (regexp to-string
           (if (and transient-mark-mode mark-active)
               (region-beginning))
           (if (and transient-mark-mode mark-active)
-              (region-end)))))
-  (perform-replace regexp to-string nil t delimited nil nil start end))
+              (region-end))
+          (nth 3 common))))
+  (perform-replace regexp to-string nil t delimited nil nil start end 
backward))
 (put 'replace-regexp 'interactive-only
      "use `re-search-forward' and `replace-match' instead.")
 
@@ -1847,7 +1868,7 @@ (defun replace-match-data (integers reus
                  new)))
       (match-data integers reuse t)))
 
-(defun replace-match-maybe-edit (newtext fixedcase literal noedit match-data)
+(defun replace-match-maybe-edit (newtext fixedcase literal noedit match-data 
backward)
   "Make a replacement with `replace-match', editing `\\?'.
 NEWTEXT, FIXEDCASE, LITERAL are just passed on.  If NOEDIT is true, no
 check for `\\?' is made to save time.  MATCH-DATA is used for the
@@ -1871,6 +1892,9 @@ (defun replace-match-maybe-edit (newtext
            noedit nil)))
   (set-match-data match-data)
   (replace-match newtext fixedcase literal)
+  ;; `replace-match' leaves point at the end of the replacement text,
+  ;; so move point to the beginning when replacing backward.
+  (when backward (goto-char (nth 0 match-data)))
   noedit)
 
 (defvar replace-search-function nil
@@ -1886,7 +1910,7 @@ (defvar replace-re-search-function nil
 `re-search-forward'.")
 
 (defun replace-search (search-string limit regexp-flag delimited-flag
-                                    case-fold-search)
+                                    case-fold-search backward)
   "Search for the next occurrence of SEARCH-STRING to replace."
   ;; Let-bind global isearch-* variables to values used
   ;; to search the next replacement.  These let-bindings
@@ -1905,7 +1929,7 @@ (defun replace-search (search-string lim
         (isearch-case-fold-search case-fold-search)
         (isearch-adjusted nil)
         (isearch-nonincremental t)     ; don't use lax word mode
-        (isearch-forward t)
+        (isearch-forward (not backward))
         (search-function
          (or (if regexp-flag
                  replace-re-search-function
@@ -1917,7 +1941,7 @@ (defvar replace-overlay nil)
 
 (defun replace-highlight (match-beg match-end range-beg range-end
                          search-string regexp-flag delimited-flag
-                         case-fold-search)
+                         case-fold-search backward)
   (if query-replace-highlight
       (if replace-overlay
          (move-overlay replace-overlay match-beg match-end (current-buffer))
@@ -1933,7 +1957,7 @@ (defun replace-highlight (match-beg matc
            (isearch-regexp-lax-whitespace
             replace-regexp-lax-whitespace)
            (isearch-case-fold-search case-fold-search)
-           (isearch-forward t)
+           (isearch-forward (not backward))
            (isearch-other-end match-beg)
            (isearch-error nil))
        (isearch-lazy-highlight-new-loop range-beg range-end))))
@@ -1949,7 +1973,7 @@ (defun replace-dehighlight ()
 
 (defun perform-replace (from-string replacements
                        query-flag regexp-flag delimited-flag
-                       &optional repeat-count map start end)
+                       &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:
@@ -2003,10 +2027,15 @@ (defun perform-replace (from-string repl
                      minibuffer-prompt-properties))))
 
     ;; If region is active, in Transient Mark mode, operate on region.
-    (when start
-      (setq limit (copy-marker (max start end)))
-      (goto-char (min start end))
-      (deactivate-mark))
+    (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
@@ -2036,13 +2065,17 @@ (defun perform-replace (from-string repl
     (unwind-protect
        ;; Loop finding occurrences that perhaps should be replaced.
        (while (and keep-going
-                   (not (or (eobp) (and limit (>= (point) limit))))
+                   (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 (nth 1 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
@@ -2051,22 +2084,26 @@ (defun perform-replace (from-string repl
                                 (and
                                  (replace-search search-string limit
                                                  regexp-flag delimited-flag
-                                                 case-fold-search)
+                                                 case-fold-search backward)
                                  ;; For speed, use only integers and
                                  ;; reuse the list used last time.
                                  (replace-match-data t real-match-data)))
-                               ((and (< (1+ (point)) (point-max))
+                               ((and (if backward
+                                         (> (1- (point)) (point-min))
+                                       (< (1+ (point)) (point-max)))
                                      (or (null limit)
-                                         (< (1+ (point)) 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 1)
+                                  (forward-char (if backward -1 1))
                                   (if (replace-search search-string limit
                                                       regexp-flag 
delimited-flag
-                                                      case-fold-search)
+                                                      case-fold-search 
backward)
                                       (replace-match-data
                                        t real-match-data)
                                     (goto-char opoint)
@@ -2087,7 +2124,9 @@ (defun perform-replace (from-string repl
          (setq match-again
                (and nonempty-match
                     (or (not regexp-flag)
-                        (and (looking-at search-string)
+                        (and (if backward
+                                 (looking-back search-string)
+                               (looking-at search-string))
                              (let ((match (match-data)))
                                (and (/= (nth 0 match) (nth 1 match))
                                     match))))))
@@ -2124,11 +2163,11 @@ (defun perform-replace (from-string repl
                    (replace-highlight
                     (nth 0 real-match-data) (nth 1 real-match-data)
                     start end search-string
-                    regexp-flag delimited-flag case-fold-search))
+                    regexp-flag delimited-flag case-fold-search backward))
                  (setq noedit
                        (replace-match-maybe-edit
                         next-replacement nocasify literal
-                        noedit real-match-data)
+                        noedit real-match-data backward)
                        replace-count (1+ replace-count)))
              (undo-boundary)
              (let (done replaced key def)
@@ -2143,7 +2182,7 @@ (defun perform-replace (from-string repl
                  (replace-highlight
                   (match-beginning 0) (match-end 0)
                   start end search-string
-                  regexp-flag delimited-flag case-fold-search)
+                  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)
@@ -2173,6 +2212,7 @@ (defun perform-replace (from-string repl
                                                 (get delimited-flag 
'isearch-message-prefix))
                                            "word ") "")
                                    (if regexp-flag "regexp " "")
+                                   (if backward "backward " "")
                                    from-string " with "
                                    next-replacement ".\n\n"
                                    (substitute-command-keys
@@ -2201,7 +2241,7 @@ (defun perform-replace (from-string repl
                             (setq noedit
                                   (replace-match-maybe-edit
                                    next-replacement nocasify literal
-                                   noedit real-match-data)
+                                   noedit real-match-data backward)
                                   replace-count (1+ replace-count)))
                         (setq done t replaced t))
                        ((eq def 'act-and-exit)
@@ -2209,7 +2249,7 @@ (defun perform-replace (from-string repl
                             (setq noedit
                                   (replace-match-maybe-edit
                                    next-replacement nocasify literal
-                                   noedit real-match-data)
+                                   noedit real-match-data backward)
                                   replace-count (1+ replace-count)))
                         (setq keep-going nil)
                         (setq done t replaced t))
@@ -2218,7 +2258,7 @@ (defun perform-replace (from-string repl
                             (setq noedit
                                   (replace-match-maybe-edit
                                    next-replacement nocasify literal
-                                   noedit real-match-data)
+                                   noedit real-match-data backward)
                                   replace-count (1+ replace-count)
                                   real-match-data (replace-match-data
                                                    t real-match-data)
@@ -2228,7 +2268,7 @@ (defun perform-replace (from-string repl
                             (setq noedit
                                   (replace-match-maybe-edit
                                    next-replacement nocasify literal
-                                   noedit real-match-data)
+                                   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)))
@@ -2272,7 +2312,7 @@ (defun perform-replace (from-string repl
                           (setq noedit
                                 (replace-match-maybe-edit
                                  next-replacement nocasify literal noedit
-                                 real-match-data)
+                                 real-match-data backward)
                                 replaced t))
                         (setq done t))
 

=== modified file 'lisp/isearch.el'
--- lisp/isearch.el     2013-12-16 20:32:15 +0000
+++ lisp/isearch.el     2013-12-17 19:34:05 +0000
@@ -1667,10 +1667,11 @@ (defun re-search-backward-lax-whitespace
     (re-search-backward regexp bound noerror count)))
 
 
-(defun isearch-query-replace (&optional delimited regexp-flag)
+(defun isearch-query-replace (&optional arg regexp-flag)
   "Start `query-replace' with string to replace from last search string.
-The arg DELIMITED (prefix arg if interactive), if non-nil, means replace
-only matches surrounded by word boundaries.  Note that using the prefix arg
+The ARG (prefix arg if interactive), if non-nil, means replace
+only matches surrounded by word boundaries.  The negative prefix
+arg `-' means replacing backward.  Note that using the prefix arg
 is possible only when `isearch-allow-scroll' is non-nil or
 `isearch-allow-prefix' is non-nil, and it doesn't always provide the
 correct matches for `query-replace', so the preferred way to run word
@@ -1688,6 +1689,8 @@ (defun isearch-query-replace (&optional
         isearch-lax-whitespace)
        (replace-regexp-lax-whitespace
         isearch-regexp-lax-whitespace)
+       (delimited (and arg (not (eq arg '-))))
+       (backward (and arg (eq arg '-)))
        ;; Set `isearch-recursive-edit' to nil to prevent calling
        ;; `exit-recursive-edit' in `isearch-done' that terminates
        ;; the execution of this command when it is non-nil.
@@ -1696,9 +1699,13 @@ (defun isearch-query-replace (&optional
     (isearch-done nil t)
     (isearch-clean-overlays)
     (if (and isearch-other-end
-            (< isearch-other-end (point))
+            (if backward
+                (> isearch-other-end (point))
+              (< isearch-other-end (point)))
              (not (and transient-mark-mode mark-active
-                       (< (mark) (point)))))
+                       (if backward
+                          (> (mark) (point))
+                        (< (mark) (point))))))
         (goto-char isearch-other-end))
     (set query-replace-from-history-variable
          (cons isearch-string
@@ -1718,19 +1725,21 @@ (defun isearch-query-replace (&optional
                      " word"))
                "")
              (if isearch-regexp " regexp" "")
+             (if backward " backward" "")
              (if (and transient-mark-mode mark-active) " in region" ""))
       isearch-regexp)
      t isearch-regexp (or delimited isearch-word) nil nil
      (if (and transient-mark-mode mark-active) (region-beginning))
-     (if (and transient-mark-mode mark-active) (region-end))))
+     (if (and transient-mark-mode mark-active) (region-end))
+     backward))
   (and isearch-recursive-edit (exit-recursive-edit)))
 
-(defun isearch-query-replace-regexp (&optional delimited)
+(defun isearch-query-replace-regexp (&optional arg)
   "Start `query-replace-regexp' with string to replace from last search string.
 See `isearch-query-replace' for more information."
   (interactive
    (list current-prefix-arg))
-  (isearch-query-replace delimited t))
+  (isearch-query-replace arg t))
 
 (defun isearch-occur (regexp &optional nlines)
   "Run `occur' using the last search string as the regexp.






reply via email to

[Prev in Thread] Current Thread [Next in Thread]