;;
;; - detect infloops when replacing automatically (e.g. for 1 -> '(1))
;;
-;; - highlight matches around point in a timer
-;;
;; - implement backward searching
;;
;; - improve docstrings
: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
"argument not a string or vector")
`(pred (el-search--match-key-sequence ,key-sequence)))
+(defun el-search--s (expr)
+ (cond
+ ((symbolp expr) `(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 matched by SYMBOL's name interpreted as
+ a regexp
+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)
(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
(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
"\
(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))