前段时间,我在Quora回答了一个问题:
如何跟上LaTeX上的数学讲义 。 在那里,我使用Vim和Inkscape(用于图纸)在LaTeX中解释了我的笔记工作流程。 但是自那时以来,发生了很多变化,因此我想发布一些博客文章,并描述新过程。 这是文章的第一篇。
我在数学课程的第二学期开始使用LaTeX做笔记,从那时起我写了1700多页。 以下是摘要显示的一些示例:



这些笔记(包括图纸)直接在讲座上制作,以后不再编辑。 为了在LaTeX中有效地编写摘要,必须遵循四个规则:
- 在LaTeX中编写文本和公式的速度应与讲师在黑板上书写的速度一样快:延迟是不可接受的。
- 绘图插图应几乎与讲师的插图一样快。
- 管理便笺,即添加便笺,安排所有便笺,最后两节讲座,搜索便笺等,应该快速简便。
- 如果我想用pdf文档写注释,应该可以使用LaTeX注释pdf文档。
本文是关于第一点的:在LaTeX上做笔记。
Vim和LaTeX
要在LaTeX上编写文本和数学公式,我使用Vim。 这是一个功能强大的通用文本编辑器,具有高度可扩展性。 我用它来编写代码,LaTeX,Markdown文本……一般来说,任何文本。 他的学习曲线相当陡峭,但是如果您掌握了基础知识,那么如果没有通常的热键就很难返回编辑器。 这是我编辑LaTeX文档时的屏幕外观:

左边是Vim,右边是
Zathura PDF查看器,它也支持Vim样式的键盘快捷键。 我在Ubuntu中使用
bspwm窗口管理器。 LaTeX作为插件安装了
vimtex 。 它提供语法高亮显示,目录,synctex等。使用
vim-plug,我将其配置如下:
Plug 'lervag/vimtex'
let g:tex_flavor='latex'
let g:vimtex_view_method='zathura'
let g:vimtex_quickfix_mode=0
set conceallevel=1
let g:tex_conceal='abdmg'
最后两行调整伪装。 此功能是当光标不在此行上时,LaTeX代码将被替换或变得不可见的功能。 如果您隐藏
\ [
,
\]
,
$
,那么它们并不那么显眼,可以更好地浏览文档。 该函数还用
\bigcap
替换
\bigcap
,用
∈
替换
\bigcap
\in
,等等,如动画所示:

使用此设置,您可以完成任务:在LaTeX上书写的速度与讲师在黑板上书写的速度相同。 片段在这里起作用。
片段
什么是摘要?
摘录是一小段可重用的文本,被其他文本调用。 例如,当键入sign并按Tab时,sign单词变成签名:

代码段可以是动态的:当我
today
输入并按
Tab
,
today
一词将替换为当前日期,并且
box
Tab
变为自动增加大小的字段。


您甚至可以在另一个代码段中使用一个代码段:

使用UltiSnips创建片段
为了控制代码片段,我使用
UltiSnips插件。 这是它的配置:
Plug 'sirver/ultisnips' let g:UltiSnipsExpandTrigger = '<tab>' let g:UltiSnipsJumpForwardTrigger = '<tab>' let g:UltiSnipsJumpBackwardTrigger = '<s-tab>'
代码段
sign
的代码:
snippet sign "Signature" Yours sincerely, Gilles Castel endsnippet
对于动态代码段,您可以将代码放在反引号之间,当代码段扩展时,将运行该代码。 在这里,我使用bash格式化当前日期:
date + %F
snippet today "Date" `date +%F` endsnippet
在
`!p ... `
块中,您可以用Python编写。 查看
box
摘要的代码:
snippet box "Box" `!p snip.rv = '┌' + '─' * (len(t[1]) + 2) + '┐'` │ $1 │ `!p snip.rv = '└' + '─' * (len(t[1]) + 2) + '┘'` $0 endsnippet
代替此代码,
snip.rv
变量的值将插入到文档中。 在块内部,您可以访问代码段的当前状态,例如,
t[1]
对应于第一个选项卡的位置,
fn
当前文件名,等等。
乳胶片段
片段显着加快了工作速度,尤其是一些较复杂的片段。 让我们从最简单的开始。
环境
要插入环境,只需在行首输入
beg
。 然后是环境名称,该名称反映在
\end{}
命令中。 按
Tab
将光标放在其中。

代码如下:
snippet beg "begin{} / end{}" bA \begin{$1} $0 \end{$1} endsnippet
b
符号表示这样的代码段仅在行的开头起作用,
A
表示自动扩展,即不需要按
Tab
。 通过按
Tab
和
Shift
+
Tab
转到的
Tab
Tab
表示为
$1
,
$2
,...,后者表示为
$0
。
内联和显示公式
两个最常用的代码段是
mk
和
dm
,它们会触发数学模式。 第一个用于内联公式,第二个用于显示公式。

聪明的公式摘要:他知道何时在美元符号后插入空格。 当我在$结束后立即开始输入单词时,它会添加一个空格。 但是,如果我键入另一个字符,则不会添加空格,例如'$ p $ -value'。

此代码段的代码为:
snippet mk "Math" wA $${1}$`!p if t[2] and t[2][0] not in [',', '.', '?', '-', ' ']: snip.rv = ' ' else: snip.rv = '' `$2 endsnippet
第一行末尾
W
表示该代码段仅在单词边界处扩展。 因此,例如,
hellomk
将不起作用,而
hello mk
将起作用。
显示公式的代码段比较简单,但也很方便。 它总是使方程式以点结尾。

<snippet dm "Math" wA \[ $1 .\] $0 endsnippet
下标和上标字符
另一个有用的代码段是用于索引。 它将
a1
更改为
a_1
,将
a_12
为
a_{12}
。

此代码段的代码使用正则表达式作为触发器。 当您输入一个字符后跟一个编码为
[A-Za-z]\d
的字符,或一个字符后接
_
和两个数字时,它将扩展该片段。
[A-Za-z]_\d\d
。
snippet '([A-Za-z])(\d)' "auto subscript" wrA `!p snip.rv = match.group(1)`_`!p snip.rv = match.group(2)` endsnippet snippet '([A-Za-z])_(\d\d)' "auto subscript2" wrA `!p snip.rv = match.group(1)`_{`!p snip.rv = match.group(2)`} endsnippet
当您使用括号将正则表达式的各个部分组合成一个组时,例如
(\d\d)
,则可以通过Python中的
match.group(i)
在代码段扩展中使用它们。
对于上标字符,我使用
td
,它变成
^{}
。 尽管对于最常见的(正方形,立方体和其他几个),打算使用单独的代码段,例如
sr
,
cb
和
comp
。

snippet sr "^2" iA ^2 endsnippet snippet cb "^3" iA ^3 endsnippet snippet compl "complement" iA ^{c} endsnippet snippet td "superscript" iA ^{$1}$0 endsnippet
分数
最便捷的片段之一适用于分数。 他进行了以下替换:
//
→
\frac{}{}
3/
→
\frac{3}{}
4\pi^2/
→
\frac{4\pi^2}{}
(1 + 2 + 3)/
→
\frac{1 + 2 + 3}{}
(1+(2+3)/)
→
(1 + \frac{2+3}{})
(1 + (2+3))/
→
\frac{1 + (2+3)}{}

首先,一个简单的代码:
snippet // "Fraction" iA \\frac{$1}{$2}$0 endsnippet
在与表达式
3/
,
4ac/
,
6\pi^2/
,
a_2/
等对应的正则表达式的帮助下,进行第二和第三次替换。
snippet '((\d+)|(\d*)(\\)?([A-Za-z]+)((\^|_)(\{\d+\}|\d))*)/' "Fraction" wrA \\frac{`!p snip.rv = match.group(1)`}{$1}$0 endsnippet
如您所见,正则表达式可能会变得很长,但是下面的图应该解释了所有内容:

在第四和第五种情况下,代码段尝试查找相应的括号。 由于UltiSnips正则表达式引擎不知道如何执行此操作,因此我不得不使用Python:
priority 1000 snippet '^.*\)/' "() Fraction" wrA `!p stripped = match.string[:-1] depth = 0 i = len(stripped) - 1 while True: if stripped[i] == ')': depth += 1 if stripped[i] == '(': depth -= 1 if depth == 0: break; i -= 1 snip.rv = stripped[0:i] + "\\frac{" + stripped[i+1:-1] + "}" `{$1}$0 endsnippet
最后,我想分享一个片段,将当前选择片段化。 选择文本,按
Tab
,输入
/
然后再次按
Tab
Tab
。

该代码使用
${VISUAL}
变量,它反映了您的选择。
snippet / "Fraction" iA \\frac{${VISUAL}}{$1}$0 endsnippet
Sympy和Mathematica
另一个很酷但使用较少的代码段运行
sympy来评估数学表达式。 例如:
sympy
Tab
扩展为
sympy | sympy
sympy | sympy
和
sympy 1 + 1 sympy
Tab
变为
2
。

snippet sympy "sympy block " w sympy $1 sympy$0 endsnippet priority 10000 snippet 'sympy(.*)sympy' "evaluate sympy" wr `!p from sympy import * x, y, z, t = symbols('xyz t') k, m, n = symbols('km n', integer=True) f, g, h = symbols('fg h', cls=Function) init_printing() snip.rv = eval('latex(' + match.group(1).replace('\\', '') \ .replace('^', '**') \ .replace('{', '(') \ .replace('}', ')') + ')') ` endsnippet
对于Mathematica,类似的事情也是可能的:

priority 1000 snippet math "mathematica block" w math $1 math$0 endsnippet priority 10000 snippet 'math(.*)math' "evaluate mathematica" wr `!p import subprocess code = 'ToString[' + match.group(1) + ', TeXForm]' snip.rv = subprocess.check_output(['wolframscript', '-code', code]) ` endsnippet
Postfix片段
在我看来,值得一提的是后缀代码段,这些后缀代码段在输入某些字符后插入了适当的文本。 例如,
phat
→
\hat{p}
和
zbar
→
\overline{z}
。 类似的代码段会插入一个向量,例如
v,.
→
\vec{v}
和
v.,
→
\vec{v}
。 句号和分号的顺序无关紧要,因此我可以同时单击它们。 这些摘要确实可以节省时间,因为您以与讲师在黑板上相同的速度输入它们。

请注意,
bar
和
hat
前缀仍然可以使用,但是优先级较低。 这些代码段的代码是:
priority 10 snippet "bar" "bar" riA \overline{$1}$0 endsnippet priority 100 snippet "([a-zA-Z])bar" "bar" riA \overline{`!p snip.rv=match.group(1)`} endsnippet
priority 10 snippet "hat" "hat" riA \hat{$1}$0 endsnippet priority 100 snippet "([a-zA-Z])hat" "hat" riA \hat{`!p snip.rv=match.group(1)`} endsnippet
snippet "(\\?\w+)(,\.|\.,)" "Vector postfix" riA \vec{`!p snip.rv=match.group(1)`} endsnippet
其他片段
我仍然有大约一百个常用片段。 所有这些都可以
在这里找到 。 其中大多数都很简单。 例如,
!>
变成
\mapsto
,
->
变成
\to
,等等。

fun
转换为
f: \R \to \R :
,
!>
→
\mapsto
,
cc
→
\subset
。

lim
变为
\lim_{n \to \infty}
,
sum
→
\sum_{n = 1}^{\infty}
,
ooo
→
\infty
。


课程特定片段
除了常用的片段外,我还提供了一些特定的片段。 它们在
.vimrc
作为单行加载:
set rtp+=~/current_course
这里
current_course
是到当前课程的
符号链接 (另一篇文章中有更多关于此课程的信息)。 这个文件夹中的文件是
~/current_course/UltiSnips/tex.snippets
,在其中添加课程摘要。 例如,对于量子力学,有一些片段可以记录壁式和酮式量子态。
<a|
→
\bra{a}
<q|
→
\bra{\psi}
|a>
→
\ket{a}
|q>
→
\ket{\psi}
\braket{a}{b}
→
\braket{a}{b}
由于量子力学经常使用
\psi
,因此我自动用
\psi
替换了brake中的所有
q
。

snippet "\<(.*?)\|" "bra" riA \bra{`!p snip.rv = match.group(1).replace('q', f'\psi').replace('f', f'\phi')`} endsnippet snippet "\|(.*?)\>" "ket" riA \ket{`!p snip.rv = match.group(1).replace('q', f'\psi').replace('f', f'\phi')`} endsnippet snippet "(.*)\\bra{(.*?)}([^\|]*?)\>" "braket" riA `!p snip.rv = match.group(1)`\braket{`!p snip.rv = match.group(2)`}{`!p snip.rv = match.group(3).replace('q', f'\psi').replace('f', f'\phi')`} endsnippet
语境
编写这些摘要时,应考虑是否可以在纯文本中找到它们。 例如,根据我的词典,带sr的英语大约72个单词,荷兰语的大约2000个单词。 因此,当我键入
disregard
,
sr
更改为
^2
,并且得到
di^2egard
。
解决此问题的方法是向片段添加上下文。 Vim的语法高亮显示确定UltiSnips是否应使用代码段,具体取决于您是处于公式模式还是文本模式。 我想出了这个选项:
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()"
添加到只想在数学上下文中应用的那些片段。
context "math()" snippet sr "^2" iA ^2 endsnippet
请注意,数学上下文是微妙的事情。 有时在公式模式下,我们也使用
\text{...}
编写文本。 在这种情况下,我们不想使用片段。 但是,在以下情况下:
\[ \text{$...$} \]
,
必须应用它们。 这就是为什么
math
上下文的代码不是那么简单。 以下动画说明了这些细微之处。

即时进行拼写校正
尽管公式是摘要的重要组成部分,但我大部分时间都是用英语打印的。 每分钟大约80个单词,我的打字技巧非常好,但我经常打错字。 这就是为什么我在Vim上添加了一个绑定,该绑定可以纠正拼写错误而不干扰工作。 在输入过程中
Ctrl+L
时,先前的拼写错误已得到纠正。 看起来像这样:

我的拼写检查设置:
setlocal spell set spelllang=nl,en_gb inoremap <Cl> <cg>u<Esc>[s1z=`]a<cg>u
在这里,转到上一个拼写错误
[s
,然后选择第一个选项
1z=
并返回
`]a
。 中间的
<cg>u
命令可让您快速撤消更正。
总结
感谢Vim片段,编写LaTeX代码不再烦人,而是一种乐趣。 结合动态拼写,这使您可以方便快捷地概述数学讲座。
在下一篇文章中,我将讨论其他主题,例如以数字方式绘制插图并将其嵌入LaTeX文档中。