Skip to content

twlz0ne/psearch.el

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

51 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CI

psearch.el

Pcase based search for Emacs Lisp. Not as powerful as el-search, but easier to use.

Installation

(quelpa '(psearch
          :fetcher github
          :repo "twlz0ne/psearch.el"
          :files ("psearch.el")))

Usage

  • psearch-forward (pattern &optional result-pattern result-callback count)

    Move forward across one sexp matched by PATTERN. Set point to the end of the occurrence found, and return point.

    (psearch-forward '`(foo . ,rest))
    ;; |(bar a b         =>    (bar a b
    ;;       (foo 1 2))             (foo 1 2)|)
    • PATTERN pattern to match sexp.
    • RESULT-PATTERN pattern to generate result.
    • RESULT-CALLBACK a function to handle the result, return non-nil if success. The function accpet two arguments:
      • result the result generated by RESULT-PATTERN
      • bounds the bounds of the matched sexp
    • COUNT a number (default 1) that indicates the number of occurrences to search for. The current number will be stored in psearch-count-current which avariable in RESULT-CALLBACK.
  • psearch-backward (pattern &optional result-pattern result-callback count)

    Move backward across one sexp matched by PATTERN. Set point to the beginning of the occurrence found, and return point.

    (psearch-backward '`(foo . ,rest))
    ;; (foo a b         =>    |(foo a b
    ;;      (bar|1 2))              (bar 1 2))
    • PATTERN pattern to match sexp.
    • RESULT-PATTERN pattern to generate result.
    • RESULT-CALLBACK a function to handle the result, return non-nil if success. The function accpet two arguments:
      • result the result generated by RESULT-PATTERN.
      • bounds the bounds of the matched sexp.
    • COUNT a number (default 1) that indicates the number of occurrences to search for. The current number will be stored in psearch-count-current which avariable in RESULT-CALLBACK.
  • psearch-replace (match-pattern replace-pattern &splice)

    Replace some occurences mathcing MATCH-PATTERN with REPLACE-PATTERN.

    (psearch-replace '`(foo . ,rest) '`(bar ,@rest))
    ;; |(foo a b         =>    (bar a b
    ;;       (c 1 2))               (c 1 2))|

    or M-x psearch-replace RET `(foo . ,rest) RET `(bar ,@rest).

    REPLACE-PATTERN also can be a pair:

    (psearch-replace '`(setq ,sym ,val)
                     '(`(,sym ,val)                    ;; collect only
                       `(setq ,@(-flattern-n 1 its)))) ;; apply when search complete
    ;; [(setq foo 1)  =>  (setq foo 1
    ;;  (setq bar 2)]           bar 2)|

    Splice the the replacement:

    (psearch-replace '`(setq . ,(and rest (guard (> (length rest) 2))))
                     '(mapcar (lambda (pair)
                                (cons 'setq pair))
                       (seq-partition rest 2))
                     nil nil
                     :splice t)
    ;; |(setq foo 1    =>   (setq foo 1)
    ;;        bar 2)        (setq bar 2)|

    Without :splice t the result will be:

    ((setq foo 1)
     (setq bar 2))
  • psearch-replace-at-point (match-pattern replace-pattern &key splice)

    Similar to psearch-replace, but replace only the sexp at point, that is, the point is at the beginning of it or inside it. For example:

    |(match a (b c))     =>     |(replace a (b c))  ;; point at beginning of sexp
     (match a (b|c))     =>     |(replace a (b c))  ;; point in sexp
    

    Unlike ‘psearch-replace’, the replace-pattern here does not support (and is not necessary) the collect->finle operation.

  • psearch-patch (orig-func-spec &rest patch-form)

    Patch function with PATCH-FORM.

    ORIG-FUNC-SPEC could be a symbol if function is defined by defun/cl-defun:

    (psearch-patch test
      (psearch-replace '`(if nil ,body)
                       '`(if t ,body)))
    ;; (defun test ()               =>    (defun test ()
    ;;   (list '(1 2 3)                     (list '(1 2 3)
    ;;         (if nil '(4 5 6))                  (if t '(4 5 6))
    ;;         '(7 8 9)))                         '(7 8 9)))

    or a list if it is a generic method:

    ;; Definition
    (cl-defgeneric test (_tag) nil)
    (cl-defdefmethod test ((tag (eql foo))) (list 1 (if nil 2) 3))
    ;; Patch
    (psearch-patch (cl-defmethod test ((tag (eql tag))))
      (psearch-replace '`(if nil ,body)
                       '`(if t ,body)))

    The target function will be patched only if PATCH-FORM returns a non-nil, otherwise raise an error. If the PATCH-FORM contains multiple statements, make sure each one of them executes successfully:

    (with-eval-after-load 'corfu-doc-terminal
      (psearch-patch corfu-doc-terminal--preprocess-docstring
        ;; Delete all `(let ...)` form except the first.
        (let ((pattern '`(let . ,rest)))
          (and (psearch-forward pattern)
               (psearch-replace pattern nil)))))

Known issue

Since the behaviour of thing-at-point has changed since 27 (0efb881), the result of the following code will vary depending on the Emacs version:

;; |(foo (bar))
(when (psearch-forward '`(bar))
  (thing-at-point 'sexp))
;; => (bar) [28.0]
;;    (bar) [27.1]
;;     nil  [26.1]
;;     nil  [25.1]

The reliable way to get the sexp is:

;; |(foo (bar))
(let (sexp-bounds)
  (when (psearch-forward '`(bar) t (lambda (_ bounds) (setq sexp-bounds bounds)))
    (buffer-substring (car sexp-bounds) (car sexp-bounds))))

About

Pcase based search for Emacs Lisp

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages