From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Juri Linkov Newsgroups: gmane.emacs.bugs Subject: bug#14979: 24.3; Feature Request: query-replace-backward Date: Tue, 17 Dec 2013 21:35:32 +0200 Organization: JURTA Message-ID: <8738lr1eo3.fsf@mail.jurta.org> References: <87mwp612ou.fsf@ThinkPad-W520.localdomain> <87y58pg7pq.fsf@gmail.com> <87txjdg7kc.fsf@gmail.com> <87wqo922so.fsf@mail.jurta.org> <87a9l5yt8g.fsf@mail.jurta.org> <871u6fzu6h.fsf@ThinkPad-W520.localdomain> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: text/plain X-Trace: ger.gmane.org 1387309339 27391 80.91.229.3 (17 Dec 2013 19:42:19 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Tue, 17 Dec 2013 19:42:19 +0000 (UTC) Cc: 14979@debbugs.gnu.org To: ben.a@gmx.us Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Tue Dec 17 20:42:25 2013 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1Vt0XA-0002Yh-F4 for geb-bug-gnu-emacs@m.gmane.org; Tue, 17 Dec 2013 20:42:24 +0100 Original-Received: from localhost ([::1]:35139 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Vt0X6-0005So-9V for geb-bug-gnu-emacs@m.gmane.org; Tue, 17 Dec 2013 14:42:20 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:45408) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Vt0Wv-0005Rt-DQ for bug-gnu-emacs@gnu.org; Tue, 17 Dec 2013 14:42:15 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Vt0Wp-0007Pi-Bm for bug-gnu-emacs@gnu.org; Tue, 17 Dec 2013 14:42:09 -0500 Original-Received: from debbugs.gnu.org ([140.186.70.43]:42042) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Vt0Wp-0007PU-5E for bug-gnu-emacs@gnu.org; Tue, 17 Dec 2013 14:42:03 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.80) (envelope-from ) id 1Vt0Wo-0003zI-N3 for bug-gnu-emacs@gnu.org; Tue, 17 Dec 2013 14:42:02 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Juri Linkov Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Tue, 17 Dec 2013 19:42:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 14979 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: Original-Received: via spool by 14979-submit@debbugs.gnu.org id=B14979.138730926715248 (code B ref 14979); Tue, 17 Dec 2013 19:42:02 +0000 Original-Received: (at 14979) by debbugs.gnu.org; 17 Dec 2013 19:41:07 +0000 Original-Received: from localhost ([127.0.0.1]:56061 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1Vt0Vu-0003xm-7K for submit@debbugs.gnu.org; Tue, 17 Dec 2013 14:41:07 -0500 Original-Received: from [69.163.184.122] (port=35405 helo=ps18281.dreamhostps.com) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1Vt0Vo-0003x8-DU for 14979@debbugs.gnu.org; Tue, 17 Dec 2013 14:41:02 -0500 Original-Received: from localhost.jurta.org (ps18281.dreamhostps.com [69.163.184.122]) by ps18281.dreamhostps.com (Postfix) with ESMTP id 50AB136C00F85D; Tue, 17 Dec 2013 11:40:58 -0800 (PST) In-Reply-To: <871u6fzu6h.fsf@ThinkPad-W520.localdomain> (ben a.'s message of "Tue, 30 Jul 2013 16:09:26 -0400") User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3.50 (x86_64-pc-linux-gnu) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.15 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 140.186.70.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.bugs:82142 Archived-At: > 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.