Org LaTeX Fragment Editor (Ver. 1.2)

注意: 我已经把这个做成了一个包,放在 github 上: GitHub - et2010/org-edit-latex: Edit LaTeX fragments like editing src blocks 便于维护。这里的不再更新。另外,最新版本已经支持行内 latex 的编辑。

已经提交到 MELPA,可以用 package-list-packages 查看并下载,欢迎使用!

在 Org mode 中使用 LaTeX 的正确姿势不是 LaTeX src block,或 export block,而是 LaTeX fragment。因为 latex fragment

  1. 可以预览
  2. 可以预览
  3. 可以预览

预览 LaTex 是 Org 的一个很 cool 的 feature,preview 可以实现整个 Org 文档中不出现 LaTeX 代码,增强可读性。对于用 Org-mode 做科学笔记,写科技论文的人来说非常重要。

但是 latex fragment 在编辑时有一个劣势,就是无法打开一个专门的 buffer 用 auctex 编辑,也就没有代码高亮和自动缩进这些功能。下面的代码解决了这一问题。

使用很简单,把光标移动到 fragment 内部(这里限定了只能编辑 display math,不支持 inline math,因为我个人认为行内公式有 cdlatex 和 yasnippet 一般就足够了)。然后用 org-edit-special 命令开始编辑就行了(我用的 spacemacs,默认是绑定到 , ',编辑完了也是和 src block 一样退出。

这里感谢论坛里各位同学的帮忙,特别是 @xuchunyang @twlz0ne 帮忙解答 advice 方面的问题,还要感谢 @CuriousBull 和我聊天启发我想出了这个方法。欢迎大家试用并提出宝贵的意见。首发 Emacs-China。

最好配合下列配置使用

(setq org-src-preserve-indentation t)

另外,需要把 latex 添加到 org-babel-load-langauges 中去(确保你可以 C-c C-c 运行 latex 代码块)。

Update

Ver 1.2

  • 修复了当 latex fragment 位于 buffer 末尾处时,wrapper 不能正常工作的问题。 Ver 1.1
  • 优化了 LaTeX 环境的判断 Ver 1.0
  • 重写了跳过空行那部份的代码。现在不会再出现搜索不到导致的死循环了
  • 增加了对 $$ $$ 形式 display math 的支持

Known Issue

- latex fragment 不能位于文件的末尾(也就是文件末尾至少要有一个空行),不然会进入死循环,可以用 C-g 退出。欢迎提出解决方案。

  • 如果出现了不 work 的情况,一般是正则表达式不匹配造成的死循环,请将你的不工作的例子贴在下面,方便我进行 debug,谢谢!
(defun org-wrap-latex-fragment ()
  "Wrap latex fragment in a latex src block"
  (interactive)
  (let* ((ele (org-element-context))
         (beg (org-element-property :begin ele))
         (end (org-element-property :end ele))
         (nb (org-element-property :post-blank ele))
         (env-p (save-excursion
                  (goto-char beg)
                  (looking-at-p "^[ \t]*\\\\begin"))))
    (when (memq (org-element-type ele)
                '(latex-fragment latex-environment))
      (save-excursion
        (cond
         (env-p
          (goto-char end)
          (when (not (and (eobp)
                          (equal 0 nb)
                          (save-excursion
                            (beginning-of-line)
                            (looking-at-p "[ \t]*\\\\end{"))))
            (forward-line (- (1+ nb)))
            (end-of-line))
          (insert "\n#+END_SRC")
          (goto-char beg)
          (insert "#+BEGIN_SRC latex\n"))
         (t
          (goto-char end)
          (insert "\n#+END_SRC")
          (goto-char beg)
          (beginning-of-line)
          (insert "#+BEGIN_SRC latex\n")))))))

(defun org-unwrap-latex-fragment (&rest args)
  "Unwrap latex fragment"
  (interactive)
  (let* ((ele (org-element-context))
         (lang (org-element-property :language ele))
         (beg (org-element-property :begin ele))
         (end (org-element-property :end ele))
         (nb (org-element-property :post-blank ele)))
    (when (and (eq 'src-block
                   (org-element-type ele))
               (string= "latex" lang))
      (save-excursion
        (goto-char end)
        (if (and (eobp)
                 (equal 0 nb)
                 (save-excursion
                   (beginning-of-line)
                   (looking-at-p "#\\+end_src")))
            (delete-region (point-at-bol) (point-at-eol))
          (forward-line (- (1+ nb)))
          (delete-region (point-at-bol) (1+ (point-at-eol))))
        (goto-char beg)
        (delete-region (point-at-bol) (1+ (point-at-eol)))))))

(defun org-wrap-latex-fragment-maybe (&rest args)
  "Wrap a latex fragment with \"begin_src latex\" and \"end_src\" so that we could edit it like a latex src block. This only works on display math."
  (when (save-excursion
          (goto-char (org-element-property :begin (org-element-context)))
          ;; display math :
          (looking-at-p "[ \t]*\\$\\$\\|[ \t]*\\\\\\[\\|[ \t]*\\\\begin"))
    (org-wrap-latex-fragment)))

(advice-add #'org-edit-special :before #'org-wrap-latex-fragment-maybe)
(advice-add #'org-edit-src-exit :after #'org-unwrap-latex-fragment '((depth . 100)))

效果

8 个赞

太给力鸟,不过我在windows下测试,不明原因卡顿,明天在Linux平台下试试 :joy:

卡顿有可能是和其它函数冲突造成的,你可以看看 org-edit-specialorg-edit-src-exit 是否有其它 advice,另外最好也检查一下 org-src-mode-hook

我这边使用没有卡顿的现象,等下放动图 :joy:

挺申请,本来以为是我自己配置的一些org,会有冲突,于是注释了我自己的配置,但是依然卡顿,具体来讲,就是,只要一用到你这个函数,笔记本风扇开始狂转:joy: 我用的默认的org layer的配置是这样的

先把这些信息提供给我,我帮你看看

另外,需要注意:

另外最好把你试验的 latex fragment 也粘贴在这里

只是进入死循环而已,不要说得那么恐怖:joy:。因为我里面用到了 regex 来匹配,如果匹配不到,就会一直搜索,进入死循环。所以最好能够提供不 work 的例子,我好改进正则表达式。

这只是一个简单的脚本,是不会耗用你大量的系统资源的。正常使用情况下是不会造成卡顿的,因为它只在进入/退出编辑模式的时候起作用…

确实是因为我写的latex代码在文件尾,加了一个空行就ok了

1 个赞

奇怪的是,我在正常的org文件中进行latex输入时,如果我先加入前缀

#+BEGIN_SRC latex
latex code
#+END_SRC

那么,几乎肯定会进入死循环,这种情况如何避免?

不要用 begin_src latex 了,原因我上面已经说了

latex src block 基本上没有什么用处,所以我这里借助它来进入 latex buffer。

(事实上,使用这个代码以后,退出 latex 代码块会自动将 begin_src latex 和 end_src 删除,当然只会删除 latex 代码块的,对其它的没有任何影响

我没有碰到你说的这种情况。

如果你坚持要用 latex 代码块的话,能不能把你的 code (包括前后的段落)贴出来,我看一下?

其中的几乎所有代码块都会遇到这种问题,之所以偏爱使用代码块,是因为spacemacs内置的优秀的补全方式,譬如, 当我需要输入 \beign{equation} … \end{equation} 这样的代码时,如果不在src block中进行,需要输入太多字了,而在src block中,我只需要输入’\bege" 然后选择即可, 因为我对snippet不熟,就它内置的补全我就挺满意了

你补全是在 org buffer 中进行的吗?还是在 org edit src buffer?

这个补全是 auctex 的作用,你进入 src edit buffer 以后也是用它来补全代码的

抱歉,我实在看不到继续坚持 latex src block 还有什么意义。

src block的补全代码也得进入到 edit buffer内,我这个代码(本质上也是src block,你可以理解为隐藏的 latex src block)也是进入到 edit buffer 内,都是用的同样的补全(auctex),所有的功能一样也没少。

增加的就是更方便预览。而且没有那么多latex代码块需要维护,也不用操心是不是需要导出,是导出结果还是导出代码这类问题。如果你只是用 org 记笔记的话,这样很合适,不是吗?

你也可以反过来思考,给代码块添加latex-frame预览功能:-)

是的,但是貌似那样也没有什么大的区别,不是吗?

差不多,唯一的区别就是更容易实现,边界处理比较容易,毕竟有明确的BEGIN/END

是的,因为我平常是不用 latex 代码块的,所以选择了现在这种方式。另外, org 下面 latex fragment 的预览更自然,而且有很多现成的 hack 可以用。

但是,细想一下两种方式工作量应该差不多。

边界不是问题,都是通过 org-element-property 取得的。如果是实现 src block 预览的话,还得处理生成预览以后恢复代码块的问题。大概顺序是 unwrap => preview => wrap。我目前的实现是 wrap => edit => unwrap,所以说工作量应该是差不多的。

Edit: 我又仔细思考了一下,感觉那种方式可能工作量更大,因为还牵涉到预览以后,overlay 图片和 latex code 之间的转换,因为必须考虑到后续 latex 代码的修改。感觉问题更复杂了。 @tumashu 我的想法不一定对,欢迎指正。

org edit src buffer中,会有自动补全的提示候选栏,我觉得挺好的,譬如我输入\beg,就会有\begin{equation},\begin{eqnarray}等候选,然后我按需要选择即可,这个是让我觉得最方便的:grin: