;; `(defvar ,_)
;;
;; you search for all defvar forms that don't specify an init value.
-;;
+;;
;; The following will search for defvar forms with a docstring whose
;; first line is longer than 70 characters:
;;
;; (define-key isearch-mode-map [(control ?%)] #'el-search-replace-from-isearch)
;;
;; The bindings in `isearch-mode-map' let you conveniently switch to
-;; elisp searching from isearch.
+;; "el-search" searching from isearch.
;;
;;
;; Bugs, Known Limitations
;;
;; the comment will be lost.
;;
+;; FIXME: when we have resumable sessions, pause and warn about this case.
+;;
;;
;; Acknowledgments
;; ===============
;; TODO:
;;
;; - detect infloops when replacing automatically (e.g. for 1 -> '(1))
+;; Should we just fall back to interactive mode?
;;
;; - implement backward searching
;;
+;; - Make `el-search-pattern' accept an &optional limit, at least for
+;; the non-interactive use case?
+;;
;; - improve docstrings
;;
;; - handle more reader syntaxes, e.g. #n, #n#
Don't move if already at beginning of a sexp. Point must not be
inside a string or comment. `read' the expression at that point
and return it."
+ ;; This doesn't catch end-of-buffer to keep the return value non-ambiguous
(let ((not-done t) res)
(while not-done
(let ((stop-here nil)
((null (cdr patterns))
(let ((pattern (car patterns)))
`(app ,(apply-partially #'el-search--contains-p (el-search--matcher pattern))
- (,'\` (t (,'\, ,pattern))))))
+ (,'\` (t (,'\, ,pattern))))))
(t `(and ,@(mapcar (lambda (pattern) `(contains ,pattern)) patterns)))))
(el-search-defpattern not (pattern)
(funcall do-replace)
(while (not (pcase (if replaced-this
(read-char-choice "[SPC ! q] (? for help)"
- '(?\ ?! ?q ?n ??))
+ '(?\ ?! ?q ?\C-g ?n ??))
(read-char-choice
(concat "Replace this occurrence"
(if (or (string-match-p "\n" to-insert)
"? "
(if splice "{splice} " "")
"[y SPC r ! s q] (? for help)" )
- '(?y ?n ?r ?\ ?! ?q ?s ??)))
+ '(?y ?n ?r ?\ ?! ?q ?\C-g ?s ??)))
(?r (funcall do-replace)
nil)
(?y (funcall do-replace)
(?s (cl-callf not splice)
(setq to-insert (funcall get-replacement-string))
nil)
- (?q (setq done t)
- t)
+ ((or ?q ?\C-g)
+ (setq done t)
+ t)
(?? (ignore (read-char el-search-search-and-replace-help-string))
nil)))))
(unless (or done (eobp)) (el-search--skip-expression nil t)))))
(defun el-search-query-replace-read-args ()
(barf-if-buffer-read-only)
- (let* ((from (el-search--read-pattern "Replace from: "))
+ (let* ((from (el-search--read-pattern "Query replace pattern: "))
(to (let ((el-search--initial-mb-contents nil))
(el-search--read-pattern "Replace with result of evaluation of: " from))))
(list (el-search--wrap-pattern (read from)) (read to) to)))
;;;###autoload
-(defun el-search-query-replace (from to &optional to-input-string)
- "Replace some occurrences of FROM pattern with evaluated TO."
+(defun el-search-query-replace (from-pattern to-expr &optional textual-to)
+ "Replace some matches of \"el-search\" pattern FROM-PATTERN.
+
+TO-EXPR is an Elisp expression that is evaluated repeatedly for
+each match with bindings created in FROM-PATTERN in effect to
+produce a replacement expression.
+
+As each match is found, the user must type a character saying
+what to do with it. For directions, type ? at that time."
(interactive (el-search-query-replace-read-args))
(setq this-command 'el-search-query-replace) ;in case we come from isearch
- (setq el-search-current-pattern from)
+ (setq el-search-current-pattern from-pattern)
(barf-if-buffer-read-only)
- (el-search-search-and-replace-pattern from to nil to-input-string))
+ (el-search-search-and-replace-pattern from-pattern to-expr nil textual-to))
(defun el-search--take-over-from-isearch (&optional goto-left-end)
(let ((other-end (and goto-left-end isearch-other-end))