* bug#14979: 24.3; Feature Request: query-replace-backward
2013-07-30 20:09 ` ben.a
2013-08-01 21:13 ` Juri Linkov
@ 2013-12-17 19:35 ` Juri Linkov
1 sibling, 0 replies; 12+ messages in thread
From: Juri Linkov @ 2013-12-17 19:35 UTC (permalink / raw)
To: ben.a; +Cc: 14979
> 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)))
\f
-(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.
^ permalink raw reply [flat|nested] 12+ messages in thread