应用背景
我在 org-mode 中输入数学公式的时候,为了尽量加快对具有重复性的符号的输入(例如用粗斜体表示矢量 \bm{a}
),会使用 yasnippet 实现存一些代码片段,需要的时候输入关键子加 tab 展开代码片段(也就是将输入内容替换为代码片段)。
为了更省时间,我希望能够不用输入 tab ,只输入关键字就能展开片段。这就是自动展开功能。
举几个例子:
用户输入 | 被替换成的文本 |
---|---|
a,. |
\vec{a} |
abar |
\overline{a} |
beg |
\begin{} |
latex-auto-activating-snippet 不仅可用于 org-mode,也可用于 tex-mode 等等,只要设置激活条件。
已知解决方案
Vim UltiSnips 自动展开功能
Gilles Castel 在怎么用vim做数学笔记一文中使用 Vim 插件 UltiSnips 中的自动展开功能实现了如下图的片段展开:
对应于
snippet beg "begin{} / end{}" bA
\begin{$1}
$0
\end{$1}
endsnippet
UltiSnips 是通过对一行的内容使用正则表达式匹配实现的,这是 auto-activating-snippets 作者说的。
VSCode LaTeX-Utilities
tecosaur 做了一个 VSCode 插件 LaTeX-Utilities,用于 latex 文档中的数学公式输入。其中有一个 Live Snippets 的功能,通过正则表达式(可以自定义)匹配和替换实现了自动展开的功能。
这里使用了 xx -> \times
和 (a \times b)/ -> \frac{a \times b}{}
。
Emacs yasnippet
yasnippet 也是可以实现这个功能的。在一篇 issue 中有人提出定义一个函数
(defun my-yas-try-expanding-auto-snippets ()
(when (and (boundp 'yas-minor-mode) yas-minor-mode)
(let ((yas-buffer-local-condition ''(require-snippet-condition . auto)))
(yas-expand))))
(add-hook 'post-command-hook #'my-yas-try-expanding-auto-snippets)
然后在 snippet 定义的 condition 中加上:
# -*- mode: snippet -*-
# name: parentheses
# key: ()
# condition: (and (texmathp) 'auto)
# --
\left($1\right)$0
也可以实现类似的功能。
Emacs latex-auto-activating-snippets
这就是我要推荐的 emacs 包。 tecosaur 也对这个包做了贡献。它分了两层:
- 用于自动展开的引擎 auto-activating-snippets
- 各种 latex 代码片段定义 latex-auto-activating-snippets
它的实现方式是,追踪用户最后打的字,如果连续几个字构成的序列(key-sequence)能匹配到某个片段,就展开。 这类似于一个很长前缀的命令,只不过那些前缀会直接进到 buffer 中,直到匹配到后,运行某种函数将函数的输出替换序列。
This package implements an engine for auto-expanding snippets. It is done by tracking your inputted chars along a tree until you complete a registered key sequence.
Its like running a long prefix command, but the keys you type are not ‘consumed’ and appear in the buffer until you complete the whole command - and then the snippet is triggered!
据作者所说,这种实现方式相较于 UltiSnips 的实现方式的优势是,可以在 snippet 很多的时候也不卡,就和有很多 keymaps 的时候也不卡是一个道理。
相较于 yasnippet 的优势是,yasnippet 在每次展开的时候都会检查所有 snippet 的 condition,所以会比较慢。
laas(简称)的配置和使用
我的部分配置
(use-package! laas
:hook (org-mode . laas-mode)
:config
;; 不自动插入空格
(setq laas-enable-auto-space nil)
(aas-set-snippets 'laas-mode
;; 只在 org latex 片段中展开
:cond #'org-inside-LaTeX-fragment-p
"tan" "\\tan"
;; 内积
"i*" (lambda () (interactive)
(yas-expand-snippet "\\langle $1\\rangle$0"))
"sr" "^2"
;; 还可以绑定函数,和 yasnippet 联动
"Sum" (lambda () (interactive)
(yas-expand-snippet "\\sum_{$1}^{$2} $0"))
;; 这是 laas 中定义的用于包裹式 latex 代码的函数,实现 \bm{a}
:cond #'laas-object-on-left-condition
",." (lambda () (interactive) (laas-wrap-previous-object "bm"))
".," (lambda () (interactive) (laas-wrap-previous-object "bm"))))
效果是这样的:
这里使用了:
用户输入 | 被替换成的文本 |
---|---|
a,. |
\bm{a} |
** |
\cdot |
\bm{c}/ |
\frac{\bm{c}}{} |