]> code.delx.au - gnu-emacs-elpa/blobdiff - packages/el-search/el-search.el
Improve doc of el-search-this-expression-identifier.
[gnu-emacs-elpa] / packages / el-search / el-search.el
index 22a20a4bd3262278a8b50249d0e9a1d110a762f6..c71bb198d82add43a2a17bd5b8010f40f9930ec6 100644 (file)
 ;;
 ;; - detect infloops when replacing automatically (e.g. for 1 -> '(1))
 ;;
-;; - highlight matches around point in a timer
-;;
 ;; - implement backward searching
 ;;
 ;; - improve docstrings
   :group 'lisp)
 
 (defcustom el-search-this-expression-identifier 'exp
-  "Name of the identifier referring to the current expression.
-The default value is `exp'.  You can use this name in the search
-prompt to refer to the value of the currently tested expression."
+  "Identifier referring to the current expression in pattern input.
+When entering a PATTERN in an interactive \"el-search\" command,
+the pattern actually used will be
+
+    `(and ,el-search-this-expression-identifier ,pattern)
+
+The default value is `exp'."
   :type 'symbol)
 
 (defface el-search-match '((((background dark)) (:background "#0000A0"))
-                          (t                   (:background "DarkSlateGray1")))
+                          (t                   (:background "DarkSlateGray3")))
   "Face for highlighting the current match.")
 
+(defface el-search-other-match '((((background dark)) (:background "#202060"))
+                                 (t                   (:background "DarkSlateGray1")))
+  "Face for highlighting the other matches.")
+
 
 ;;;; Helpers
 
@@ -372,14 +378,15 @@ of the definitions is limited to \"el-search\"."
 
 (defun el-search--matcher (pattern &rest body)
   (eval ;use `eval' to allow for user defined pattern types at run time
-   `(el-search--with-additional-pcase-macros
-     (let ((byte-compile-debug t) ;make undefined pattern types raise an error
-           (warning-suppress-log-types '((bytecomp)))
-           (pcase--dontwarn-upats (cons '_ pcase--dontwarn-upats)))
-       (byte-compile (lambda (expression)
-                       (pcase expression
-                         (,pattern ,@(or body (list t)))
-                         (_        nil))))))))
+   (let ((expression (make-symbol "expression")))
+     `(el-search--with-additional-pcase-macros
+       (let ((byte-compile-debug t) ;make undefined pattern types raise an error
+             (warning-suppress-log-types '((bytecomp)))
+             (pcase--dontwarn-upats (cons '_ pcase--dontwarn-upats)))
+         (byte-compile (lambda (,expression)
+                         (pcase ,expression
+                           (,pattern ,@(or body (list t)))
+                           (_        nil)))))))))
 
 (defun el-search--match-p (matcher expression)
   (funcall matcher expression))
@@ -659,11 +666,68 @@ matches any of these expressions:
                                  "argument not a string or vector")
   `(pred (el-search--match-key-sequence ,key-sequence)))
 
+(defun el-search--s (expr)
+  (cond
+   ((symbolp expr) `(or (symbol ,(symbol-name expr))
+                        (,'\` (,'quote    (,'\, (symbol ,(symbol-name expr)))))
+                        (,'\` (,'function (,'\, (symbol ,(symbol-name expr)))))))
+   ((stringp expr) `(string ,expr))
+   (t expr)))
+
+(el-search-defpattern l (&rest lpats)
+  "Alternative pattern type for matching lists.
+Match any list with subsequent elements matched by all LPATS in
+order.
+
+The idea is to be able to search for pieces of code (i.e. lists)
+with very brief input by using a specialized syntax.
+
+An LPAT can take the following forms:
+
+SYMBOL  Matches any symbol S matched by SYMBOL's name interpreted
+        as a regexp.  Matches also 'S and #'S for any such S.
+STRING  Matches any string matched by STRING interpreted as a
+        regexp
+_       Matches any list element
+__      Matches any number of list elements (including zero)
+^       Matches zero elements, but only at the beginning of a list
+$       Matches zero elements, but only at the end of a list
+PAT     Anything else is interpreted as a normal pcase pattern, and
+        matches one list element matched by it
+
+^ is only valid as the first, $ as the last of the LPATS.
+
+Example: To match defuns that contain \"hl\" in their name and
+have at least one mandatory, but also optional arguments, you
+could use this pattern:
+
+    (l ^ 'defun hl (l _ &optional))"
+  (let ((match-start nil) (match-end nil))
+    (when (eq (car-safe lpats) '^)
+      (setq match-start t)
+      (cl-callf cdr lpats))
+    (when (eq (car-safe (last lpats)) '$)
+      (setq match-end t)
+      (cl-callf butlast lpats 1))
+    `(append ,@(if match-start '() '(_))
+             ,@(mapcar
+                (lambda (elt)
+                  (pcase elt
+                    ('__ '_)
+                    ('_ '`(,_))
+                    ('_? '(or '() `(,_))) ;FIXME: useful - document? or should we provide a (? PAT)
+                                          ;thing?
+                    (_ `(,'\` ((,'\, ,(el-search--s elt)))))))
+                lpats)
+             ,@(if match-end '() '(_)))))
+
 
 ;;;; Highlighting
 
 (defvar-local el-search-hl-overlay nil)
 
+(defvar-local el-search-hl-other-overlays '())
+
 (defvar el-search-keep-hl nil)
 
 (defun el-search-hl-sexp (&optional bounds)
@@ -672,12 +736,55 @@ matches any of these expressions:
     (if (overlayp el-search-hl-overlay)
         (apply #'move-overlay el-search-hl-overlay bounds)
       (overlay-put (setq el-search-hl-overlay (apply #'make-overlay bounds))
-                   'face 'el-search-match)))
+                   'face 'el-search-match))
+    (overlay-put el-search-hl-overlay 'priority 1002))
   (add-hook 'post-command-hook #'el-search-hl-post-command-fun t t))
 
+(defun el-search--hl-other-matches-1 (pattern from to)
+  (mapc #'delete-overlay el-search-hl-other-overlays)
+  (setq el-search-hl-other-overlays '())
+  (let ((matcher (el-search--matcher pattern))
+        this-match-beg this-match-end
+        (done nil))
+    (save-excursion
+      (goto-char from)
+      (while (not done)
+        (setq this-match-beg (el-search--search-pattern-1 matcher t))
+        (if (not this-match-beg)
+            (setq done t)
+          (goto-char this-match-beg)
+          (setq this-match-end (el-search--end-of-sexp))
+          (let ((ov (make-overlay this-match-beg this-match-end)))
+            (overlay-put ov 'face 'el-search-other-match)
+            (overlay-put ov 'priority 1001)
+            (push ov el-search-hl-other-overlays)
+            (goto-char this-match-end)
+            (when (>= (point) to) (setq done t))))))))
+
+(defun el-search-hl-other-matches (pattern)
+  "Highlight all matches visible in the selected window."
+  (el-search--hl-other-matches-1 pattern
+                                 (save-excursion
+                                   (goto-char (window-start))
+                                   (beginning-of-defun-raw)
+                                   (point))
+                                 (window-end))
+  (add-hook 'window-scroll-functions #'el-search--after-scroll t t))
+
+(defun el-search--after-scroll (_win start)
+  (el-search--hl-other-matches-1 el-search-current-pattern
+                                 (save-excursion
+                                   (goto-char start)
+                                   (beginning-of-defun-raw)
+                                   (point))
+                                 (window-end nil t)))
+
 (defun el-search-hl-remove ()
   (when (overlayp el-search-hl-overlay)
-    (delete-overlay el-search-hl-overlay)))
+    (delete-overlay el-search-hl-overlay))
+  (remove-hook 'window-scroll-functions #'el-search--after-scroll t)
+  (mapc #'delete-overlay el-search-hl-other-overlays)
+  (setq el-search-hl-other-overlays '()))
 
 (defun el-search-hl-post-command-fun ()
   (unless (or el-search-keep-hl
@@ -736,7 +843,9 @@ The following additional pattern types are currently defined:"
                            (ding)
                            nil))
       (setq el-search-success t)
-      (el-search-hl-sexp))))
+      (el-search-hl-sexp)
+      (unless (eq this-command last-command)
+        (el-search-hl-other-matches pattern)))))
 
 (defvar el-search-search-and-replace-help-string
   "\
@@ -761,7 +870,10 @@ Hit any key to proceed."
     (unwind-protect
         (while (and (not done) (el-search--search-pattern pattern t))
           (setq opoint (point))
-          (unless replace-all (el-search-hl-sexp))
+          (unless replace-all
+            (el-search-hl-sexp)
+            (unless (eq this-command last-command)
+              (el-search-hl-other-matches pattern)))
           (let* ((read-mapping (el-search--create-read-map))
                  (region (list (point) (el-search--end-of-sexp)))
                  (substring (apply #'buffer-substring-no-properties region))
@@ -847,8 +959,13 @@ Hit any key to proceed."
   (barf-if-buffer-read-only)
   (el-search-search-and-replace-pattern from to mapping))
 
-(defun el-search--take-over-from-isearch ()
-  (prog1 isearch-string (isearch-exit)))
+(defun el-search--take-over-from-isearch (&optional goto-left-end)
+  (let ((other-end (and goto-left-end isearch-other-end))
+        (input isearch-string))
+    (isearch-exit)
+    (when (and other-end (< other-end (point)))
+      (goto-char other-end))
+    input))
 
 ;;;###autoload
 (defun el-search-search-from-isearch ()
@@ -863,7 +980,7 @@ Hit any key to proceed."
 ;;;###autoload
 (defun el-search-replace-from-isearch ()
   (interactive)
-  (let ((el-search--initial-mb-contents (concat "'" (el-search--take-over-from-isearch))))
+  (let ((el-search--initial-mb-contents (concat "'" (el-search--take-over-from-isearch t))))
     (call-interactively #'el-search-query-replace)))