org-babel: 按需加载所有的语言

org-bable 支持很多的语言,但每次在配置中修改需要加载的语言很麻烦。而且增加越来越多的语言到 org-babel-load-languages,会造成启动 org-mode 非常慢,特别是在 Mac 和 Windows 系统。

比如,增加 C 语言的支持时,就会自动加载 cc-mode,但我很多时候只是要打开 org-mode 编辑一下文档而已,并不需要这些编程语言。

不知道 org-mode 有没有这么一个选项,可以在执行 org-babel-execute-src-block 时再加载相应的语言?

目前我是通过下面的 advice 来实现这样的需求,请各位大佬指教:

(defun my/org-babel-execute-src-block (fn &rest ARG)
    "Load language if needed"    
    (let* ((lang (org-element-property :language (org-element-at-point)))
           (language (if (string-equal lang "cpp") "C" lang)))
      (unless (cdr (assoc (intern language) org-babel-load-languages))
        (add-to-list 'org-babel-load-languages (cons (intern language) t))
        (org-babel-do-load-languages 'org-babel-load-languages org-babel-load-languages)))
    (apply fn ARG))

  (advice-add 'org-babel-execute-src-block :around #'my/org-babel-execute-src-block )
1 个赞

把对应的函数 autoload 就可以了

(use-package ob-emacs-lisp
      :commands (org-babel-execute:elisp
                 org-babel-expand-body:elisp
                 org-babel-execute:emacs-lisp
                 org-babel-expand-body:emacs_lisp))

问题是在打开 org-mode 之前,我是不知道自己会用到那个语言的。 这样不是要对所有的 org-babel 都进行设置。

我自己会用的语言就那些,所以还好,本质上和你设置 org-babel-load-languages 一样的,只是长点

发现邮件列表上有这个方面的讨论,doom-emacs 支持 org babel 的 lazy load https://lists.gnu.org/archive/html/emacs-orgmode/2020-02/msg00595.html

可以去提个 issue,按需加载是比较省心一点。

advice 没必要用 :around,用 :before 就可以了。而且 info 参数已经包含所有信息,直接使用即可:

(defun my/org-babel-execute-src-block (&optional _arg info _params)
  "Load language if needed"    
  (let* ((lang (nth 0 info))
         (sym (if (member (downcase lang) '("c" "cpp" "c++")) 'C (intern lang)))
         (backup-languages org-babel-load-languages)
         (pair (assoc sym backup-languages)))
    ;; - `(LANG . nil)' 是有意义的,不宜覆盖,详见 `org-babel-do-load-languages'。
    ;; - 只加载当前语言,「按需」到底。
    (unwind-protect
        (org-babel-do-load-languages 'org-babel-load-languages (list (cons sym t)))
      (setq-default org-babel-load-languages
                    (if pair
                        backup-languages
                      (append (list (cons sym t)) backup-languages))))))

(advice-add 'org-babel-execute-src-block :before #'my/org-babel-execute-src-block )

多谢大佬指正,我测试了你的方案,很棒! :+1:

但是目前我们使用的这种 advice 的方案有个问题:

只要打开的buffer中包含了相应语言的 Code block,就会自动加载相应的语言。这个会造成打开这些buffer 时会很慢。 我觉得只有在用户执行 C-c , C-c org-babel-execute-src-block 时才会加载会不会更好一些?

我对在邮件列表上提 issue 不怎么熟悉 :sweat_smile:

doom Emacs的orgmode模块有lazy load语言的配置,可以解决你的这些需求。

org-babel-load-languages 在初始化的时候只存放 (LANG . nil),表示需禁止的语言。其它所有需要的语言都动态加载,加载成功后存入 org-babel-load-languages

(defun my/org-babel-execute-src-block (&optional _arg info _params)
  "Load language if needed"
  (let* ((lang (nth 0 info))
         (sym (if (member (downcase lang) '("c" "cpp" "c++")) 'C (intern lang)))
         (backup-languages org-babel-load-languages))
    ;; - (LANG . nil) 明确禁止的语言,不加载。
    ;; - (LANG . t) 已加载过的语言,不重复载。
    (unless (assoc sym backup-languages)
      (condition-case err
          (progn
            (org-babel-do-load-languages 'org-babel-load-languages (list (cons sym t)))
            (setq-default org-babel-load-languages (append (list (cons sym t)) backup-languages)))
        (file-missing
         (setq-default org-babel-load-languages backup-languages)
         err)))))

(advice-add 'org-babel-execute-src-block :before #'my/org-babel-execute-src-block )

EDIT: Add (condition-case ...)

5 个赞

这也是个办法 :+1:
目前这样用,效果已经很好了。

Doom 的方案我也试过了,效果是一样的。这样简洁一点的方案好。

org-mode 另一大耗时步骤是 (org-load-modules-maybe),在我这里它占了整个 org 近 90% 的消耗,而其中 ol-gnus 模块又占了九成中的九成。

这个问题在设置了 package-user-dir 并且 (package-initialize) 之后才会凸显:

$ emacs -Q -nw --eval \
"(add-hook 'emacs-startup-hook
           (lambda ()
             (view-echo-area-messages)
             (setq package-user-dir \"~/.emacs.d/28.0.50/elpa\") ;; <---
             (package-initialize)
             (print (let ((t0 (current-time))) (org-mode) (- (float-time (current-time)) (float-time t0)))))))"

最简单的办法就是去掉耗时的模块,如果你需要用到它们就得另寻他法了:

(with-eval-after-load 'org
  (setq org-modules (cl-set-difference org-modules '(ol-gnus ol-eww))))

这个问题,我之前也注意到了。我是直接在 early-init.el 里设置了:

(setq org-modules-loaded t)

这样在加载 org-mode 的时候就不会加载 org-modules 中的模块了吧?
这些模块我是一个都用不到 :smile: