# HG changeset patch # User David Kastrup # Date 1088072784 0 # Node ID 4e0ea2b7523213e3dda0b028059f48cac1974211 # Parent fb1bd8c58401d94af4fd532a0579e72dc16de8ae (query-replace-read-args): Implement `\,' and `\#' replacements here. (query-replace-regexp): Doc string explaining this and the new `\?' replacement. Remove `\,' and `\#' implementation here, as it is better placed in `query-replace-read-args'. (replace-regexp): Explain `\,', `\#' and `\?'. (replace-match-data): New function for thorough reuse/destruction of old match-data. (replace-match-maybe-edit): Function for implementing `\?' editing. (perform-replace): Fix maintaining of the match stack including already matched regions, implement `\?', fix various problems with regions while editing and other stuff. (replace-highlight): Simplified. diff -r fb1bd8c58401 -r 4e0ea2b75232 lisp/ChangeLog --- a/lisp/ChangeLog Wed Jun 23 23:11:23 2004 +0000 +++ b/lisp/ChangeLog Thu Jun 24 10:26:24 2004 +0000 @@ -1,3 +1,20 @@ +2004-06-24 David Kastrup + + * replace.el (query-replace-read-args): Implement `\,' and `\#' + replacements here. + (query-replace-regexp): Doc string explaining this and the new + `\?' replacement. Remove `\,' and `\#' implementation here, as it + is better placed in `query-replace-read-args'. + (replace-regexp): Explain `\,', `\#' and `\?'. + (replace-match-data): New function for thorough reuse/destruction + of old match-data. + (replace-match-maybe-edit): Function for implementing `\?' + editing. + (perform-replace): Fix maintaining of the match stack including + already matched regions, implement `\?', fix various problems + with regions while editing and other stuff. + (replace-highlight): Simplified. + 2004-06-24 Daniel Pfeiffer * progmodes/grep.el (grep-error-screen-columns): New variable. diff -r fb1bd8c58401 -r 4e0ea2b75232 lisp/replace.el --- a/lisp/replace.el Wed Jun 23 23:11:23 2004 +0000 +++ b/lisp/replace.el Thu Jun 24 10:26:24 2004 +0000 @@ -95,6 +95,28 @@ (setq to (read-from-minibuffer (format "%s %s with: " string from) nil nil nil query-replace-to-history-variable from t))) + (when (and regexp-flag + (string-match "\\(\\`\\|[^\\]\\)\\(\\\\\\\\\\)*\\\\[,#]" to)) + (let (pos list char) + (while + (progn + (setq pos (match-end 0)) + (push (substring to 0 (- pos 2)) list) + (setq char (aref to (1- pos)) + to (substring to pos)) + (cond ((eq char ?\#) + (push '(number-to-string replace-count) list)) + ((eq char ?\,) + (setq pos (read-from-string to)) + (push `(replace-quote ,(car pos)) list) + (setq to (substring to (cdr pos))))) + (string-match "\\(\\`\\|[^\\]\\)\\(\\\\\\\\\\)*\\\\[,#]" to))) + (setq to (nreverse (delete "" (cons to list))))) + (replace-match-string-symbols to) + (setq to (cons 'replace-eval-replacement + (if (> (length to) 1) + (cons 'concat to) + (car to))))) (list from to current-prefix-arg))) (defun query-replace (from-string to-string &optional delimited start end) @@ -163,59 +185,38 @@ In TO-STRING, `\\&' stands for whatever matched the whole of REGEXP, and `\\=\\N' (where N is a digit) stands for whatever what matched the Nth `\\(...\\)' in REGEXP. - -When this function is called interactively, the replacement text -can also contain `\\,' followed by a Lisp expression. The escaped -shorthands for `query-replace-regexp-eval' are also valid -here: within the Lisp expression, you can use `\\&' for the whole -match string, `\\N' for partial matches, `\\#&' and `\\#N' for -the respective numeric values, and `\\#' for `replace-count'. +`\\?' lets you edit the replacement text in the minibuffer +at the given position for each replacement. -If your Lisp expression is an identifier and the next -letter in the replacement string would be interpreted as part of it, -you can wrap it with an expression like `\\,(or \\#)'. Incidentally, -for this particular case you may also enter `\\#' in the replacement -text directly. +In interactive calls, the replacement text may contain `\\,' +followed by a Lisp expression used as part of the replacement +text. Inside of that expression, `\\&' is a string denoting the +whole match, `\\N' a partial matches, `\\#&' and `\\#N' the +respective numeric values from `string-to-number', and `\\#' +itself for `replace-count', the number of replacements occured so +far. -When you use `\\,' or `\\#' in the replacement, TO-STRING actually -becomes a list with expanded shorthands. -Use \\[repeat-complex-command] after this command to see details." +If your Lisp expression is an identifier and the next letter in +the replacement string would be interpreted as part of it, you +can wrap it with an expression like `\\,(or \\#)'. Incidentally, +for this particular case you may also enter `\\#' in the +replacement text directly. + +When using those Lisp features interactively in the replacement +text, TO-STRING is actually made a list instead of a string. +Use \\[repeat-complex-command] after this command for details." (interactive (let ((common (query-replace-read-args "Query replace regexp" t))) - (list - (nth 0 common) - (if (string-match "\\(\\`\\|[^\\]\\)\\(\\\\\\\\\\)*\\\\[,#]" - (nth 1 common)) - (let ((to-string (nth 1 common)) pos to-expr char prompt) - (while (string-match - "\\(\\`\\|[^\\]\\)\\(\\\\\\\\\\)*\\\\[,#]" - to-string) - (setq pos (match-end 0)) - (push (substring to-string 0 (- pos 2)) to-expr) - (setq char (aref to-string (1- pos)) - to-string (substring to-string pos)) - (cond ((eq char ?\#) - (push '(number-to-string replace-count) to-expr)) - ((eq char ?\,) - (setq pos (read-from-string to-string)) - (push `(replace-quote ,(car pos)) to-expr) - (setq to-string (substring to-string (cdr pos)))))) - (setq to-expr (nreverse (delete "" (cons to-string to-expr)))) - (replace-match-string-symbols to-expr) - (cons 'replace-eval-replacement - (if (> (length to-expr) 1) - (cons 'concat to-expr) - (car to-expr)))) - (nth 1 common)) - (nth 2 common) - ;; These are done separately here - ;; so that command-history will record these expressions - ;; rather than the values they had this time. - (if (and transient-mark-mode mark-active) - (region-beginning)) - (if (and transient-mark-mode mark-active) - (region-end))))) + (list (nth 0 common) (nth 1 common) (nth 2 common) + ;; These are done separately here + ;; so that command-history will record these expressions + ;; rather than the values they had this time. + (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)) (define-key esc-map [?\C-%] 'query-replace-regexp) @@ -374,7 +375,27 @@ In TO-STRING, `\\&' stands for whatever matched the whole of REGEXP, and `\\=\\N' (where N is a digit) stands for - whatever what matched the Nth `\\(...\\)' in REGEXP. +whatever what matched the Nth `\\(...\\)' in REGEXP. +`\\?' lets you edit the replacement text in the minibuffer +at the given position for each replacement. + +In interactive calls, the replacement text may contain `\\,' +followed by a Lisp expression used as part of the replacement +text. Inside of that expression, `\\&' is a string denoting the +whole match, `\\N' a partial matches, `\\#&' and `\\#N' the +respective numeric values from `string-to-number', and `\\#' +itself for `replace-count', the number of replacements occured so +far. + +If your Lisp expression is an identifier and the next letter in +the replacement string would be interpreted as part of it, you +can wrap it with an expression like `\\,(or \\#)'. Incidentally, +for this particular case you may also enter `\\#' in the +replacement text directly. + +When using those Lisp features interactively in the replacement +text, TO-STRING is actually made a list instead of a string. +Use \\[repeat-complex-command] after this command for details. If `query-replace-interactive' is non-nil, the last incremental search regexp is used as REGEXP--you don't have to specify it with the minibuffer. @@ -1115,6 +1136,49 @@ (aset data 2 (if (consp next) next (aref data 3)))))) (car (aref data 2))) +(defun replace-match-data (integers reuse &optional new) + "Like `match-data', but markers in REUSE get invalidated. +If NEW is non-NIL, it is set and returned instead of fresh data, +but coerced to the correct value of INTEGERS." + (or (and new + (progn + (set-match-data new) + (and (eq new reuse) + (eq (null integers) (markerp (car reuse))) + new))) + (match-data integers + (prog1 reuse + (while reuse + (if (markerp (car reuse)) + (set-marker (car reuse) nil)) + (setq reuse (cdr reuse))))))) + +(defun replace-match-maybe-edit (newtext fixedcase literal noedit match-data) + "Make a replacement with `replace-match', editing `\\?'. +NEXTEXT, FIXEDCASE, LITERAL are just passed on. If NOEDIT is true, no +check for `\\?' is made to save time. MATCH-DATA is used for the +replacement. In case editing is done, it is changed to use markers. + +The return value is non-NIL if there has been no `\\?' or NOEDIT was +passed in. If LITERAL is set, no checking is done, anyway." + (unless (or literal noedit) + (setq noedit t) + (while (string-match "\\(\\`\\|[^\\]\\)\\(\\\\\\\\\\)*\\(\\\\\\?\\)" + newtext) + (setq newtext + (read-input "Edit replacement string: " + (prog1 + (cons + (replace-match "" t t newtext 3) + (1+ (match-beginning 3))) + (setq match-data + (replace-match-data + nil match-data match-data)))) + noedit nil))) + (set-match-data match-data) + (replace-match newtext fixedcase literal) + noedit) + (defun perform-replace (from-string replacements query-flag regexp-flag delimited-flag &optional repeat-count map start end) @@ -1145,6 +1209,7 @@ (search-string from-string) (real-match-data nil) ; the match data for the current match (next-replacement nil) + (noedit nil) (keep-going t) (stack nil) (replace-count 0) @@ -1201,7 +1266,9 @@ (setq real-match-data (if (consp match-again) (progn (goto-char (nth 1 match-again)) - match-again) + (replace-match-data t + real-match-data + match-again)) (and (or match-again ;; MATCH-AGAIN non-nil means we ;; accept an adjacent match. If @@ -1217,7 +1284,7 @@ (funcall search-function search-string limit t) ;; For speed, use only integers and ;; reuse the list used last time. - (match-data t real-match-data))))) + (replace-match-data t real-match-data))))) ;; Optionally ignore matches that have a read-only property. (unless (and query-replace-skip-read-only (text-property-not-all @@ -1249,16 +1316,23 @@ (set-match-data real-match-data) (setq next-replacement (funcall (car replacements) (cdr replacements) - replace-count))) + replace-count) + noedit nil)) (if (not query-flag) (let ((inhibit-read-only query-replace-skip-read-only)) - (set-match-data real-match-data) - (replace-match next-replacement nocasify literal) - (setq replace-count (1+ replace-count))) + (setq noedit + (replace-match-maybe-edit + next-replacement nocasify literal + noedit real-match-data) + replace-count (1+ replace-count))) (undo-boundary) (let (done replaced key def) ;; Loop reading commands until one of them sets done, - ;; which means it has finished handling this occurrence. + ;; which means it has finished handling this + ;; occurrence. Any command that sets `done' should + ;; leave behind proper match data for the stack. + ;; Commands not setting `done' need to adjust + ;; `real-match-data'. (while (not done) (set-match-data real-match-data) (replace-highlight (match-beginning 0) (match-end 0)) @@ -1290,37 +1364,49 @@ ((eq def 'backup) (if stack (let ((elt (pop stack))) - (goto-char (car elt)) - (setq replaced (eq t (cdr elt))) - (or replaced - (set-match-data (cdr elt)))) + (goto-char (nth 0 elt)) + (setq replaced (nth 1 elt) + real-match-data + (replace-match-data + t real-match-data + (nth 2 elt)))) (message "No previous match") (ding 'no-terminate) (sit-for 1))) ((eq def 'act) (or replaced - (progn - (replace-match next-replacement nocasify literal) - (setq replace-count (1+ replace-count)))) + (setq noedit + (replace-match-maybe-edit + next-replacement nocasify literal + noedit real-match-data) + replace-count (1+ replace-count))) (setq done t replaced t)) ((eq def 'act-and-exit) (or replaced - (progn - (replace-match next-replacement nocasify literal) - (setq replace-count (1+ replace-count)))) + (setq noedit + (replace-match-maybe-edit + next-replacement nocasify literal + noedit real-match-data) + replace-count (1+ replace-count))) (setq keep-going nil) (setq done t replaced t)) ((eq def 'act-and-show) (if (not replaced) - (progn - (replace-match next-replacement nocasify literal) - (setq replace-count (1+ replace-count)) - (setq replaced t)))) + (setq noedit + (replace-match-maybe-edit + next-replacement nocasify literal + noedit real-match-data) + replace-count (1+ replace-count) + real-match-data (replace-match-data + t real-match-data) + replaced t))) ((eq def 'automatic) (or replaced - (progn - (replace-match next-replacement nocasify literal) - (setq replace-count (1+ replace-count)))) + (setq noedit + (replace-match-maybe-edit + next-replacement nocasify literal + noedit real-match-data) + replace-count (1+ replace-count))) (setq done t query-flag nil replaced t)) ((eq def 'skip) (setq done t)) @@ -1328,36 +1414,45 @@ (recenter nil)) ((eq def 'edit) (let ((opos (point-marker))) + (setq real-match-data (replace-match-data + nil real-match-data + real-match-data)) (goto-char (match-beginning 0)) (save-excursion - (funcall search-function search-string limit t) - (setq real-match-data (match-data))) - (save-excursion (save-window-excursion (recursive-edit))) - (goto-char opos)) - (set-match-data real-match-data) + (goto-char opos) + (set-marker opos nil)) ;; Before we make the replacement, ;; decide whether the search string ;; can match again just after this match. (if (and regexp-flag nonempty-match) (setq match-again (and (looking-at search-string) (match-data))))) - ;; Edit replacement. ((eq def 'edit-replacement) - (setq next-replacement + (setq real-match-data (replace-match-data + nil real-match-data + real-match-data) + next-replacement (read-input "Edit replacement string: " - next-replacement)) - (or replaced - (replace-match next-replacement nocasify literal)) + next-replacement) + noedit nil) + (if replaced + (set-match-data real-match-data) + (setq noedit + (replace-match-maybe-edit + next-replacement nocasify literal noedit + real-match-data) + replaced t)) (setq done t)) ((eq def 'delete-and-edit) - (delete-region (match-beginning 0) (match-end 0)) - (set-match-data - (prog1 (match-data) - (save-excursion (recursive-edit)))) + (replace-match "" t t) + (setq real-match-data (replace-match-data + nil real-match-data)) + (replace-dehighlight) + (save-excursion (recursive-edit)) (setq replaced t)) ;; Note: we do not need to treat `exit-prefix' ;; specially here, since we reread @@ -1372,10 +1467,23 @@ ;; Record previous position for ^ when we move on. ;; Change markers to numbers in the match data ;; since lots of markers slow down editing. - (setq stack - (cons (cons (point) - (or replaced (match-data t))) - stack)))))) + (push (list (point) replaced +;;; If the replacement has already happened, all we need is the +;;; current match start and end. We could get this with a trivial +;;; match like +;;; (save-excursion (goto-char (match-beginning 0)) +;;; (search-forward (match-string 0)) +;;; (match-data t)) +;;; if we really wanted to avoid manually constructing match data. +;;; Adding current-buffer is necessary so that match-data calls can +;;; return markers which are appropriate for editing. + (if replaced + (list + (match-beginning 0) + (match-end 0) + (current-buffer)) + (match-data t))) + stack))))) ;; The code preventing adjacent regexp matches in the condition ;; of the while-loop above will haven taken us one character @@ -1405,14 +1513,12 @@ (defun replace-highlight (start end) (and query-replace-highlight - (progn - (or replace-overlay - (progn - (setq replace-overlay (make-overlay start end)) - (overlay-put replace-overlay 'face - (if (facep 'query-replace) - 'query-replace 'region)))) - (move-overlay replace-overlay start end (current-buffer))))) + (if replace-overlay + (move-overlay replace-overlay start end (current-buffer)) + (setq replace-overlay (make-overlay start end)) + (overlay-put replace-overlay 'face + (if (facep 'query-replace) + 'query-replace 'region))))) ;;; arch-tag: 16b4cd61-fd40-497b-b86f-b667c4cf88e4 ;;; replace.el ends here