《elisp 每日一练》—— 综合练习

《elisp 每日一练》—— 综合练习

Masimaro
2026-08-22 / 0 评论 / 0 阅读 / 正在检测是否收录...

到此为止我们已经对处理文本有了一个新的认识。回顾之前的内容,我们主要介绍了针对文本的普通特性,通过正则表达式进行搜索和捕获,以及关于属性的内容。针对编程语言的类型的文本,我们可以通过syntax-table 和 tree-sitter相关机制进行处理。

知识回顾

针对正则表达式的晦涩以及emacs中特有的连续 \的处理,我们可以使用 rx 宏处理

在有关文本属性方面,我们介绍了如下一些函数

  • add-text-properties
  • put-text-property
  • remove-text-properties
  • set-text-properties
  • add-face-text-property
  • get-text-property

后面我们介绍了emacs 中的撤销与反撤销,以及如何进行文本变动方面的现场保护和还原,有以下几个函数:

  • undo
  • undo-redo
  • undo-boundary
  • atomic-change-group

如果我们将文本视为一个编程语言的源码或者配置文件这种有一定语法规则的文本,emacs 为我们提供了基本的词法分析的 Syntax-Table, 操作的相关函数有:

  • forward-sexpbackward-sexp
  • scan-sexps
  • syntax-ppss

如果我们想从语法层面区分每个部分,可以通过新引入的 treesitter机制。有关的函数比较多,主要有:

  • treesit-available-p
  • treesit-language-available-p
  • treesit-parser-create
  • treesit-parser-list
  • treesit-parser-buffer
  • `treesit-parser-language
  • treesit-buffer-root-node
  • treesit-node-type
  • treesit-node-text
  • treesit-node-starttreesit-node-end
  • treesit-node-child
  • treesit-node-parent
  • treesit-node-prev-siblingtreesit-node-next-sibling
  • treesit-node-check
  • treesit-node-p
  • treesit-node-eq
  • treesit-query-capture
  • treesit-query-string
  • treesit-query-validate

综合练习

本次的练习,我们结合前面的内容,来完成一个简单的显示当前buffer函数定义的面板。它的主要功能有:

  • 搜集当前buffer中的所有elisp函数定义
  • 将这些函数定义集中到新的面板中进行显示
  • 在新的面板中,我们将光标放到对应的函数上可以回到函数定义的位置

我们将上一节的代码拿过来做一个修改

(defun my/get-all-functions-define ()
  "遍历当前buffer中的所有函数定义,并将它们一一记录到对应面板中"
  (interactive)
  (unless (cl-some (lambda (p)
                     (eq (treesit-parser-language p) 'elisp))
                   (treesit-parser-list))
    (treesit-parser-create 'elisp))
  (let* ((root (treesit-buffer-root-node))
         ;; 捕获 defun 的根节点 @def_node 以获取整个函数的起始位置
         (captures (treesit-query-capture root "(function_definition name: (symbol) @func_name) @def_node")))
    (dolist (capture captures)
      ;; captures 包含 @func_name 和 @def_node,只需处理 @func_name 匹配项
      (when (eq (car capture) 'func_name)
        (let* ((node (cdr capture))
               (name (treesit-node-text node))
               ;; 拿到该函数所在 defun 节点的起点位置
               (def-node (treesit-node-parent node))
               (start (treesit-node-start def-node))
               (buffer (current-buffer)))
          (my/add-function-to-function-panel name start buffer))))))

这部分的代码,我们大部分都延续上一节的内容,不过在存储数据时我们调用一个额外的函数将数据存储到一个新的buffer中。

存储buffer的部分,我们主要是通过创建新的buffer,并且将这些数据以属性的方式加入。它的实现如下:

(defun my/add-function-to-function-panel (name start buffer)
  "将函数信息加入新的面板中"
  (let ((panel-buffer (get-buffer-create "*function-panel*")))
    (with-current-buffer panel-buffer
      (let ((inhibit-read-only t))
        (goto-char (point-max))
        ;; 1. 记录插入前的位置
        (let ((start-point (point)))
          (insert name "\n")
          ;; 2. 使用反引号 `(...) 允许 ,buffer 和 ,start 动态求值
          (add-text-properties start-point (1- (point))
                               `(source-buf ,buffer
                                 function-point ,start)))))))

有了这些信息,在我们打开面板之后,执行跳转的函数,我们读取当前位置的属性,根据事先存储的属性信息,找到原始的buffer和对应的point值,根据这些值执行跳转

(defun my/jump-to-source-buffer ()
  "跳转回到源码位置"
  (interactive)
  (let ((source-buffer (get-text-property (point) 'source-buf))
        (func-point (get-text-property (point) 'function-point)))
    (when (and source-buffer func-point)
      (pop-to-buffer source-buffer)
      ;; 修正变量引用与位置跳转
      (goto-char func-point))))

总结

到此我们关于文本部分的学习就都结束了。上述的练习只实现了基础功能,很多地方考虑的不完善,例如多次执行有重复信息加入。没有考虑后续源码buffer被清理掉的问题。这些问题读者如果有兴趣可以自行完善。

0

评论 (0)

取消