b3c58910ed8a1d9f32281e7428a74f885207b0de
[gnu-emacs-elpa] / packages / el-search / el-search.el
1 ;;; el-search.el --- Expression based incremental search for emacs-lisp-mode -*- lexical-binding: t -*-
2
3 ;; Copyright (C) 2015 Free Software Foundation, Inc
4
5 ;; Author: Michael Heerdegen <michael_heerdegen@web.de>
6 ;; Maintainer: Michael Heerdegen <michael_heerdegen@web.de>
7 ;; Created: 29 Jul 2015
8 ;; Keywords: lisp
9 ;; Compatibility: GNU Emacs 25
10 ;; Version: 0.1.3
11 ;; Package-Requires: ((emacs "25"))
12
13
14 ;; This file is not part of GNU Emacs.
15
16 ;; GNU Emacs is free software: you can redistribute it and/or modify
17 ;; it under the terms of the GNU General Public License as published by
18 ;; the Free Software Foundation, either version 3 of the License, or
19 ;; (at your option) any later version.
20
21 ;; GNU Emacs is distributed in the hope that it will be useful,
22 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
23 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 ;; GNU General Public License for more details.
25
26 ;; You should have received a copy of the GNU General Public License
27 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
28
29
30 ;;; Commentary:
31
32 ;; Introduction
33 ;; ============
34 ;;
35 ;;
36 ;; The main user entry point is `el-search-pattern'. This command
37 ;; prompts for a `pcase' pattern and searches the current buffer for
38 ;; matching expressions by iteratively `read'ing buffer contents. For
39 ;; any match, point is put at the beginning of the expression found
40 ;; (unlike isearch which puts point at the end of matches).
41 ;;
42 ;; Why is it based on `pcase'? Because pattern matching (and the
43 ;; ability to combine destructuring and condition testing) is well
44 ;; suited for this task. In addition, pcase allows to add specialized
45 ;; pattern types and to combine them with other patterns in a natural
46 ;; and transparent way out of the box.
47 ;;
48 ;; It doesn't matter how the code is actually formatted. Comments are
49 ;; ignored, and strings are treated as atomic objects, their contents
50 ;; are not being searched.
51 ;;
52 ;;
53 ;; Example 1: if you enter
54 ;;
55 ;; 97
56 ;;
57 ;; at the prompt, this will find any occurrence of the number 97 in
58 ;; the code, but not 977 or (+ 90 7) or "My string containing 97".
59 ;; But it will find anything `eq' to 97 after reading, e.g. #x61 or
60 ;; ?a.
61 ;;
62 ;;
63 ;; Example 2: If you enter the pattern
64 ;;
65 ;; `(defvar ,_)
66 ;;
67 ;; you search for all defvar forms that don't specify an init value.
68 ;;
69 ;; The following will search for defvar forms with a docstring whose
70 ;; first line is longer than 70 characters:
71 ;;
72 ;; `(defvar ,_ ,_
73 ;; ,(and s (guard (< 70 (length (car (split-string s "\n")))))))
74 ;;
75 ;;
76 ;; When a search pattern is processed, the searched buffer is current
77 ;; with point at the beginning of the currently tested expression.
78 ;;
79 ;;
80 ;; Convenience
81 ;; ===========
82 ;;
83 ;; For pattern input, the minibuffer is put into `emacs-lisp-mode'.
84 ;;
85 ;; Any input PATTERN is silently transformed into (and exp PATTERN)
86 ;; so that you can always refer to the whole currently tested
87 ;; expression via the variable `exp'.
88 ;;
89 ;;
90 ;; Example 3:
91 ;;
92 ;; If you want to search a buffer for symbols that are defined in
93 ;; "cl-lib", you can use this pattern
94 ;;
95 ;; (guard (and (symbolp exp)
96 ;; (when-let ((file (symbol-file exp)))
97 ;; (string-match-p "cl-lib\\.elc?$" file))))
98 ;;
99 ;;
100 ;; ,----------------------------------------------------------------------
101 ;; | Q: "But I hate `pcase'! Can't we just do without?" |
102 ;; | |
103 ;; | A: Respect that you kept up until here! Just use (guard CODE), where|
104 ;; | CODE is any normal Elisp expression that returns non-nil when and |
105 ;; | only when you have a match. Use the variable `exp' to refer to |
106 ;; | the currently tested expression. Just like in the last example! |
107 ;; `----------------------------------------------------------------------
108 ;;
109 ;;
110 ;; It's cumbersome to write out the same complicated pattern
111 ;; constructs in the minibuffer again and again. You can define your
112 ;; own pcase pattern types for the purpose of el-search with
113 ;; `el-search-defpattern'. It is just like `pcase-defmacro', but the
114 ;; effect is limited to this package. See C-h f `el-search-pattern'
115 ;; for a list of predefined additional pattern forms.
116 ;;
117 ;;
118 ;; Replacing
119 ;; =========
120 ;;
121 ;; You can replace expressions with command `el-search-query-replace'.
122 ;; You are queried for a (pcase) pattern and a replacement expression.
123 ;; For each match of the pattern, the replacement expression is
124 ;; evaluated with the bindings created by the pcase matching in
125 ;; effect, and printed to produce the replacement string.
126 ;;
127 ;; Example: In some buffer you want to swap the two expressions at the
128 ;; places of the first two arguments in all calls of function `foo',
129 ;; so that e.g.
130 ;;
131 ;; (foo 'a (* 2 (+ 3 4)) t)
132 ;;
133 ;; becomes
134 ;;
135 ;; (foo (* 2 (+ 3 4)) 'a t).
136 ;;
137 ;; This will do it:
138 ;;
139 ;; M-x el-search-query-replace RET
140 ;; `(foo ,a ,b . ,rest) RET
141 ;; `(foo ,b ,a . ,rest) RET
142 ;;
143 ;; Type y to replace a match and go to the next one, r to replace
144 ;; without moving, SPC to go to the next match and ! to replace all
145 ;; remaining matches automatically. q quits. n is like SPC, so that
146 ;; y and n work like in isearch (meaning "yes" and "no") if you are
147 ;; used to that.
148 ;;
149 ;; It is possible to replace a match with multiple expressions using
150 ;; "splicing mode". When it is active, the replacement expression
151 ;; must evaluate to a list, and is spliced instead of inserted into
152 ;; the buffer for any replaced match. Use s to toggle splicing mode
153 ;; in a `el-search-query-replace' session.
154 ;;
155 ;;
156 ;; Suggested key bindings
157 ;; ======================
158 ;;
159 ;; (define-key emacs-lisp-mode-map [(control ?S)] #'el-search-pattern)
160 ;; (define-key emacs-lisp-mode-map [(control ?%)] #'el-search-query-replace)
161 ;;
162 ;; (define-key isearch-mode-map [(control ?S)] #'el-search-search-from-isearch)
163 ;; (define-key isearch-mode-map [(control ?%)] #'el-search-replace-from-isearch)
164 ;;
165 ;; (define-key el-search-read-expression-map [(control ?S)] #'exit-minibuffer)
166 ;;
167 ;; The bindings in `isearch-mode-map' let you conveniently switch to
168 ;; "el-search" searching from isearch. The binding in
169 ;; `el-search-read-expression-map' allows you to hit C-S twice to
170 ;; start a search for the last search pattern.
171 ;;
172 ;;
173 ;; Bugs, Known Limitations
174 ;; =======================
175 ;;
176 ;; - Replacing: in some cases the reader syntax of forms
177 ;; is changing due to reading+printing. "Some" because we can treat
178 ;; that problem in most cases.
179 ;;
180 ;; - Similarly: Comments are normally preserved (where it makes
181 ;; sense). But when replacing like `(foo ,a ,b) -> `(foo ,b ,a)
182 ;;
183 ;; in a content like
184 ;;
185 ;; (foo
186 ;; a
187 ;; ;;a comment
188 ;; b)
189 ;;
190 ;; the comment will be lost.
191 ;;
192 ;; FIXME: when we have resumable sessions, pause and warn about this case.
193 ;;
194 ;;
195 ;; Acknowledgments
196 ;; ===============
197 ;;
198 ;; Thanks to Stefan Monnier for corrections and advice.
199 ;;
200 ;;
201 ;; TODO:
202 ;;
203 ;; - implement backward searching
204 ;;
205 ;; - Make `el-search-pattern' accept an &optional limit, at least for
206 ;; the non-interactive use case?
207 ;;
208 ;; - improve docstrings
209 ;;
210 ;; - handle more reader syntaxes, e.g. #n, #n#
211 ;;
212 ;; - Implement sessions; add multi-file support based on iterators. A
213 ;; file list is read in (or the user can specify an iterator as a
214 ;; variable). The state in the current buffer is just (buffer
215 ;; . marker). Or should this be abstracted into an own lib? Could
216 ;; be named "files-session" or so.
217
218
219
220 ;;; Code:
221
222 ;;;; Requirements
223
224 (eval-when-compile
225 (require 'subr-x))
226
227 (require 'cl-lib)
228 (require 'elisp-mode)
229 (require 'thingatpt)
230 (require 'help-fns) ;el-search--make-docstring
231
232
233 ;;;; Configuration stuff
234
235 (defgroup el-search nil
236 "Expression based search and replace for `emacs-lisp-mode'."
237 :group 'lisp)
238
239 (defcustom el-search-this-expression-identifier 'exp
240 "Identifier referring to the current expression in pattern input.
241 When entering a PATTERN in an interactive \"el-search\" command,
242 the pattern actually used will be
243
244 `(and ,el-search-this-expression-identifier ,pattern)
245
246 The default value is `exp'."
247 :type 'symbol)
248
249 (defface el-search-match '((((background dark)) (:background "#0000A0"))
250 (t (:background "DarkSlateGray3")))
251 "Face for highlighting the current match.")
252
253 (defface el-search-other-match '((((background dark)) (:background "#202060"))
254 (t (:background "DarkSlateGray1")))
255 "Face for highlighting the other matches.")
256
257 (defcustom el-search-smart-case-fold-search t
258 "Whether to use smart case folding in pattern matching.
259 When an \"el-search\" pattern involves regexp matching (like for
260 \"string\" or \"source\") and this option is non-nil,
261 case-fold-search will be temporarily bound to t if the according
262 regexp contains any upper case letter, and nil else. This is
263 done independently for every single matching operation.
264
265 If nil, the value of `case-fold-search' is decisive."
266 :type 'boolean)
267
268 (defcustom el-search-use-sloppy-strings nil
269 "Whether to allow the usage of \"sloppy strings\".
270 When this option is turned on, for faster typing you are allowed
271 to specify symbols instead of strings as arguments to an
272 \"el-search\" pattern type that would otherwise accept only
273 strings, and their names will be used as input (with other words,
274 this spares you to type the string delimiters in many cases).
275
276 For example,
277
278 \(source ^cl\)
279
280 is then equivalent to
281
282 \(source \"^cl\"\)
283
284 When this option is off, the first form would just signal an
285 error."
286 :type 'boolean)
287
288
289 ;;;; Helpers
290
291 (defun el-search--smart-string-match-p (regexp string)
292 "`string-match-p' taking `el-search-smart-case-fold-search' into account."
293 (let ((case-fold-search (if el-search-smart-case-fold-search
294 (not (let ((case-fold-search nil))
295 (string-match-p "[[:upper:]]" regexp)))
296 case-fold-search)))
297 (string-match-p regexp string)))
298
299 (defun el-search--pp-to-string (expr)
300 (let ((print-length nil)
301 (print-level nil))
302 (pp-to-string expr)))
303
304 (defvar el-search-read-expression-map
305 (let ((map (make-sparse-keymap)))
306 (set-keymap-parent map read-expression-map)
307 (define-key map [(control ?g)] #'abort-recursive-edit)
308 (define-key map [up] nil)
309 (define-key map [down] nil)
310 (define-key map [(control ?j)] #'newline)
311 map)
312 "Map for reading input with `el-search-read-expression'.")
313
314 (defun el-search--setup-minibuffer ()
315 (let ((inhibit-read-only t))
316 (put-text-property 1 (minibuffer-prompt-end) 'font-lock-face 'minibuffer-prompt))
317 (emacs-lisp-mode)
318 (use-local-map el-search-read-expression-map)
319 (setq font-lock-mode t)
320 (funcall font-lock-function 1)
321 (goto-char (minibuffer-prompt-end))
322 (when (looking-at ".*\n")
323 (indent-sexp))
324 (goto-char (point-max))
325 (when-let ((this-sexp (with-current-buffer (window-buffer (minibuffer-selected-window))
326 (thing-at-point 'sexp))))
327 (let ((more-defaults (list (concat "'" this-sexp))))
328 (setq-local minibuffer-default-add-function
329 (lambda () (if (listp minibuffer-default)
330 (append minibuffer-default more-defaults)
331 (cons minibuffer-default more-defaults)))))))
332
333 ;; $$$$$FIXME: this should be in Emacs! There is only a helper `read--expression'.
334 (defun el-search-read-expression (prompt &optional initial-contents hist default read)
335 "Read expression for `my-eval-expression'."
336 (minibuffer-with-setup-hook #'el-search--setup-minibuffer
337 (read-from-minibuffer prompt initial-contents el-search-read-expression-map read
338 (or hist 'read-expression-history) default)))
339
340 (defvar el-search-history '()
341 "List of search input strings.")
342
343 (defvar el-search-query-replace-history '()
344 "List of input strings from `el-search-query-replace'.")
345
346 (defvar el-search--initial-mb-contents nil)
347
348 (defun el-search--pushnew-to-history (input histvar)
349 (let ((hist-head (car (symbol-value histvar))))
350 (unless (or (string-match-p "\\`\\'" input)
351 (and (stringp hist-head)
352 (or (string= input hist-head)
353 (ignore-errors (equal (read input) (read hist-head))))))
354 (push (if (string-match-p "\\`.+\n" input)
355 (with-temp-buffer
356 (emacs-lisp-mode)
357 (insert "\n" input)
358 (indent-region 1 (point))
359 (buffer-string))
360 input)
361 (symbol-value histvar)))))
362
363 (defun el-search--read-pattern (prompt &optional default histvar)
364 (cl-callf or histvar 'el-search-history)
365 (let ((input (el-search-read-expression
366 prompt el-search--initial-mb-contents histvar default)))
367 (el-search--pushnew-to-history input histvar)
368 (if (not (string= input "")) input (car (symbol-value histvar)))))
369
370 (defun el-search--end-of-sexp ()
371 ;;Point must be at sexp beginning
372 (or (scan-sexps (point) 1) (point-max)))
373
374 (defun el-search--ensure-sexp-start ()
375 "Move point to the next sexp beginning position.
376 Don't move if already at beginning of a sexp. Point must not be
377 inside a string or comment. `read' the expression at that point
378 and return it."
379 ;; This doesn't catch end-of-buffer to keep the return value non-ambiguous
380 (let ((not-done t) res)
381 (while not-done
382 (let ((stop-here nil)
383 (looking-at-from-back (lambda (regexp n)
384 (and (> (point) n)
385 (save-excursion
386 (backward-char n)
387 (looking-at regexp))))))
388 (while (not stop-here)
389 (cond
390 ((eobp) (signal 'end-of-buffer nil))
391 ((looking-at (rx (and (* space) ";"))) (forward-line))
392 ((looking-at (rx (+ (or space "\n")))) (goto-char (match-end 0)))
393
394 ;; FIXME: can the rest be done more generically?
395 ((and (looking-at (rx (or (syntax symbol) (syntax word))))
396 (not (looking-at "\\_<"))
397 (not (funcall looking-at-from-back ",@" 2)))
398 (forward-symbol 1))
399 ((or (and (looking-at "'") (funcall looking-at-from-back "#" 1))
400 (and (looking-at "@") (funcall looking-at-from-back "," 1)))
401 (forward-char))
402 (t (setq stop-here t)))))
403 (condition-case nil
404 (progn
405 (setq res (save-excursion (read (current-buffer))))
406 (setq not-done nil))
407 (error (forward-char))))
408 res))
409
410 (defvar el-search--pcase-macros '()
411 "List of additional \"el-search\" pcase macros.")
412
413 (defun el-search--make-docstring ()
414 ;; code mainly from `pcase--make-docstring'
415 (let* ((main (documentation (symbol-function 'el-search-pattern) 'raw))
416 (ud (help-split-fundoc main 'pcase)))
417 (with-temp-buffer
418 (insert (or (cdr ud) main))
419 (mapc
420 (pcase-lambda (`(,symbol . ,fun))
421 (when-let ((doc (documentation fun)))
422 (insert "\n\n\n-- ")
423 (setq doc (help-fns--signature symbol doc fun fun nil))
424 (insert "\n" (or doc "Not documented."))))
425 (reverse el-search--pcase-macros))
426 (let ((combined-doc (buffer-string)))
427 (if ud (help-add-fundoc-usage combined-doc (car ud)) combined-doc)))))
428
429 (put 'el-search-pattern 'function-documentation '(el-search--make-docstring))
430
431 (defmacro el-search-defpattern (name args &rest body)
432 "Like `pcase-defmacro', but limited to el-search patterns.
433 The semantics is exactly that of `pcase-defmacro', but the scope
434 of the definitions is limited to \"el-search\"."
435 (declare (indent 2) (debug defun))
436 `(setf (alist-get ',name el-search--pcase-macros)
437 (lambda ,args ,@body)))
438
439 (defun el-search--macroexpand-1 (pattern)
440 "Expand \"el-search\" PATTERN.
441 This is like `pcase--macroexpand', but expands only patterns
442 defined with `el-search-defpattern' and performs only one
443 expansion step.
444
445 Return PATTERN if this pattern type was not defined with
446 `el-search-defpattern'."
447 (if-let ((expander (alist-get (car-safe pattern) el-search--pcase-macros)))
448 (apply expander (cdr pattern))
449 pattern))
450
451 (defmacro el-search--with-additional-pcase-macros (&rest body)
452 `(cl-letf ,(mapcar (pcase-lambda (`(,symbol . ,fun))
453 `((get ',symbol 'pcase-macroexpander) #',fun))
454 el-search--pcase-macros)
455 ,@body))
456
457 (defun el-search--matcher (pattern &rest body)
458 (eval ;use `eval' to allow for user defined pattern types at run time
459 (let ((expression (make-symbol "expression")))
460 `(el-search--with-additional-pcase-macros
461 (let ((byte-compile-debug t) ;make undefined pattern types raise an error
462 (warning-suppress-log-types '((bytecomp)))
463 (pcase--dontwarn-upats (cons '_ pcase--dontwarn-upats)))
464 (byte-compile (lambda (,expression)
465 (pcase ,expression
466 (,pattern ,@(or body (list t)))
467 (_ nil)))))))))
468
469 (defun el-search--match-p (matcher expression)
470 (funcall matcher expression))
471
472 (defun el-search--wrap-pattern (pattern)
473 `(and ,el-search-this-expression-identifier ,pattern))
474
475 (defun el-search--skip-expression (expression &optional read)
476 ;; Move forward at least one character. Don't move into a string or
477 ;; comment. Don't move further than the beginning of the next sexp.
478 ;; Try to move as far as possible. Point must be at the beginning
479 ;; of an expression.
480 ;; If there are positions where `read' would succeed, but that do
481 ;; not represent a valid sexp start, move past them (e.g. when
482 ;; before "#'" move past both characters).
483 ;;
484 ;; EXPRESSION must be the (read) expression at point, but when READ
485 ;; is non-nil, ignore the first argument and read the expression at
486 ;; point instead.
487 (when read (setq expression (save-excursion (read (current-buffer)))))
488 (cond
489 ((or (null expression)
490 (equal [] expression)
491 (not (or (listp expression) (vectorp expression))))
492 (goto-char (el-search--end-of-sexp)))
493 ((looking-at (rx (or ",@" "," "#'" "'")))
494 (goto-char (match-end 0)))
495 (t (forward-char))))
496
497 (defun el-search--search-pattern-1 (matcher &optional noerror)
498 (let ((match-beg nil) (opoint (point)) current-expr)
499
500 ;; when inside a string or comment, move past it
501 (let ((syntax-here (syntax-ppss)))
502 (when (nth 3 syntax-here) ;inside a string
503 (goto-char (nth 8 syntax-here))
504 (forward-sexp))
505 (when (nth 4 syntax-here) ;inside a comment
506 (forward-line 1)
507 (while (and (not (eobp)) (looking-at (rx (and (* space) ";"))))
508 (forward-line 1))))
509
510 (if (catch 'no-match
511 (while (not match-beg)
512 (condition-case nil
513 (setq current-expr (el-search--ensure-sexp-start))
514 (end-of-buffer
515 (goto-char opoint)
516 (throw 'no-match t)))
517 (if (el-search--match-p matcher current-expr)
518 (setq match-beg (point)
519 opoint (point))
520 (el-search--skip-expression current-expr))))
521 (if noerror nil (signal 'end-of-buffer nil)))
522 match-beg))
523
524 (defun el-search--search-pattern (pattern &optional noerror)
525 "Search elisp buffer with `pcase' PATTERN.
526 Set point to the beginning of the occurrence found and return
527 point. Optional second argument, if non-nil, means if fail just
528 return nil (no error)."
529 (el-search--search-pattern-1 (el-search--matcher pattern) noerror))
530
531 (defun el-search--format-replacement (replacement original replace-expr-input splice)
532 ;; Return a printed representation of REPLACEMENT. Try to reuse the
533 ;; layout of subexpressions shared with the original (replaced)
534 ;; expression and the replace expression.
535 (if (and splice (not (listp replacement)))
536 (error "Expression to splice in is an atom")
537 (let ((orig-buffer (generate-new-buffer "orig-expr")))
538 (with-current-buffer orig-buffer
539 (emacs-lisp-mode)
540 (insert original)
541 (when replace-expr-input (insert "\n\n" replace-expr-input)))
542 (unwind-protect
543 (with-temp-buffer
544 (emacs-lisp-mode)
545 (insert (if splice
546 (mapconcat #'el-search--pp-to-string replacement " ")
547 (el-search--pp-to-string replacement)))
548 (goto-char 1)
549 (let (start this-sexp end orig-match-start orig-match-end done)
550 (while (and (< (point) (point-max))
551 (condition-case nil
552 (progn
553 (setq start (point)
554 this-sexp (read (current-buffer))
555 end (point))
556 t)
557 (end-of-buffer nil)))
558 (setq done nil orig-match-start nil)
559 (with-current-buffer orig-buffer
560 (goto-char 1)
561 (if (el-search--search-pattern `',this-sexp t)
562 (setq orig-match-start (point)
563 orig-match-end (progn (forward-sexp) (point)))
564 (setq done t)))
565 ;; find out whether we have a sequence of equal expressions
566 (while (and (not done)
567 (condition-case nil
568 (progn (setq this-sexp (read (current-buffer))) t)
569 ((invalid-read-syntax end-of-buffer end-of-file) nil)))
570 (if (with-current-buffer orig-buffer
571 (condition-case nil
572 (if (not (equal this-sexp (read (current-buffer))))
573 nil
574 (setq orig-match-end (point))
575 t)
576 ((invalid-read-syntax end-of-buffer end-of-file) nil)))
577 (setq end (point))
578 (setq done t)))
579 (if orig-match-start
580 (let ((match (with-current-buffer orig-buffer
581 (buffer-substring-no-properties orig-match-start
582 orig-match-end))))
583 (delete-region start end)
584 (goto-char start)
585 (when (string-match-p "\n" match)
586 (unless (looking-back "^[[:space:]\(]*" (line-beginning-position))
587 (insert "\n"))
588 (unless (looking-at "[[:space:]\)]*$")
589 (insert "\n")
590 (backward-char)))
591 (insert match))
592 (goto-char start)
593 (el-search--skip-expression nil t))
594 (condition-case nil
595 (el-search--ensure-sexp-start)
596 (end-of-buffer (goto-char (point-max))))))
597 (delete-trailing-whitespace (point-min) (point-max)) ;FIXME: this should not be necessary
598 (let ((result (buffer-substring (point-min) (point-max))))
599 (if (equal replacement (read result))
600 result
601 (error "Error in `el-search--format-replacement' - please make a bug report"))))
602 (kill-buffer orig-buffer)))))
603
604 (defun el-search--check-pattern-args (type args predicate &optional message)
605 "Check whether all ARGS fulfill PREDICATE.
606 Raise an error if not. The string arguments TYPE and optional
607 MESSAGE are used to construct the error message."
608 (mapc (lambda (arg)
609 (unless (funcall predicate arg)
610 (error (concat "Pattern `%s': "
611 (or message (format "argument doesn't fulfill %S" predicate))
612 ": %S")
613 type arg)))
614 args))
615
616 (defvar el-search-current-pattern nil)
617
618 (defvar el-search-success nil)
619
620
621 ;;;; Additional pattern type definitions
622
623 (defun el-search--split (matcher1 matcher2 list)
624 "Helper for the append pattern type.
625
626 When a splitting of LIST into two lists L1, L2 exist so that Li
627 is matched by MATCHERi, return (L1 L2) for such Li, else return
628 nil."
629 (let ((try-match (lambda (list1 list2)
630 (when (and (el-search--match-p matcher1 list1)
631 (el-search--match-p matcher2 list2))
632 (list list1 list2))))
633 (list1 list) (list2 '()) (match nil))
634 ;; don't use recursion, this could hit `max-lisp-eval-depth'
635 (while (and (not (setq match (funcall try-match list1 list2)))
636 (consp list1))
637 (let ((last-list1 (last list1)))
638 (if-let ((cdr-last-list1 (cdr last-list1)))
639 ;; list1 is a dotted list. Then list2 must be empty.
640 (progn (setcdr last-list1 nil)
641 (setq list2 cdr-last-list1))
642 (setq list1 (butlast list1 1)
643 list2 (cons (car last-list1) list2)))))
644 match))
645
646 (el-search-defpattern append (&rest patterns)
647 "Matches any list factorable into lists matched by PATTERNS in order.
648
649 PATTERNS is a list of patterns P1..Pn. Match any list L for that
650 lists L1..Ln exist that are matched by P1..Pn in order and L is
651 equal to the concatenation of L1..Ln. Ln is allowed to be no
652 list.
653
654 When different ways of matching are possible, it is unspecified
655 which one is chosen.
656
657 Example: the pattern
658
659 (append '(1 2 3) x (app car-safe 7))
660
661 matches the list (1 2 3 4 5 6 7 8 9) and binds `x' to (4 5 6)."
662 (if (null patterns)
663 '(pred null)
664 (pcase-let ((`(,pattern . ,more-patterns) patterns))
665 (cond
666 ((null more-patterns) pattern)
667 ((null (cdr more-patterns))
668 `(and (pred listp)
669 (app ,(apply-partially #'el-search--split
670 (el-search--matcher pattern)
671 (el-search--matcher (car more-patterns)))
672 (,'\` ((,'\, ,pattern)
673 (,'\, ,(car more-patterns)))))))
674 (t `(append ,pattern (append ,@more-patterns)))))))
675
676 (defun el-search--stringish-p (thing)
677 (or (stringp thing) (and el-search-use-sloppy-strings (symbolp thing))))
678
679 (el-search-defpattern string (&rest regexps)
680 "Matches any string that is matched by all REGEXPS."
681 (el-search--check-pattern-args "string" regexps #'el-search--stringish-p
682 "Argument not a string")
683 `(and (pred stringp)
684 ,@(mapcar (lambda (thing) `(pred (el-search--smart-string-match-p
685 ,(if (symbolp thing) (symbol-name thing) thing))))
686 regexps)))
687
688 (el-search-defpattern symbol (&rest regexps)
689 "Matches any symbol whose name is matched by all REGEXPS."
690 (el-search--check-pattern-args "symbol" regexps #'el-search--stringish-p
691 "Argument not a string")
692 `(and (pred symbolp)
693 (app symbol-name (string ,@regexps))))
694
695 (defun el-search--contains-p (matcher exp)
696 "Return non-nil when tree EXP contains a match for MATCHER.
697 Recurse on all types of sequences. In the positive case the
698 return value is (t elt), where ELT is a matching element found in
699 EXP."
700 (if (el-search--match-p matcher exp)
701 (list t exp)
702 (and (sequencep exp)
703 (let ((try-match (apply-partially #'el-search--contains-p matcher)))
704 (if (consp exp)
705 (or (funcall try-match (car exp))
706 (funcall try-match (cdr exp)))
707 (cl-some try-match exp))))))
708
709 (el-search-defpattern contains (&rest patterns)
710 "Matches trees that contain a match for all PATTERNs.
711 Searches any tree of sequences recursively for matches. Objects
712 of any kind matched by all PATTERNs are also matched.
713
714 Example: (contains (string \"H\") 17) matches ((\"Hallo\") x (5 [1 17]))"
715 (cond
716 ((null patterns) '_)
717 ((null (cdr patterns))
718 (let ((pattern (car patterns)))
719 `(app ,(apply-partially #'el-search--contains-p (el-search--matcher pattern))
720 (,'\` (t (,'\, ,pattern))))))
721 (t `(and ,@(mapcar (lambda (pattern) `(contains ,pattern)) patterns)))))
722
723 (el-search-defpattern not (pattern)
724 "Matches any object that is not matched by PATTERN."
725 `(app ,(apply-partially #'el-search--match-p (el-search--matcher pattern))
726 (pred not)))
727
728 (defun el-search--match-symbol-file (regexp symbol)
729 (when-let ((symbol-file (and (symbolp symbol)
730 (symbol-file symbol))))
731 (el-search--smart-string-match-p
732 (if (symbolp regexp) (concat "\\`" (symbol-name regexp) "\\'") regexp)
733 (file-name-sans-extension (file-name-nondirectory symbol-file)))))
734
735 (el-search-defpattern source (regexp)
736 "Matches any symbol whose `symbol-file' is matched by REGEXP.
737
738 This pattern matches when the object is a symbol for that
739 `symbol-file' returns a (non-nil) FILE-NAME that fulfills
740 (string-match-p REGEXP (file-name-sans-extension
741 (file-name-nondirectory FILENAME)))
742
743 REGEXP can also be a symbol, in which case
744
745 (concat \"^\" (symbol-name regexp) \"$\")
746
747 is used as regular expression."
748 (el-search--check-pattern-args "source" (list regexp) #'el-search--stringish-p
749 "Argument not a string")
750 `(pred (el-search--match-symbol-file ,(if (symbolp regexp) (symbol-name regexp) regexp))))
751
752 (defun el-search--match-key-sequence (keys expr)
753 (when-let ((expr-keys (pcase expr
754 ((or (pred stringp) (pred vectorp)) expr)
755 (`(kbd ,(and (pred stringp) string)) (ignore-errors (kbd string))))))
756 (apply #'equal
757 (mapcar (lambda (keys) (ignore-errors (key-description keys)))
758 (list keys expr-keys)))))
759
760 (el-search-defpattern keys (key-sequence)
761 "Matches descriptions of the KEY-SEQUENCE.
762 KEY-SEQUENCE is a string or vector representing a key sequence,
763 or an expression of the form (kbd STRING).
764
765 Match any description of the same key sequence in any of these
766 formats.
767
768 Example: the pattern
769
770 (keys (kbd \"C-s\"))
771
772 matches any of these expressions:
773
774 \"\\C-s\"
775 \"\C-s\"
776 (kbd \"C-s\")
777 [(control ?s)]"
778 (when (eq (car-safe key-sequence) 'kbd)
779 (setq key-sequence (kbd (cadr key-sequence))))
780 (el-search--check-pattern-args "keys" (list key-sequence) (lambda (x) (or (stringp x) (vectorp x)))
781 "argument not a string or vector")
782 `(pred (el-search--match-key-sequence ,key-sequence)))
783
784 (defun el-search--transform-nontrivial-lpat (expr)
785 (cond
786 ((symbolp expr) `(or (symbol ,(symbol-name expr))
787 (,'\` (,'quote (,'\, (symbol ,(symbol-name expr)))))
788 (,'\` (,'function (,'\, (symbol ,(symbol-name expr)))))))
789 ((stringp expr) `(string ,expr))
790 (t expr)))
791
792 (el-search-defpattern l (&rest lpats)
793 "Alternative pattern type for matching lists.
794 Match any list with subsequent elements matched by all LPATS in
795 order.
796
797 The idea is to be able to search for pieces of code (i.e. lists)
798 with very brief input by using a specialized syntax.
799
800 An LPAT can take the following forms:
801
802 SYMBOL Matches any symbol S matched by SYMBOL's name interpreted
803 as a regexp. Matches also 'S and #'S for any such S.
804 STRING Matches any string matched by STRING interpreted as a
805 regexp
806 _ Matches any list element
807 __ Matches any number of list elements (including zero)
808 ^ Matches zero elements, but only at the beginning of a list
809 $ Matches zero elements, but only at the end of a list
810 PAT Anything else is interpreted as a normal pcase pattern, and
811 matches one list element matched by it
812
813 ^ is only valid as the first, $ as the last of the LPATS.
814
815 Example: To match defuns that contain \"hl\" in their name and
816 have at least one mandatory, but also optional arguments, you
817 could use this pattern:
818
819 (l ^ 'defun hl (l _ &optional))"
820 (let ((match-start nil) (match-end nil))
821 (when (eq (car-safe lpats) '^)
822 (setq match-start t)
823 (cl-callf cdr lpats))
824 (when (eq (car-safe (last lpats)) '$)
825 (setq match-end t)
826 (cl-callf butlast lpats 1))
827 `(append ,@(if match-start '() '(_))
828 ,@(mapcar
829 (lambda (elt)
830 (pcase elt
831 ('__ '_)
832 ('_ '`(,_))
833 ('_? '(or '() `(,_))) ;FIXME: useful - document? or should we provide a (? PAT)
834 ;thing?
835 (_ `(,'\` ((,'\, ,(el-search--transform-nontrivial-lpat elt)))))))
836 lpats)
837 ,@(if match-end '() '(_)))))
838
839 (el-search-defpattern char-prop (property)
840 "Matches the object if completely covered with PROPERTY.
841 This pattern matches the object if its representation in the
842 search buffer is completely covered with the character property
843 PROPERTY.
844
845 This pattern always tests the complete expression in the search
846 buffer, it is not possible to test subexpressions calculated in
847 the search pattern."
848 `(guard (and (get-char-property (point) ',property)
849 ,(macroexp-let2 nil limit '(scan-sexps (point) 1)
850 `(= (next-single-char-property-change
851 (point) ',property nil ,limit)
852 ,limit)))))
853
854 (el-search-defpattern includes-prop (property)
855 "Matches the object if partly covered with PROPERTY.
856 This pattern matches the object if its representation in the
857 search buffer is partly covered with the character property
858 PROPERTY.
859
860 This pattern always tests the complete expression in the search
861 buffer, it is not possible to test subexpressions calculated in
862 the search pattern."
863 `(guard (or (get-char-property (point) ',property)
864 ,(macroexp-let2 nil limit '(scan-sexps (point) 1)
865 `(not (= (next-single-char-property-change
866 (point) ',property nil ,limit)
867 ,limit))))))
868
869 (el-search-defpattern change ()
870 "Matches the object if it is part of a change.
871 This is equivalent to (char-prop diff-hl-hunk).
872
873 You need `diff-hl-mode' turned on, provided by the library
874 \"diff-hl\" available in Gnu Elpa."
875 (or (bound-and-true-p diff-hl-mode)
876 (error "diff-hl-mode not enabled"))
877 '(char-prop diff-hl-hunk))
878
879 (el-search-defpattern changed ()
880 "Matches the object if it contains a change.
881 This is equivalent to (includes-prop diff-hl-hunk).
882
883 You need `diff-hl-mode' turned on, provided by the library
884 \"diff-hl\" available in Gnu Elpa."
885 (or (bound-and-true-p diff-hl-mode)
886 (error "diff-hl-mode not enabled"))
887 '(includes-prop diff-hl-hunk))
888
889
890 ;;;; Highlighting
891
892 (defvar-local el-search-hl-overlay nil)
893
894 (defvar-local el-search-hl-other-overlays '())
895
896 (defvar el-search-keep-hl nil)
897
898 (defun el-search-hl-sexp (&optional bounds)
899 (let ((bounds (or bounds
900 (list (point) (el-search--end-of-sexp)))))
901 (if (overlayp el-search-hl-overlay)
902 (apply #'move-overlay el-search-hl-overlay bounds)
903 (overlay-put (setq el-search-hl-overlay (apply #'make-overlay bounds))
904 'face 'el-search-match))
905 (overlay-put el-search-hl-overlay 'priority 1002))
906 (add-hook 'post-command-hook #'el-search-hl-post-command-fun t t))
907
908 (defun el-search--hl-other-matches-1 (pattern from to)
909 (mapc #'delete-overlay el-search-hl-other-overlays)
910 (setq el-search-hl-other-overlays '())
911 (let ((matcher (el-search--matcher pattern))
912 this-match-beg this-match-end
913 (done nil))
914 (save-excursion
915 (goto-char from)
916 (while (not done)
917 (setq this-match-beg (el-search--search-pattern-1 matcher t))
918 (if (not this-match-beg)
919 (setq done t)
920 (goto-char this-match-beg)
921 (setq this-match-end (el-search--end-of-sexp))
922 (let ((ov (make-overlay this-match-beg this-match-end)))
923 (overlay-put ov 'face 'el-search-other-match)
924 (overlay-put ov 'priority 1001)
925 (push ov el-search-hl-other-overlays)
926 (goto-char this-match-end)
927 (when (>= (point) to) (setq done t))))))))
928
929 (defun el-search-hl-other-matches (pattern)
930 "Highlight all matches visible in the selected window."
931 (el-search--hl-other-matches-1 pattern
932 (save-excursion
933 (goto-char (window-start))
934 (beginning-of-defun-raw)
935 (point))
936 (window-end))
937 (add-hook 'window-scroll-functions #'el-search--after-scroll t t))
938
939 (defun el-search--after-scroll (_win start)
940 (el-search--hl-other-matches-1 el-search-current-pattern
941 (save-excursion
942 (goto-char start)
943 (beginning-of-defun-raw)
944 (point))
945 (window-end nil t)))
946
947 (defun el-search-hl-remove ()
948 (when (overlayp el-search-hl-overlay)
949 (delete-overlay el-search-hl-overlay))
950 (remove-hook 'window-scroll-functions #'el-search--after-scroll t)
951 (mapc #'delete-overlay el-search-hl-other-overlays)
952 (setq el-search-hl-other-overlays '()))
953
954 (defun el-search-hl-post-command-fun ()
955 (unless (or el-search-keep-hl
956 (eq this-command 'el-search-query-replace)
957 (eq this-command 'el-search-pattern))
958 (el-search-hl-remove)
959 (remove-hook 'post-command-hook 'el-search-hl-post-command-fun t)))
960
961
962 ;;;; Core functions
963
964 ;;;###autoload
965 (defun el-search-pattern (pattern &optional no-error)
966 "Start new or resume last elisp search.
967
968 Search current buffer for expressions that are matched by `pcase'
969 PATTERN. Use `read' to transform buffer contents into
970 expressions.
971
972 Use `emacs-lisp-mode' for reading input. Some keys in the
973 minibuffer have a special binding: to make it possible to edit
974 multi line input, C-j inserts a newline, and up and down move the
975 cursor vertically - see `el-search-read-expression-map' for more
976 details.
977
978
979 Additional `pcase' pattern types to be used with this command can
980 be defined with `el-search-defpattern'.
981
982 The following additional pattern types are currently defined:"
983 (interactive (list (if (and (eq this-command last-command)
984 el-search-success)
985 el-search-current-pattern
986 (let* ((input (el-search--read-pattern "Find pcase pattern: "
987 (car el-search-history)))
988 (pattern (read input)))
989 ;; A very common mistake: input "foo" instead of "'foo"
990 (when (and (symbolp pattern)
991 (not (eq pattern '_))
992 (or (not (boundp pattern))
993 (not (eq (symbol-value pattern) pattern))))
994 (error "Please don't forget the quote when searching for a symbol"))
995 ;; Make input available also in query-replace history
996 (el-search--pushnew-to-history input 'el-search-query-replace-history)
997 ;; and wrap the PATTERN
998 (el-search--wrap-pattern pattern)))))
999 (if (not (called-interactively-p 'any))
1000 (el-search--search-pattern pattern no-error)
1001 (setq this-command 'el-search-pattern) ;in case we come from isearch
1002 (setq el-search-current-pattern pattern)
1003 (let ((opoint (point)))
1004 (when (and (eq this-command last-command) el-search-success)
1005 (el-search--skip-expression nil t))
1006 (setq el-search-success nil)
1007 (when (condition-case nil
1008 (el-search--search-pattern pattern)
1009 (end-of-buffer (message "No match")
1010 (goto-char opoint)
1011 (el-search-hl-remove)
1012 (ding)
1013 nil))
1014 (setq el-search-success t)
1015 (el-search-hl-sexp)
1016 (unless (eq this-command last-command)
1017 (el-search-hl-other-matches pattern))))))
1018
1019 (defvar el-search-search-and-replace-help-string
1020 "\
1021 y Replace this match and move to the next.
1022 SPC or n Skip this match and move to the next.
1023 r Replace this match but don't move.
1024 ! Replace all remaining matches automatically.
1025 q Quit. To resume, use e.g. `repeat-complex-command'.
1026 ? Show this help.
1027 s Toggle splicing mode. When splicing mode is
1028 on (default off), the replacement expression must
1029 evaluate to a list, and the result is spliced into the
1030 buffer, instead of just inserted.
1031
1032 Hit any key to proceed."
1033 "Help string for ? in `el-search-query-replace'.")
1034
1035 (defun el-search--search-and-replace-pattern (pattern replacement &optional splice to-input-string)
1036 (let ((replace-all nil) (nbr-replaced 0) (nbr-skipped 0) (done nil)
1037 (el-search-keep-hl t) (opoint (point))
1038 (get-replacement (el-search--matcher pattern replacement))
1039 (skip-matches-in-replacement 'ask))
1040 (unwind-protect
1041 (while (and (not done) (el-search--search-pattern pattern t))
1042 (setq opoint (point))
1043 (unless replace-all
1044 (el-search-hl-sexp)
1045 (unless (eq this-command last-command)
1046 (el-search-hl-other-matches pattern)))
1047 (let* ((region (list (point) (el-search--end-of-sexp)))
1048 (original-text (apply #'buffer-substring-no-properties region))
1049 (expr (read original-text))
1050 (replaced-this nil)
1051 (new-expr (funcall get-replacement expr))
1052 (get-replacement-string
1053 (lambda () (el-search--format-replacement new-expr original-text to-input-string splice)))
1054 (to-insert (funcall get-replacement-string))
1055 (replacement-contains-another-match
1056 (with-temp-buffer
1057 (emacs-lisp-mode)
1058 (insert to-insert)
1059 (goto-char 1)
1060 (el-search--skip-expression new-expr)
1061 (condition-case nil
1062 (progn (el-search--ensure-sexp-start)
1063 (el-search--search-pattern pattern t))
1064 (end-of-buffer nil))))
1065 (do-replace (lambda ()
1066 (atomic-change-group
1067 (apply #'delete-region region)
1068 (let ((inhibit-message t)
1069 (opoint (point)))
1070 (insert to-insert)
1071 (indent-region opoint (point))
1072 (el-search-hl-sexp (list opoint (point)))
1073 (goto-char opoint)))
1074 (cl-incf nbr-replaced)
1075 (setq replaced-this t))))
1076 (if replace-all
1077 (funcall do-replace)
1078 (while (not (pcase (if replaced-this
1079 (read-char-choice "[SPC ! q] (? for help)"
1080 '(?\ ?! ?q ?\C-g ?n ??))
1081 (read-char-choice
1082 (concat "Replace this occurrence"
1083 (if (or (string-match-p "\n" to-insert)
1084 (< 40 (length to-insert)))
1085 "" (format " with `%s'" to-insert))
1086 "? "
1087 (if splice "{splice} " "")
1088 "[y SPC r ! s q] (? for help)" )
1089 '(?y ?n ?r ?\ ?! ?q ?\C-g ?s ??)))
1090 (?r (funcall do-replace)
1091 nil)
1092 (?y (funcall do-replace)
1093 t)
1094 ((or ?\ ?n)
1095 (unless replaced-this (cl-incf nbr-skipped))
1096 t)
1097 (?! (unless replaced-this
1098 (funcall do-replace))
1099 (setq replace-all t)
1100 t)
1101 (?s (cl-callf not splice)
1102 (setq to-insert (funcall get-replacement-string))
1103 nil)
1104 ((or ?q ?\C-g)
1105 (setq done t)
1106 t)
1107 (?? (ignore (read-char el-search-search-and-replace-help-string))
1108 nil)))))
1109 (unless (or done (eobp))
1110 (cond
1111 ((not (and replaced-this replacement-contains-another-match))
1112 (el-search--skip-expression nil t))
1113 ((eq skip-matches-in-replacement 'ask)
1114 (if (setq skip-matches-in-replacement
1115 (yes-or-no-p "Match in replacement - always skip? "))
1116 (forward-sexp)
1117 (el-search--skip-expression nil t)
1118 (when replace-all
1119 (setq replace-all nil)
1120 (message "Falling back to interactive mode")
1121 (sit-for 3.))))
1122 (skip-matches-in-replacement (forward-sexp))
1123 (t
1124 (el-search--skip-expression nil t)
1125 (message "Replacement contains another match%s"
1126 (if replace-all " - falling back to interactive mode" ""))
1127 (setq replace-all nil)
1128 (sit-for 2.)))))))
1129 (el-search-hl-remove)
1130 (goto-char opoint)
1131 (message "Replaced %d matches%s"
1132 nbr-replaced
1133 (if (zerop nbr-skipped) ""
1134 (format " (%d skipped)" nbr-skipped)))))
1135
1136 (defun el-search-query-replace--read-args ()
1137 (barf-if-buffer-read-only)
1138 (let ((from-input (let ((el-search--initial-mb-contents
1139 (or el-search--initial-mb-contents
1140 (and (eq last-command 'el-search-pattern)
1141 (car el-search-history)))))
1142 (el-search--read-pattern "Query replace pattern: " nil
1143 'el-search-query-replace-history)))
1144 from to)
1145 (with-temp-buffer
1146 (emacs-lisp-mode)
1147 (insert from-input)
1148 (goto-char 1)
1149 (forward-sexp)
1150 (skip-chars-forward " \t\n\f")
1151 ;; FIXME: maybe more sanity tests here...
1152 (if (not (looking-at "->"))
1153 (setq from from-input
1154 to (let ((el-search--initial-mb-contents nil))
1155 (el-search--read-pattern "Replace with result of evaluation of: " from)))
1156 (delete-char 2)
1157 (goto-char 1)
1158 (forward-sexp)
1159 (setq from (buffer-substring 1 (point)))
1160 (skip-chars-forward " \t\n\f")
1161 (setq to (buffer-substring (point) (progn (forward-sexp) (point))))))
1162 (unless (and el-search-query-replace-history
1163 (not (string= from from-input))
1164 (string= from-input (car el-search-query-replace-history)))
1165 (push (with-temp-buffer
1166 (emacs-lisp-mode)
1167 (insert (let ((newline-in-from (string-match-p "\n" from))
1168 (newline-in-to (string-match-p "\n" to)))
1169 (format "%s%s%s ->%s%s"
1170 (if (and (or newline-in-from newline-in-to)
1171 (not (string-match-p "\\`\n" from))) "\n" "")
1172 (if newline-in-from "\n" "" ) from
1173 (if (and (or newline-in-from newline-in-to)
1174 (not (string-match-p "\\`\n" to))) "\n" " ") to)))
1175 (indent-region 1 (point-max))
1176 (buffer-string))
1177 el-search-query-replace-history))
1178 (el-search--pushnew-to-history from 'el-search-history)
1179 (list (el-search--wrap-pattern (read from)) (read to) to)))
1180
1181 ;;;###autoload
1182 (defun el-search-query-replace (from-pattern to-expr &optional textual-to)
1183 "Replace some matches of \"el-search\" pattern FROM-PATTERN.
1184
1185 TO-EXPR is an Elisp expression that is evaluated repeatedly for
1186 each match with bindings created in FROM-PATTERN in effect to
1187 produce a replacement expression. Operate from point
1188 to (point-max).
1189
1190 As each match is found, the user must type a character saying
1191 what to do with it. For directions, type ? at that time.
1192
1193 As an alternative to enter FROM-PATTERN and TO-EXPR separately,
1194 you can also give an input of the form
1195
1196 FROM-PATTERN -> TO-EXPR
1197
1198 to the first prompt and specify both expressions at once. This
1199 format is also used for history entries."
1200 (interactive (el-search-query-replace--read-args))
1201 (setq this-command 'el-search-query-replace) ;in case we come from isearch
1202 (setq el-search-current-pattern from-pattern)
1203 (barf-if-buffer-read-only)
1204 (el-search--search-and-replace-pattern from-pattern to-expr nil textual-to))
1205
1206 (defun el-search--take-over-from-isearch (&optional goto-left-end)
1207 (let ((other-end (and goto-left-end isearch-other-end))
1208 (input isearch-string))
1209 (isearch-exit)
1210 (when (and other-end (< other-end (point)))
1211 (goto-char other-end))
1212 input))
1213
1214 ;;;###autoload
1215 (defun el-search-search-from-isearch ()
1216 ;; FIXME: an interesting alternative would be to really integrate it
1217 ;; with Isearch, using `isearch-search-fun-function'.
1218 ;; Alas, this is not trivial if we want to transfer our optimizations.
1219 (interactive)
1220 (let ((el-search--initial-mb-contents (concat "'" (el-search--take-over-from-isearch))))
1221 ;; use `call-interactively' so we get recorded in `extended-command-history'
1222 (call-interactively #'el-search-pattern)))
1223
1224 ;;;###autoload
1225 (defun el-search-replace-from-isearch ()
1226 (interactive)
1227 (let ((el-search--initial-mb-contents (concat "'" (el-search--take-over-from-isearch t))))
1228 (call-interactively #'el-search-query-replace)))
1229
1230
1231
1232 (provide 'el-search)
1233 ;;; el-search.el ends here