通过几个函数用 org-mode 管理 emacs 配置代码片段,并在需要时快速执行

我采用文学式编程的方法来管理我的 Emacs 配置。与将配置文件分散到多个 .el 文件中的传统方法相比,这种方法使我能够更便捷地对代码进行注释说明和重新排列组合。

然而,我们不可能始终保持所有配置处于激活状态,否则 Emacs 的运行速度会显著下降。因此,某些代码应当在需要时才执行。

如果我将所有代码分散存储在不同的文件中,可以通过 require 的方式导入这些代码。但如果将这些配置代码放在 Org 文件的 src-block 中,某些 block 可能仅包含一个函数。在这种情况下,为每个 block 都编写一个 provide 语句,似乎有些过度。

另一方面,如果每次需要执行某段代码时,都必须找到配置文件中的对应代码块并手动执行,这显然也相当繁琐。

为此,我编写了几个函数来帮助管理我的配置文件。这些函数的通用性较强,稍作修改后,应该还可以用于管理其他代码库。

0. 配置文件地址

首先需要设定一个变量用来存储配置文件的地址。注意这是一个 org 文件

(defvar src/default-file-path "/path/to/.doom.d/manual.org"
  "Default file path for source code blocks.")

1. 主函数:选择并执行代码块

当需要执行某一部分代码的时候,就执行这个函数。

按下 C-u 再执行这个函数就可以选择 org 文件,从而执行不是 src/default-file-path 中设置的文件。

值得注意的是,只会列出有 ~#+name:~ 属性的代码块。

(defun src/choose-and-execute-block (&optional file-path)
  (interactive
   (list (if current-prefix-arg
             (src--choose-folder-or-org-file default-directory)
           src/default-file-path)))
  (if (file-directory-p file-path)
      (src/choose-and-execute-block (src--choose-folder-or-org-file file-path))
    (let ((block-name (src/choose-named-src-block file-path)))
      (src/execute-code-block-with-name block-name))))

以下这些函数,列出一个文件下所有的 src-block, 根据一个函数的名称在一个文件中将其找到并执行,以及选择一个 src-block 是为了上面的 src/choose-and-execute-block 能正确执行而写的辅助函数。

2. 选择文件夹或 .org 文件

(defun src--choose-folder-or-org-file (dir)
  "Prompt the user to choose a folder or .org file under DIR."
  (let ((file (read-file-name "Choose a folder or .org file: " dir nil t
                              nil
                              (lambda (name)
                                (or (file-directory-p name)
                                    (and (file-regular-p name)
                                         (string= (file-name-extension name) "org")))))))
    (if (file-directory-p file)
        (message "You chose a directory: %s" file)
      (message "You chose an .org file: %s" file))
    file))

3. 执行指定名称的代码块

(defun src/execute-code-block-with-name (code-block-name &optional file-path)
  "Execute the CODE-BLOCK-NAME in the specified FILE-PATH.
If FILE-PATH is not provided, defaults to `src/default-file-path`."
  (interactive
   (list (read-string "Code block name: ")
         (if current-prefix-arg (read-file-name "File: " nil src/default-file-path))))
  (with-temp-buffer
    (insert-file-contents (or file-path src/default-file-path))
    (org-mode) ; Ensure Org mode is active for proper parsing
    (org-babel-goto-named-src-block code-block-name)
    (org-babel-execute-src-block)))

4. 列出文件中所有命名的代码块

(defun src/list-named-code-blocks (&optional file-path)
  "Return a list of all named source code blocks in the specified FILE-PATH.
If FILE-PATH is not provided, defaults to `src/default-file-path`."
  (with-temp-buffer
    (insert-file-contents (or file-path src/default-file-path))
    (org-mode) ; Ensure Org mode is active for proper parsing
    (org-element-map (org-element-parse-buffer) 'src-block
      (lambda (src-block)
        (let ((name (org-element-property :name src-block)))
          (when name
            name))))))

5. 选择命名的代码块

(defun src/choose-named-src-block (&optional file-path)
  "Choose a named src block from the specified FILE-PATH in the mini-buffer.
If called without C-u, defaults to `src/default-file-path`.
If called with C-u, prompts for the file path."
  (let* ((named-blocks (src/list-named-code-blocks file-path))
         (chosen-block (completing-read "Choose a named src block: " named-blocks)))
    chosen-block))
1 个赞

补充了几个函数用于执行一整个子树的代码。

6. 主函数:选择并执行子树下的所有代码块

(defun src/choose-and-execute-all-blocks-in-subtree (&optional file-path)
  (interactive
   (list (if current-prefix-arg
             (src--choose-folder-or-org-file default-directory)
           src/default-file-path)))
  (if (file-directory-p file-path)
      (src/choose-and-execute-all-blocks-in-subtree (src--choose-folder-or-org-file file-path))
    (let* ((subtree-name (my/choose-subtree)))
      (my/execute-all-src-blocks-in-subtree file-path subtree-name))))

7. 列出文件中所有的子树

(defun my/list-all-subtrees (&optional file-path)
  "List all subtrees in an Org file."
  (with-temp-buffer
    (insert-file-contents (or file-path src/default-file-path))
    (org-mode) ; Ensure Org mode is active for proper parsing
    (org-element-map (org-element-parse-buffer) 'headline
      (lambda (headline)
        (let ((title (org-element-property :raw-value headline)))
          (when title
            title))))))

8. 执行子树下所有的源代码块

(defun my/execute-all-src-blocks-in-subtree (&optional file-path subtree-name)
  "执行指定 Org 文件中的子树下的所有源代码块(支持异步)。
FILE-PATH: 可选,指定 Org 文件路径,默认为当前文件。
SUBTREE-NAME: 可选,指定子树标题,默认为当前子树。"
  (interactive)
  (let* ((file-path (or file-path (buffer-file-name)))
         (subtree-name (or subtree-name (org-get-heading t t t t)))
         (buffer (find-file-noselect file-path)))
    (with-current-buffer buffer
      (unless (org-in-src-block-p)
        (save-excursion
          (save-restriction
            (widen)
            (goto-char (point-min))
            (when (re-search-forward (concat "^\\*+ " subtree-name) nil t)
              (org-back-to-heading)
              (let* ((beg (point))
                     (end (progn (org-end-of-subtree) (point)))
                     (counter 0)
                     (total 0)
                     (org-confirm-babel-evaluate nil)
                     (inhibit-message t))

                ;; 统计代码块总数
                (org-element-map (org-element-parse-buffer) 'src-block
                  (lambda (block)
                    (when (<= beg (org-element-property :begin block) end)
                      (cl-incf total))))

                (when (> total 0)
                  ;; 遍历执行代码块
                  (org-element-map (org-element-parse-buffer) 'src-block
                    (lambda (block)
                      (when (<= beg (org-element-property :begin block) end)
                        (let ((block-beg (org-element-property :begin block))
                              (lang (org-element-property :language block)))
                          (save-excursion
                            (goto-char block-beg)
                            (cl-incf counter)
                            (message "正在执行代码块 %d/%d (%s)..."
                                     counter total lang)
                            (condition-case err
                                (if (member lang '("python" "shell" "javascript"))
                                    (org-babel-execute-src-block 'async) ; 异步执行支持
                                  (org-babel-execute-src-block)) ; 同步执行其他语言
                              (error (message "执行失败: %s" err)))))))
                    nil beg end)

                  (message "完成!已执行 %d 个代码块" total))
                (message "当前子树没有可执行的代码块")))))))))

9. 选择子树

(defun my/choose-subtree (&optional file-path)
  "Prompt the user to choose a subtree from the specified FILE-PATH.
If FILE-PATH is not provided, defaults to the current buffer's file."
  (interactive)
  (let* ((subtrees (my/list-all-subtrees file-path))
         (chosen-subtree (completing-read "Choose a subtree: " subtrees)))
    chosen-subtree))

(my/choose-subtree)

1 个赞