X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/91192f5895f2e4d1ff8eec6faeab40099867ac89..c835174aa35b47c4036aea0c56836b38bff43511:/packages/el-search/el-search.el diff --git a/packages/el-search/el-search.el b/packages/el-search/el-search.el index a15a2d4e1..b3c58910e 100644 --- a/packages/el-search/el-search.el +++ b/packages/el-search/el-search.el @@ -162,8 +162,12 @@ ;; (define-key isearch-mode-map [(control ?S)] #'el-search-search-from-isearch) ;; (define-key isearch-mode-map [(control ?%)] #'el-search-replace-from-isearch) ;; +;; (define-key el-search-read-expression-map [(control ?S)] #'exit-minibuffer) +;; ;; The bindings in `isearch-mode-map' let you conveniently switch to -;; "el-search" searching from isearch. +;; "el-search" searching from isearch. The binding in +;; `el-search-read-expression-map' allows you to hit C-S twice to +;; start a search for the last search pattern. ;; ;; ;; Bugs, Known Limitations @@ -292,11 +296,10 @@ error." case-fold-search))) (string-match-p regexp string))) -(defun el-search--print (expr) - (let ((print-quoted t) - (print-length nil) +(defun el-search--pp-to-string (expr) + (let ((print-length nil) (print-level nil)) - (prin1-to-string expr))) + (pp-to-string expr))) (defvar el-search-read-expression-map (let ((map (make-sparse-keymap))) @@ -304,18 +307,20 @@ error." (define-key map [(control ?g)] #'abort-recursive-edit) (define-key map [up] nil) (define-key map [down] nil) - (define-key map [(control meta backspace)] #'backward-kill-sexp) - (define-key map [(control ?S)] #'exit-minibuffer) + (define-key map [(control ?j)] #'newline) map) "Map for reading input with `el-search-read-expression'.") (defun el-search--setup-minibuffer () + (let ((inhibit-read-only t)) + (put-text-property 1 (minibuffer-prompt-end) 'font-lock-face 'minibuffer-prompt)) (emacs-lisp-mode) (use-local-map el-search-read-expression-map) (setq font-lock-mode t) (funcall font-lock-function 1) - (backward-sexp) - (indent-sexp) + (goto-char (minibuffer-prompt-end)) + (when (looking-at ".*\n") + (indent-sexp)) (goto-char (point-max)) (when-let ((this-sexp (with-current-buffer (window-buffer (minibuffer-selected-window)) (thing-at-point 'sexp)))) @@ -332,12 +337,35 @@ error." (read-from-minibuffer prompt initial-contents el-search-read-expression-map read (or hist 'read-expression-history) default))) +(defvar el-search-history '() + "List of search input strings.") + +(defvar el-search-query-replace-history '() + "List of input strings from `el-search-query-replace'.") + (defvar el-search--initial-mb-contents nil) -(defun el-search--read-pattern (prompt &optional default read) +(defun el-search--pushnew-to-history (input histvar) + (let ((hist-head (car (symbol-value histvar)))) + (unless (or (string-match-p "\\`\\'" input) + (and (stringp hist-head) + (or (string= input hist-head) + (ignore-errors (equal (read input) (read hist-head)))))) + (push (if (string-match-p "\\`.+\n" input) + (with-temp-buffer + (emacs-lisp-mode) + (insert "\n" input) + (indent-region 1 (point)) + (buffer-string)) + input) + (symbol-value histvar))))) + +(defun el-search--read-pattern (prompt &optional default histvar) + (cl-callf or histvar 'el-search-history) (let ((input (el-search-read-expression - prompt el-search--initial-mb-contents 'el-search-history default read))) - (if (or read (not (string= input ""))) input (car el-search-history)))) + prompt el-search--initial-mb-contents histvar default))) + (el-search--pushnew-to-history input histvar) + (if (not (string= input "")) input (car (symbol-value histvar))))) (defun el-search--end-of-sexp () ;;Point must be at sexp beginning @@ -353,9 +381,10 @@ and return it." (while not-done (let ((stop-here nil) (looking-at-from-back (lambda (regexp n) - (save-excursion - (backward-char n) - (looking-at regexp))))) + (and (> (point) n) + (save-excursion + (backward-char n) + (looking-at regexp)))))) (while (not stop-here) (cond ((eobp) (signal 'end-of-buffer nil)) @@ -514,8 +543,8 @@ return nil (no error)." (with-temp-buffer (emacs-lisp-mode) (insert (if splice - (mapconcat #'el-search--print replacement " ") - (el-search--print replacement))) + (mapconcat #'el-search--pp-to-string replacement " ") + (el-search--pp-to-string replacement))) (goto-char 1) (let (start this-sexp end orig-match-start orig-match-end done) (while (and (< (point) (point-max)) @@ -584,6 +613,10 @@ MESSAGE are used to construct the error message." type arg))) args)) +(defvar el-search-current-pattern nil) + +(defvar el-search-success nil) + ;;;; Additional pattern type definitions @@ -748,7 +781,7 @@ matches any of these expressions: "argument not a string or vector") `(pred (el-search--match-key-sequence ,key-sequence))) -(defun el-search--s (expr) +(defun el-search--transform-nontrivial-lpat (expr) (cond ((symbolp expr) `(or (symbol ,(symbol-name expr)) (,'\` (,'quote (,'\, (symbol ,(symbol-name expr))))) @@ -799,7 +832,7 @@ could use this pattern: ('_ '`(,_)) ('_? '(or '() `(,_))) ;FIXME: useful - document? or should we provide a (? PAT) ;thing? - (_ `(,'\` ((,'\, ,(el-search--s elt))))))) + (_ `(,'\` ((,'\, ,(el-search--transform-nontrivial-lpat elt))))))) lpats) ,@(if match-end '() '(_))))) @@ -928,20 +961,20 @@ You need `diff-hl-mode' turned on, provided by the library ;;;; Core functions -(defvar el-search-history '() - "List of input strings.") - -(defvar el-search-success nil) -(defvar el-search-current-pattern nil) - ;;;###autoload -(defun el-search-pattern (pattern) +(defun el-search-pattern (pattern &optional no-error) "Start new or resume last elisp search. Search current buffer for expressions that are matched by `pcase' PATTERN. Use `read' to transform buffer contents into expressions. +Use `emacs-lisp-mode' for reading input. Some keys in the +minibuffer have a special binding: to make it possible to edit +multi line input, C-j inserts a newline, and up and down move the +cursor vertically - see `el-search-read-expression-map' for more +details. + Additional `pcase' pattern types to be used with this command can be defined with `el-search-defpattern'. @@ -950,19 +983,21 @@ The following additional pattern types are currently defined:" (interactive (list (if (and (eq this-command last-command) el-search-success) el-search-current-pattern - (let ((pattern - (el-search--read-pattern "Find pcase pattern: " - (car el-search-history) - t))) + (let* ((input (el-search--read-pattern "Find pcase pattern: " + (car el-search-history))) + (pattern (read input))) ;; A very common mistake: input "foo" instead of "'foo" (when (and (symbolp pattern) (not (eq pattern '_)) (or (not (boundp pattern)) (not (eq (symbol-value pattern) pattern)))) (error "Please don't forget the quote when searching for a symbol")) + ;; Make input available also in query-replace history + (el-search--pushnew-to-history input 'el-search-query-replace-history) + ;; and wrap the PATTERN (el-search--wrap-pattern pattern))))) (if (not (called-interactively-p 'any)) - (el-search--search-pattern pattern) + (el-search--search-pattern pattern no-error) (setq this-command 'el-search-pattern) ;in case we come from isearch (setq el-search-current-pattern pattern) (let ((opoint (point))) @@ -1010,12 +1045,12 @@ Hit any key to proceed." (unless (eq this-command last-command) (el-search-hl-other-matches pattern))) (let* ((region (list (point) (el-search--end-of-sexp))) - (substring (apply #'buffer-substring-no-properties region)) - (expr (read substring)) + (original-text (apply #'buffer-substring-no-properties region)) + (expr (read original-text)) (replaced-this nil) (new-expr (funcall get-replacement expr)) (get-replacement-string - (lambda () (el-search--format-replacement new-expr substring to-input-string splice))) + (lambda () (el-search--format-replacement new-expr original-text to-input-string splice))) (to-insert (funcall get-replacement-string)) (replacement-contains-another-match (with-temp-buffer @@ -1090,7 +1125,7 @@ Hit any key to proceed." (message "Replacement contains another match%s" (if replace-all " - falling back to interactive mode" "")) (setq replace-all nil) - (sit-for 3.))))))) + (sit-for 2.))))))) (el-search-hl-remove) (goto-char opoint) (message "Replaced %d matches%s" @@ -1100,9 +1135,47 @@ Hit any key to proceed." (defun el-search-query-replace--read-args () (barf-if-buffer-read-only) - (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)))) + (let ((from-input (let ((el-search--initial-mb-contents + (or el-search--initial-mb-contents + (and (eq last-command 'el-search-pattern) + (car el-search-history))))) + (el-search--read-pattern "Query replace pattern: " nil + 'el-search-query-replace-history))) + from to) + (with-temp-buffer + (emacs-lisp-mode) + (insert from-input) + (goto-char 1) + (forward-sexp) + (skip-chars-forward " \t\n ") + ;; FIXME: maybe more sanity tests here... + (if (not (looking-at "->")) + (setq from from-input + to (let ((el-search--initial-mb-contents nil)) + (el-search--read-pattern "Replace with result of evaluation of: " from))) + (delete-char 2) + (goto-char 1) + (forward-sexp) + (setq from (buffer-substring 1 (point))) + (skip-chars-forward " \t\n ") + (setq to (buffer-substring (point) (progn (forward-sexp) (point)))))) + (unless (and el-search-query-replace-history + (not (string= from from-input)) + (string= from-input (car el-search-query-replace-history))) + (push (with-temp-buffer + (emacs-lisp-mode) + (insert (let ((newline-in-from (string-match-p "\n" from)) + (newline-in-to (string-match-p "\n" to))) + (format "%s%s%s ->%s%s" + (if (and (or newline-in-from newline-in-to) + (not (string-match-p "\\`\n" from))) "\n" "") + (if newline-in-from "\n" "" ) from + (if (and (or newline-in-from newline-in-to) + (not (string-match-p "\\`\n" to))) "\n" " ") to))) + (indent-region 1 (point-max)) + (buffer-string)) + el-search-query-replace-history)) + (el-search--pushnew-to-history from 'el-search-history) (list (el-search--wrap-pattern (read from)) (read to) to))) ;;;###autoload @@ -1111,10 +1184,19 @@ Hit any key to proceed." 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. +produce a replacement expression. Operate from point +to (point-max). As each match is found, the user must type a character saying -what to do with it. For directions, type ? at that time." +what to do with it. For directions, type ? at that time. + +As an alternative to enter FROM-PATTERN and TO-EXPR separately, +you can also give an input of the form + + FROM-PATTERN -> TO-EXPR + +to the first prompt and specify both expressions at once. This +format is also used for history entries." (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-pattern)