虽然只剩一个月就2021年了,但仍然有很多人使用着非常原始的工具书写LaTeX(包括一个星期前的我在内),那么我就不废话直奔主题,请使用现代化的工具——VS Code。

起源

就在前不久,由于意外获得了一块1T的固态硬盘,这使得我有足够的空间在笔记本上装了个Ubuntu双系统,然后在折腾各种工具之余,我也开始测试在Ubuntu上使用什么编辑器写起LaTeX来最舒服。

最先安装的自然是TeXstudio,这家伙比在win10上看起来舒服了不少,随后我顺便测试了一下VS Code,然后我流泪了,我深刻地理解了什么叫做 a modern approach.

配置

如何安装LaTeX就不废话了,但要注意两点:

  1. 下载 TeXlive 而不是 CTeX
  2. 下载 texlive-full

如何在VS Code中配置 LaTeX呢?

分为以下几部分:

安装 LaTeX Workshop

在插件商店中搜索 LaTeX Workshop,安装即可。

打开插件设置

这里有两种方法,一个是在 设置-拓展-LaTeX 中,瞪大眼睛一个个查看选项并勾选。

我们该如何优雅地使用LaTeX in 2020 - 图1

另一种是在 settings.json 中设置,只需 Ctrl+Shift+P 打开命令面板,然后输入 Open Settings(json),如果你的VScode不巧抽风了,Ctrl+P+输入> ,之后再输入 Open Settings(json)也一样。

我们该如何优雅地使用LaTeX in 2020 - 图2

复制配置代码

  1. "latex-workshop.latex.recipes": [
  2. {
  3. "name": "XeLaTeX",
  4. "tools": [
  5. "xelatex"
  6. ]
  7. },
  8. {
  9. "name": "PDFLaTeX",
  10. "tools": [
  11. "pdflatex"
  12. ]
  13. },
  14. {
  15. "name": "xelatex -> bibtex -> xelatex*2",
  16. "tools": [
  17. "xelatex",
  18. "bibtex",
  19. "xelatex",
  20. "xelatex"
  21. ]
  22. },
  23. // {
  24. // "name": "latexmk 🔃",
  25. // "tools": [
  26. // "latexmk"
  27. // ]
  28. // },
  29. // {
  30. // "name": "latexmk (latexmkrc)",
  31. // "tools": [
  32. // "latexmk_rconly"
  33. // ]
  34. // },
  35. // {
  36. // "name": "latexmk (lualatex)",
  37. // "tools": [
  38. // "lualatexmk"
  39. // ]
  40. // },
  41. {
  42. "name": "pdflatex ➞ bibtex ➞ pdflatex * 2",
  43. "tools": [
  44. "pdflatex",
  45. "bibtex",
  46. "pdflatex",
  47. "pdflatex"
  48. ]
  49. }
  50. // {
  51. // "name": "Compile Rnw files",
  52. // "tools": [
  53. // "rnw2tex",
  54. // "latexmk"
  55. // ]
  56. // },
  57. // {
  58. // "name": "Compile Jnw files",
  59. // "tools": [
  60. // "jnw2tex",
  61. // "latexmk"
  62. // ]
  63. // },
  64. // {
  65. // "name": "tectonic",
  66. // "tools": [
  67. // "tectonic"
  68. // ]
  69. // }
  70. ],
  71. "latex-workshop.latex.tools": [
  72. {
  73. "name": "xelatex",
  74. "command": "xelatex",
  75. "args": [
  76. "-synctex=1",
  77. "-interaction=nonstopmode",
  78. "-file-line-error",
  79. "-shell-escape",
  80. "%DOCFILE%"
  81. ],
  82. "env": {}
  83. },
  84. {
  85. "name": "latexmk",
  86. "command": "latexmk",
  87. "args": [
  88. "-synctex=1",
  89. "-interaction=nonstopmode",
  90. "-file-line-error",
  91. "-pdf",
  92. "-outdir=%OUTDIR%",
  93. "%DOC%"
  94. ],
  95. "env": {}
  96. },
  97. {
  98. "name": "lualatexmk",
  99. "command": "latexmk",
  100. "args": [
  101. "-synctex=1",
  102. "-interaction=nonstopmode",
  103. "-file-line-error",
  104. "-lualatex",
  105. "-outdir=%OUTDIR%",
  106. "%DOC%"
  107. ],
  108. "env": {}
  109. },
  110. {
  111. "name": "latexmk_rconly",
  112. "command": "latexmk",
  113. "args": [
  114. "%DOC%"
  115. ],
  116. "env": {}
  117. },
  118. {
  119. "name": "pdflatex",
  120. "command": "pdflatex",
  121. "args": [
  122. "-synctex=1",
  123. "-interaction=nonstopmode",
  124. "-file-line-error",
  125. "%DOC%"
  126. ],
  127. "env": {}
  128. },
  129. {
  130. "name": "bibtex",
  131. "command": "bibtex",
  132. "args": [
  133. "%DOCFILE%"
  134. ],
  135. "env": {}
  136. },
  137. {
  138. "name": "rnw2tex",
  139. "command": "Rscript",
  140. "args": [
  141. "-e",
  142. "knitr::opts_knit$set(concordance = TRUE); knitr::knit('%DOCFILE_EXT%')"
  143. ],
  144. "env": {}
  145. },
  146. {
  147. "name": "jnw2tex",
  148. "command": "julia",
  149. "args": [
  150. "-e",
  151. "using Weave; weave(\"%DOC_EXT%\", doctype=\"tex\")"
  152. ],
  153. "env": {}
  154. },
  155. {
  156. "name": "jnw2texmintex",
  157. "command": "julia",
  158. "args": [
  159. "-e",
  160. "using Weave; weave(\"%DOC_EXT%\", doctype=\"texminted\")"
  161. ],
  162. "env": {}
  163. },
  164. {
  165. "name": "tectonic",
  166. "command": "tectonic",
  167. "args": [
  168. "--synctex",
  169. "--keep-logs",
  170. "%DOC%.tex"
  171. ],
  172. "env": {}
  173. }
  174. ],

这是对应你的编译流程,一般来说只需通过第一个 XeLaTeX 就够了,但如果你插入了参考文献,就得使用第三个流程 xelatex -> bibtex -> xelatex*2,这对于老手来说应该不陌生了。

我们该如何优雅地使用LaTeX in 2020 - 图3

  1. "latex-workshop.message.error.show": false,
  2. "latex-workshop.message.warning.show": false,
  3. "latex-workshop.intellisense.package.enabled": true,
  4. "latex-workshop.latex.autoClean.run": "onFailed",
  5. "latex-workshop.latex.recipe.default": "lastUsed",
  6. "latex-workshop.showContextMenu": true,

前两个是关闭错误和警告消息提示,不然会一直在右下角弹窗,非常烦人,一般都是自己从状态栏看报错信息。

我们该如何优雅地使用LaTeX in 2020 - 图4

双向同步

接下来是稍微复杂一点的配置——双向同步,也就是你能从当前代码跳转到PDF对应行,也能从PDF所在行跳转到对应代码处。

  1. "latex-workshop.view.pdf.viewer": "external",
  2. "latex-workshop.view.pdf.external.synctex.command": "C:\\Program Files\\SumatraPDF\\SumatraPDF.exe",
  3. "latex-workshop.view.pdf.external.viewer.command": "C:\\Program Files\\SumatraPDF\\SumatraPDF.exe",
  4. "latex-workshop.view.pdf.invert": 1,
  5. "latex-workshop.view.pdf.external.synctex.args": [
  6. "-forward-search",
  7. "%TEX%",
  8. "%LINE%",
  9. "-reuse-instance",
  10. "-inverse-search",
  11. "\"C:\\Users\\Theigrams\\AppData\\Local\\Programs\\Microsoft VS Code\\code.exe\" \"C:\\Users\\Theigrams\\AppData\\Local\\Programs\\Microsoft VS Code\\resources\\app\\out\\cli.js\" -r -g \"%f:%l\"",
  12. "%PDF%"
  13. ],

第一行是设置使用外置PDF浏览器,VSCode内置的会比较卡。

第二三行是 SumatraPDF.exe 所在的路径。

第5-13行自己对照着修改VScode和SumatraPDF的路径即可。

然后记得打开SumatraPDF-设置-高级选项,找到 InverseSearchCmdLine 那一行修改,记得换成自己的路径。

  1. InverseSearchCmdLine = "C:\Users\Theigrams\AppData\Local\Programs\Microsoft VS Code\code.exe" "C:\Users\Theigrams\AppData\Local\Programs\Microsoft VS Code\resources\app\out\cli.js" -r -g "%f:%l"

A modern approach

至此,你已经可以开始用VScode编写LaTeX文档了,但如果你看过这篇文章1700页数学笔记火了!全程敲代码,速度飞快易搜索,硬核小哥教你上手LaTeX+Vim - 知乎 (zhihu.com),大概你就能明白快速使用LaTeX的核心就是Snippets。

使用Snippets

请点进 Snippets · James-Yu/LaTeX-Workshop Wiki (github.com),详细观看里面有哪些Snippets,

核心Snippets有如下:

Prefix Environment name
BEQ equation
BAL align
BIT itemize
BEN enumerate
Prefix Sectioning level
SCH chapter
SSE section
SSS subsection
SS2 subsubsection
Prefix Shortcut Command
MRM ctrl+m, ctrl+r \mathrm{${1}}
MBF ctrl+m, ctrl+s \mathsf{${1}}
MBF ctrl+m, ctrl+b \mathbf{${1}}
MBB ctrl+m, ctrl+shift+b \mathbb{${1}}
MCA ctrl+m, ctrl+c \mathcal{${1}}
MIT ctrl+m, ctrl+i \mathit{${1}}
MTT ctrl+m, ctrl+t \mathtt{${1}}

注意使用snippet时,如果有多个空,在填写完第一个之后按Tab 就能自动跳到第二个空。

我们该如何优雅地使用LaTeX in 2020 - 图5

自制Snippets

workshop自带的这些Snippets是远远不够的,而且众口难调,不如自己配置Snippets来得爽快。

打开设置-用户代码片段-latex.json,然后你就可以模仿样例输入完成自己的Snippets.

例如,我们可以自制一个插入图片的Snippet:

  1. "Input a figure": {
  2. "prefix": "fig",
  3. "body": [
  4. "\\begin{figure}[htp]",
  5. "\t\\centering",
  6. "\t\\includegraphics[width=0.7linewidth]{fig/$1}",
  7. "\t\\caption{$2}",
  8. "\t\\label{fig:$3}",
  9. "\\end{figure}",
  10. "$0"
  11. ],
  12. "description": "Input a figure"
  13. },

直接输入 fig 就能看到Snippet提示:

我们该如何优雅地使用LaTeX in 2020 - 图6

注意生成的代码中有三个细微的竖线,也就是上面代码中 $1,$2,$3 的地方,我们可以填完内容后用 Tab 跳到下一个。

我们该如何优雅地使用LaTeX in 2020 - 图7

我们可以继续制作一个三线表的snippet:

  1. "three horizontal lines": {
  2. "prefix": "3linetable",
  3. "body": [
  4. "\\begin{table}[htbp]",
  5. "\t\\centering\n\t\\caption{$1}",
  6. "\t\\label{tab:$2}",
  7. "\t\\begin{tabular}{ccc}",
  8. "\t\t\\toprule",
  9. "\t\t姓名 & 学号 & 性别 \\\\\\\\ \\midrule",
  10. "\t\tSteve Jobs & 001 & Male \\\\\\\\",
  11. "\t\tBill Gates & 002 & Female \\\\\\\\ \\bottomrule",
  12. "\t\\end{tabular}",
  13. "\\end{table}\n"
  14. ],
  15. }

注意,由于 \ 的转移符非常特殊,要生成换行符号 \\,我们得使用 \\\\\\\\ 来完成。

插入图片

虽然我们在上面设置了插入图片的snippet,但还是得自己把图片放到文件夹下,一点也不智能。

所以我们得使用modern一点的方法:

首先进入拓展商店,安装插件 Paste Image,它的作用就是自动帮你把剪切板中的图片放到设定目录中,并生成特定代码。

然后是配置,还是打开 settings.json,将以下代码复制进去:

  1. "pasteImage.defaultName": "f-H-m-s",
  2. "pasteImage.path": "${currentFileDir}/fig",
  3. "pasteImage.filePathConfirmInputBoxMode": "onlyName",
  4. "pasteImage.showFilePathConfirmInputBox": true,
  5. "pasteImage.insertPattern": "\\begin{figure}[htp]\n\t\\centering\n\t\\includegraphics[width=0.7\\linewidth]{fig/${imageFileName}}\n\t\\caption{${imageFileNameWithoutExt}}\n\t\\label{fig:${imageFileNameWithoutExt}}\n\\end{figure}\n",

第2行是设置图片保存路径,我设置的是存在当前目录下的 fig 文件夹中,

第4行是指保存之前弹出一个框,让你修改文件名

第5行是插入后生成代码

流程大致如下:

  1. 截图,或将图片复制到剪切板
  2. Ctrl+Alt+V (如果没反应,大概率是快捷键冲突了,可以自行把其他屏蔽)
  3. 在弹出的box中修改名称
    我们该如何优雅地使用LaTeX in 2020 - 图8
  4. 自动插入代码
    我们该如何优雅地使用LaTeX in 2020 - 图9

设置快捷键

对于一些非常高频的操作,snippet也显得不够便捷,这时我们就需要自定义快捷键。

例如对于行内数学公式环境,一般使用 Ctrl+M 就能自动生成 $ $ ,并且光标还在中间。

这时我们打开 设置-键盘快捷方式,打开keybindings.json

我们该如何优雅地使用LaTeX in 2020 - 图10

复制如下代码即可:

  1. {
  2. "key": "ctrl+m",
  3. "command": "editor.action.insertSnippet",
  4. "when": "editorTextFocus",
  5. "args": {
  6. "snippet": "$ $1 $ $0"
  7. }
  8. },

第6行中,第1,3个 $ 是用于产生数学公式环境,$1 是指待会儿光标移到此处,待输入文件,$0 是指输完后按 Tap 可以调到公式环境外面去。

但是,有的时候我们还有这种需求:选中一段文本,按 Ctrl+M 希望能在其两端插入 $ 符号,这时只需稍微修改一点即可满足需求:

  1. {
  2. "key": "ctrl+m",
  3. "command": "editor.action.insertSnippet",
  4. "when": "editorTextFocus",
  5. "args": {
  6. "snippet": "$ ${TM_SELECTED_TEXT}$1 $ $0"
  7. }
  8. },

自用模板

最后贴一个自用模板,定理样式来源于 HaoyunQin 的 LaTeX Snippets

我们该如何优雅地使用LaTeX in 2020 - 图11

我们该如何优雅地使用LaTeX in 2020 - 图12

\documentclass[a4paper]{article} \usepackage[UTF8]{ctex} %中文 \usepackage{amsmath,amsfonts,amssymb,amsthm,mathrsfs,bm} \usepackage{setspace} %行间距 \usepackage{geometry} %页边距 \geometry{left=3.18cm,right=3.18cm,top=2.54cm,bottom=2.54cm} %\usepackage{pifont} %带圈数字 \ding{172} \usepackage{graphicx} %插图 \includegraphics[width=1cm]{picture.jpg} \usepackage{subfigure} \usepackage{mdframed} %\usepackage{wrapfig} \usepackage[ colorlinks=true, citecolor=blue, linkcolor=red, anchorcolor=green, urlcolor=blue, bookmarksopen=true,bookmarksnumbered=true]{hyperref} \usepackage{enumitem} \usepackage[square,numbers,sort&compress]{natbib} \usepackage{tcolorbox} \tcbuselibrary{most} \newenvironment{myitemize}{\begin{itemize}}{\end{itemize}} \tcolorboxenvironment{myitemize} {blanker, before skip = 6pt, after skip = 6pt, borderline west = {3mm}{0pt}{red}} \usepackage{array} \usepackage{float} \usepackage{booktabs} %\toprule \midrule \bottomrule \usepackage{multicol} \usepackage{minted} \usepackage{tabularx} %%%%%%%%%%%%%%%%%%%%%%%%%% Define some useful colors %%%%%%%%%%%%%%%%%%%%%%%%%% \definecolor{ocre}{RGB}{243,102,25} \definecolor{mygray}{RGB}{243,243,244} \definecolor{deepGreen}{RGB}{26,111,0} \definecolor{shallowGreen}{RGB}{235,255,255} \definecolor{deepBlue}{RGB}{61,124,222} \definecolor{shallowBlue}{RGB}{235,249,255} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newtheoremstyle{mytheoremstyle}{3pt}{3pt}{\normalfont}{0cm}{\rmfamily\bfseries}{}{\newline}{{\color{black}\thmname{#1}~\thmnumber{#2}}\thmnote{\,--\,#3}} \newtheoremstyle{myproblemstyle}{3pt}{3pt}{\normalfont}{0cm}{\rmfamily\bfseries}{}{\newline}{{\color{black}\thmname{#1}~\thmnumber{#2}}\thmnote{\,--\,#3}} \theoremstyle{mytheoremstyle} \newmdtheoremenv[linewidth=1pt,backgroundcolor=shallowGreen,linecolor=deepGreen,leftmargin=0pt,innerleftmargin=20pt, innerrightmargin=20pt,]{theorem}{Theorem}[section] \theoremstyle{mytheoremstyle} \newmdtheoremenv[linewidth=1pt,backgroundcolor=shallowBlue,linecolor=deepBlue,leftmargin=0pt,innerleftmargin=20pt, innerrightmargin=20pt,]{definition}{Definition}[section] \theoremstyle{myproblemstyle} \newmdtheoremenv[linecolor=black,leftmargin=0pt,innerleftmargin=10pt,innerrightmargin=10pt,]{problem}{Problem}[section] %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \usepackage{listings} \usepackage{fontspec} \usepackage{xcolor} \newcommand{\univname}{\href{https://www.buaa.edu.cn}{Beihang Universite}} \newcommand{\depname}{\href{http://math.buaa.edu.cn}{School of Mathematical Sciences}} \newcommand{\faculty}{\href{http://lmib.buaa.edu.cn}{MOE Key Laboratory of Mathematics Informatics and Behavioral Semantics(LMIB)}} \newcommand{\horrule}[1]{\rule{\linewidth}{#1}} \newcommand{\entit}{Geometric Deep Learning} \newcommand{\cntit}{基于图和流形上的深度学习} \renewcommand{\theFancyVerbLine}{\sffamily \textcolor[rgb]{0.5,0.5,1.0}{\scriptsize \oldstylenums{\arabic{FancyVerbLine}}}} \newminted{ruby}{mathescape,linenos,breaklines,breakanywhere,style=xcode,frame=single} \newminted{python}{texcl=true,mathescape,linenos,breaklines,breakanywhere,style=colorful,frame=single} \definecolor{darkblue}{rgb}{0.0,0.0,0.3} \newtcbox{\mybox}[1][red] {on line, arc = 3pt, colback = #1!10!white, colframe = #1!50!black, boxsep = 0pt, left = 1pt, right = 1pt, top =2.5pt, bottom = 1pt, boxrule = 0.8pt, bottomrule = 0.8pt, toprule = 0.8pt} \def\cdg#1{\mybox[green]{\mintinline{ruby}{#1}}} % text style \def\cdr#1{\mybox[red]{\mintinline{ruby}{#1}}} %\def\cdr#1{\mintinline{ruby}{#1}} %%%%%%%%%%%%%%%%%%%%%%%%%% main text %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \begin{document} \begin{titlepage} \begin{center} \horrule{0.5pt} \\[0.4cm] \vspace{-1.5ex}\textcolor{darkblue} { \zihao{-1} \bfseries \entit \\ \vspace{0.4cm}} \textcolor{darkblue}{\zihao{3} \bfseries \cntit} \\[0.1cm]\horrule{2pt} \vspace{-2ex} \end{center} \vspace{1cm} \begin{center} \textsc{\LARGE \univname\\ \large \depname \\ \unskip\strut} \vspace{-1.5ex} \\ \end{center} \vspace{3cm} \begin{table}[h] \zihao{4} \centering \begin{tabular}{p{3cm}<{\raggedleft} p{6cm}<{\centering}} 姓名: & {\fangsong 某某} \\ \specialrule{0em}{2pt}{2pt} 学号: & {\fangsong 123456} \\ \specialrule{0em}{2pt}{2pt} 导师: & {\fangsong 某某} \\ \specialrule{0em}{2pt}{2pt} 邮箱: & \url{theigrams@buaa.edu.cn} \\ \end{tabular} \end{table} \vspace{3cm} \begin{center} \zihao{4} \today \end{center} \end{titlepage} \newpage \begin{spacing}{1.5} \zihao{4} \thispagestyle{empty} \tableofcontents \setcounter{page}{1} \thispagestyle{empty} \end{spacing} \newpage \zihao{-4} \section{Basics of graph theory} \subsection{Graph Laplacian} \begin{definition}[非标准化图Laplace算子(Unnormalized Laplacian)] 定义了一个作用于 $ f $ 上的算子 $ \Delta :L^2(\mathcal{V})\rightarrow L^2(\mathcal{V}) $ ,用于刻画 $ f $ 与其局部加权平均值之间的差异. \[\begin{aligned} (\Delta f)_{i} & =\sum_{j:(i, j) \in \mathcal{E}} w_{i j}\left(f_{i}-f_{j}\right) \\ & =f_{i} \sum_{j:(i, j) \in \mathcal{E}}w_{i j}-\sum_{j:(i, j) \in \mathcal{E}}w_{i j} f_{j} \end{aligned} \] \end{definition} \begin{problem}[ $ \Delta $ 定义在 $ f $ 上 ] 注意:这里的$ \Delta $ 定义是在 $ f $ 上 ,但是由于 $ f_i $ 不是函数,因此不能写成 $ \Delta f_i $ 的形式,只能写成 $ (\Delta f)_i $. \end{problem} $ \Delta$算子 可以用一个半正定的 $ n\times n $ 矩阵 $\boldsymbol{\Delta } =\mathbf{D}-\mathbf{W} $ 代替 ,其中 $ \mathbf{W}=\left( w_{ij} \right) $ 是一个邻接矩阵(adjacency matrix), $ \mathbf{D}=\mathrm{diag}\left( \sum_{j\ne i}{w_{ij}} \right) $ 是对角阵(相当于把$ \mathbf{W}$的每一行加到对角元上). \begin{equation} \mathbf{Wf}=\left[ \begin{array}{c} \vdots \\ \sum_j{w_{ij}f_j} \\ \vdots \\ \end{array} \right] ,\qquad \mathbf{Df}=\left[ \begin{array}{c} \vdots \\ \sum_j{w_{ij}f_i} \\ \vdots \\ \end{array} \right] \end{equation} \begin{equation} \mathbf{\Delta f}=\left[ \begin{array}{c} \vdots \\ \left( \Delta f \right) _i \\ \vdots \\ \end{array} \right] =\left[ \begin{array}{c} \vdots \\ \sum_{j:(i,j)\in \mathcal{E}}{w_{ij}\left( f_i-f_j \right)} \\ \vdots \\ \end{array} \right] =\mathbf{Df}-\mathbf{Wf}=\left( \mathbf{D}-\mathbf{W} \right) \mathbf{f} \end{equation} \begin{definition}[Dirichlet energy of $ f $ ] \[\|f\|_{\mathcal{G}}^{2}=\frac{1}{2} \sum_{i j=1}^{n} w_{i j}\left(f_{i}-f_{j}\right)^{2}=\mathbf{f}^{\top} \boldsymbol{\Delta} \mathbf{f}\] \end{definition} \begin{pythoncode} import torch as tf a=1 \end{pythoncode} \cdr{redbox test} and \cdg{greenbox test} \end{document}