[分享][新手向] leaf block 和 leaf-find 以及自己实现一个 leaf-find-with-unit

leaf block

leaf.el 称下面代码块 为一个 leaf block, +unit-test-erro就是他的名字,可以通过函数 leaf-find 找到并跳转到所在的文件(下文会大致讲一下机制细节)

(leaf +unit-test-erro
  :ensure nil ;; 因为 +unit-test-erro 毕竟不是包
  :config 
  (sexp ok))

对上面的leaf宏展开一层(emacs-lisp-macroexpand

(prog1 '+unit-test-erro

  ;; 又一个 leaf 的宏
  ;; 大致功能:如果执行的文件是 load-path 里的文件就存人一个 leaf block 信息
  ;; 本文语境下,就认为供 `leaf-find` 用
  (leaf-handler-leaf-path +unit-test-erro) 

  ;;  :preface :ensure t 本文简单理解为(requrie '包)  :init :config
  ;;  都按顺序放到这里面运行
  ;;  这个宏里面有类似 java 的 try catch (也是后面测试时报错的信息来源)
  (leaf-handler-leaf-protect +unit-test-erro
    (sexp ok)
   ) 
)

leaf-find

上面的 +unit-test-erro 就出现在 leaf-find 的候选补全列表里了(但此时只能跳转到所在的 el 文件,并不能正确跳转到所在行,下文解释)

先来看 leaf-find 是怎么实现的

  (defun leaf-find (name)
    "Find the leaf block of NAME."

    ;; 利用 `leaf--paths` 里存的数据作为用户的候选列表
    ;; 而这个 `leaf--paths` 正是上面的 
    ;;  `(leaf-handler-leaf-path +unit-test-erro)` 所维护的
    (interactive
     (let ((candidates (delete-dups (mapcar #'car leaf--paths))))
       (if (not candidates)
           (error "Leaf has no definition informations")
         (list (completing-read "Find leaf: " (delete-dups (mapcar #'car leaf--paths)))))
       );;let ends here
     );;ineractive ends here
    
    ;; 最后一步中用 find-func 的 `(find-function-search-for-symbol name 'leaf path))`
    ;; 根据 leaf 的 regex 规则(下文讲)匹配到精确行
    ;; 也就是为什么前面 展开成`(prog1 '+unit-test-erro `)而不是 `(leaf +unit-test-erro`
    ;; 会找不到 `+unit-test-erro` 这个 leaf block 所在行的原因
    (require 'find-func)

    ;; 现在要一步步从数据中取出 `+unit-test-erro` leaf block
    (let* ((name (intern name))
            ;; 根据名字取出 leaf block 所在文件路径(可能多个)
           (paths (mapcan (lambda (elm) (when (eq name (car elm)) (list (cdr elm)))) leaf--paths))
            ;; 如果多个让用户选一个
           (path (if (= (length paths) 1) (car paths) (completing-read "Select one: " paths)))
            ;; 搜索匹配到 leaf block 所在行
           (location (find-function-search-for-symbol name 'leaf path)))
      
      ;; 执行跳转
      (when location
        (prog1 (pop-to-buffer (car location))
          (when (cdr location)
            (goto-char (cdr location)))
          (run-hooks 'find-function-after-hook)))))

看一下 leaf–paths (alist) 里的数据 (cons)

leaf--paths is a variable defined in ‘leaf.el’.
Value:
( (+unit-test-erro . "/Users/ingtshan/.emacs.d/etc/init/init-os.el") )

leaf block 和 leaf-find 总结

对于我这样的初学者来说, leaf-find 特别好用,可以快速跳转到自己的配置里任意地方(前提是用 leaf 装起来),其次 block 里的 require 和 自己的代码都放到 leaf-handler-leaf-protect里执行,出错时,不用打开 -debug-init 也能看到具体是哪段代码有问题。

而且 leaf.el 的代码可读性很高,不用怕展开后看不懂。

have fun :stuck_out_tongue_winking_eye:

2 个赞

接下来就是我自己运用上面的东西,设计一个符合自己需求的功能(不改动 leaf.el 的代码)

需求

  • 自己有大块大块的代码,还不足以提取成一个包
  • 同时又想放到一个 block 里,像 leaf-find 那样找到
  • 但不应该和包名混淆

解决

  • 在 leaf block 的基础上引入一个自定义的概念 unit 用前缀 +unit- 区分
  • 并且新的find 也兼顾 leaf-find (毕竟就是更细的搜索和定位配置文件而言)
  ;; add my leaf-unit module
  (defcustom leaf-find-unit-regexp ".*([[:space:]]*leaf-unit[[:space:]]+\\(%s\\)"
    "The regexp used by `leaf-find-with-unir' to search for a leaf block.
Note it must contain a `%s' at the place where `format'
should insert the leaf name."
    :type 'regexp
    :group 'leaf)

  (require 'find-func)
  (add-to-list
   'find-function-regexp-alist
   '(leaf-unit . leaf-find-unit-regexp))

  (defmacro leaf-unit (base &rest body)
    "do the sexp in body with leaf-bolck name base-unit
Generate code like (leaf base-name-unit :config body)"
    (declare (indent 1))
    (let ((base (intern (format "+unit-%s" `,base))))
      `(prog1 ',base
         (leaf-handler-leaf-path ,base)
         (leaf-handler-leaf-protect ,base ,@body))))

  (defun leaf-find-with-unit (truename)
    "Find the leaf block (and self make +unit-) of NAME."
    (interactive
     (let ((candidates (delete-dups (mapcar #'car leaf--paths))))
       (if (not candidates)
           (error "Leaf has no definition informations")
         (list (completing-read "Find leaf: " (delete-dups (mapcar #'car leaf--paths)))))))
    (let* ((name (intern truename))
           (paths (mapcan (lambda (elm) (when (eq name (car elm)) (list (cdr elm)))) leaf--paths))
           (path (if (= (length paths) 1) (car paths) (completing-read "Select one: " paths)))
           (location nil))
      (setq location
            (if (string-match-p "^+unit-" truename)
                (find-function-search-for-symbol
                 (intern (substring truename +6))
                 'leaf-unit path)
              (find-function-search-for-symbol name 'leaf path)))
      (when location
        (prog1 (pop-to-buffer (car location))
          (when (cdr location)
            (goto-char (cdr location)))
          (run-hooks 'find-function-after-hook)))))

2 个赞

请继续。我现在在用 leaf.el,并结合 straight.el,到目前一切都很好。