前几天, 在知乎上看到一篇文章:
量子位:1700页数学笔记火了!全程敲代码,速度飞快易搜索,硬核小哥教你上手LaTeX+Vimzhuanlan.zhihu.com![c9b591741bf9a5fbe4d0de383224b3eb.png](https://img-blog.csdnimg.cn/img_convert/c9b591741bf9a5fbe4d0de383224b3eb.png)
.
文章中的外国小哥, 用vim编辑latex, 甚至可以做到与板书同步. 这是怎么做出来的呢? 除了我之前提到的vim-latex-suite插件和vimtex插件, 还有一个强力插件: snippet!
1. snippet和UltiSnips的安装
用 vim-plug 插件管理器的话, 可以用下面几行代码安装 UltiSnips 插件:
Plug 'sirver/ultisnips'
Plug ‘honza/vim-snippets’
let g:UltiSnipsExpandTrigger = '<tab>'
let g:UltiSnipsJumpForwardTrigger = '<tab>'
let g:UltiSnipsJumpBackwardTrigger = '<s-tab>'
" If you want :UltiSnipsEdit to split your window.
let g:UltiSnipsEditSplit="vertical"
我们解释一下这几个命令:
Plug 'sirver/ultisnips’
: 加载插件.Plug ‘honza/vim-snippets’
: 加载vim-snippets插件. 这个插件提供了很多默认snippet命令.let g:UltiSnipsExpandTrigger = '<tab>’
: 用<tab>
展开片段代码.let g:UltiSnipsJumpForwardTrigger = '<tab>’
: 用<tab>
跳到下一个位置.let g:UltiSnipsJumpBackwardTrigger = '<s-tab>'
: 用shift
+<tab>
跳到上一个位置.
将上述代码复制到你的~/.vimrc
文件中的plug
代码管理区, 用:so %
重新加载一下, 然后用:PlugInstall
命令安装插件. 重启vim, 即可使用.
默认的代码块存到哪里了呢? 比如, 我的插件安装在~/.vim/plug
文件夹下, 那么, 在这个文件夹里, 又一个叫做vim-snippets/UltiSnips
的命令, 在这里, 找到tex.snippets
文件, 打开, 就可以看到许多自定义的snippet
命令. 以这些为例, 可以自己写出自己想要的snippet
代码块.
另外多说一句, 可以参考neocomplete
等补全插件配合snippet使用, 效果更佳.
2. 几个自带的代码块的讲解
2.1. enumerate 环境:
在tex.snippets
中, 我们找到了这么几行:
snippet enum "Enumerate" b
begin{enumerate}
item $0
end{enumerate}
endsnippet
如果定义了这个, 在tex
文本中, 输入 enum
, 在自动补全插件的作用下, 会出现
![f1cc8fae1c8c000fedf2acbcc20d1b74.png](https://img-blog.csdnimg.cn/img_convert/f1cc8fae1c8c000fedf2acbcc20d1b74.png)
那么, 这时按一下<tab>
键, 就可以得到
![c4140262dafe1ef9527d22025bfc21cd.png](https://img-blog.csdnimg.cn/img_convert/c4140262dafe1ef9527d22025bfc21cd.png)
神不神奇? 意不意外?
在这里, 我们解释一下snippet的定义. 下面是一个基本结构:
snippet Tab_trigger "Description" b
endsnippet
是一个基本格式.
Tab_trigger 就是你要输入的东西, 比如上面例子里的enum
.
在引号里, 也就是这里的Description部分, 你可以写一下这个片段代码的功能, 比如在enum
的例子里, 它会显示, 说这是Enumerate
.
后面一个东西 b
, 是这个片段代码的选项. 这里, 共有下面几个选项:
- b :只有当 trigger在行首才有效
- i :默认情况下, trigger是自成一个单词才有用. 加了这个选项, 即使是在单词中, 只要出现了这几个字母, 就可以使用. 比如 aaatrigger, 也可以使用.
- w:与
i
相反, 只有是一个单词(前面是空格) 才可以使用. - r:支持正则表达式t在这里,的其他功能失效, 就当成空格使用.
- A:trigger不需要按tab就可以自动展开.
2.2. lp 代码块
下面看接下来的一个代码块:
snippet lp "Long parenthesis"
left(${1:${VISUAL:contents}}right)$0
endsnippet
这里没有任何选项, 那就是默认. 这里我们看到有两个东西: $1
和 $0
.
$1指的是片段展开之后, 光标所在的位置, 那么如果有$2
, 按<tab>
, 就可以跳到下一个. $0就是最后都跳完了, 光标所在的位置. 这样可以很快地跳出一些环境.
${VISUAL:contents}: 是指, 光标在这里时, content是显示出来的. 如图所示:
![a93d21cc7dd493119c79cf0fbe44b844.png](https://img-blog.csdnimg.cn/img_convert/a93d21cc7dd493119c79cf0fbe44b844.png)
2.3. begin end 代码块
看看下面这个:
snippet "b(egin)?" "begin{} / end{}" br
begin{${1:something}}
${0:${VISUAL}}
end{$1}
endsnippet
我们看到, 选项部分有br
, 是说1. 只有在行首才有用; 2. 使用了正则表达. 那么我们看到, 这里, triggle 部分加了单/双引号, 这也正是正则表达的规则: 要加单/双引号.
b(egin)?
什么意思呢? 就是说, 或者只输入了b
, 或者输入begin
, 都能用这个代码块展开. 在自动补全插件的帮助下, 如图:
![eac63a77f3f293b85ac9db53387c8172.png](https://img-blog.csdnimg.cn/img_convert/eac63a77f3f293b85ac9db53387c8172.png)
![2bcccc9ba4ea6f82caaca74f07e60c03.png](https://img-blog.csdnimg.cn/img_convert/2bcccc9ba4ea6f82caaca74f07e60c03.png)
这里, 光标落在$1
处, 并默认显示了something
.
我们注意到, 在代码区, 有两个$1
, 那么就是说, 只要我们在begin
部分输入了somethingelse
, 那么在end
区, 也自动有somethingelse
. 那么继续<tab>
, 就可以跳到中间区域, 开心写环境里的东西了.
![11c57df6eb9b9ad7a6895f23d8d627b6.png](https://img-blog.csdnimg.cn/img_convert/11c57df6eb9b9ad7a6895f23d8d627b6.png)
不过, 我建议可以改成这样:
snippet "b(egin)?" "begin{} / end{}" br
begin{${1:something}}
${2:${VISUAL}Write your words here}
end{$1}$0
这样一来, 继续tab, 就可以跳出区域了. 这时, 也可以配合latex-suite
插件使用, 在$0
处换成<++>
, 这样, 用<Ctrl-J>
就可以跳出环境了.
2.4. X_1 代码块
snippet '([A-Za-z])([d])' "auto subscript" wrA
`!p snip.rv = match.group(1)`_`!p snip.rv = match.group(2)`
endsnippet
这里, 我们看到, 选项部分是wrA
. A, 代表自动展开. 同样地, 我们用到了正则表达式.
在代码区, 我们还看到了有snip.rv
. 这时用了python
的情况. !p snip.rv = 这后面就是要输出的部分
.
在trigger里, 我们看到两个括号, 一个是([a-z])
, 另一个是([d])
, 这是正则表达式, a-zA-Z
代表所有大小写字母, d
代表数字. 按照括号的顺序, 可以分出两个部分, 第一个就叫match.group(1)
, 第二个就叫match.group(2)
. 从code里看出, 这两个代码块用_
下划线连接了.
那么好, 如果我们用了这个代码块, 输入x1
, 就会自动输出x_1
.
如果在数学公式环境里, 这么自动展开还可以, 在非数学模式, 这么做显然有很多不便. 那么可以规定数学环境吗? 答案是可以.
2.5. math 环境
我们定义math 环境如下, 并把这段代码放在snippets文档的开头部分.
global !p
texMathZones = ['texMathZone'+x for x in ['A', 'AS', 'B', 'BS', 'C',
'CS', 'D', 'DS', 'E', 'ES', 'F', 'FS', 'G', 'GS', 'H', 'HS', 'I', 'IS',
'J', 'JS', 'K', 'KS', 'L', 'LS', 'DS', 'V', 'W', 'X', 'Y', 'Z']]
texIgnoreMathZones = ['texMathText']
texMathZoneIds = vim.eval('map('+str(texMathZones)+", 'hlID(v:val)')")
texIgnoreMathZoneIds = vim.eval('map('+str(texIgnoreMathZones)+", 'hlID(v:val)')")
ignore = texIgnoreMathZoneIds[0]
def math():
synstackids = vim.eval("synstack(line('.'), col('.') - (col('.')>=2 ? 1 : 0))")
try:
first = next(
i for i in reversed(synstackids)
if i in texIgnoreMathZoneIds or i in texMathZoneIds
)
return first != ignore
except StopIteration:
return False
endglobal
定义好了以后, 在上面的代码前加上一行, 变成:
context "math()"
snippet '([A-Za-z])([d])' "auto subscript" wrA
`!p snip.rv = match.group(1)`_`!p snip.rv = match.group(2)`
endsnippet
这样一来, 刚才那个片段代码, 就只有在数学公式的模式下才有用了.
在哪新添加snippets命令?
那位说了, 你讲了这么多, 感觉有点用. 那么实际用的时候, 在哪里添加自定义的命令呢?
- 你可以在刚才说的
tex.snippets
文档里添加. 但是, 这样做是有风险的, 改动了自带的文档, 当那个插件更新时, 你的自定义命令可以就被覆盖了. (别问我是怎么知道的, 谢谢. ) - 可以建一个文档:
~/.vim/UltiSnips/tex.snippets
. 你的新定义的代码块放在这里, 就可以放心用了. - 推荐 通过
:UltiSnipsEdit
命令. 这是插件自带的一个命令, 运行它, 便可以出现这样的画面:
![8af5438317f4837ab2bf62de5bcfdfa6.png](https://img-blog.csdnimg.cn/img_convert/8af5438317f4837ab2bf62de5bcfdfa6.png)
在另一个标签页, 出现你的本地snippets
文件, 在这里编辑, 保存之后, 就可以回到右面的窗口开心继续texing了.
本文是我自己使用snippets
插件编辑latex
的一些经验和心得, 部分参考了这篇文章:
![c9b591741bf9a5fbe4d0de383224b3eb.png](https://img-blog.csdnimg.cn/img_convert/c9b591741bf9a5fbe4d0de383224b3eb.png)
欢迎各位批评指正, 谢谢!
下面是我写的其他关于用vim写latex的文章:
李老师的好学生:Vim Latex 的使用和配置技巧 (一)zhuanlan.zhihu.com