小米的主页 的子部分

Emacs 终生个人知识库 的子部分

什么是双链笔记与 org-roam 初体验

什么是笔记?

笔记核心功能:

  • 输入: 记录
  • 输出: 信息的提取与归纳

笔记类型举例

  • 课堂笔记
  • 学习心得, 复习笔记
  • 读书笔记
  • 数学草稿
  • 科研日志, 灵感
  • 教程/食谱/日记等, 例如: “如何安装 Emacs”, “如何做红烧肉”

信息提取

从笔记高效地提取信息很难

  • 纸质笔记
    • 优点: 简单
    • 缺点: 不能检索
  • 电子笔记: LaTeX, org, markdown
    • 优点: 可全文搜索, 读取信息能力大大提高
    • 缺点: 思考的过程, 知识间的联系难以体现.

传统笔记的局限性

  • 自上而下的树状结构
    • 树状结构无处不在: 图书馆目录, 文件夹, 书本章节段落
  • 缺陷
    • 输入: 初始分类难
    • 输出: 不利于发现联系
  • 传统笔记的完美形式就是一本书
  • 但是现代人遇到问题第一反应不是去翻书, 而是用搜索引擎或 ChatGPT.

理想的笔记模型

  • 双链笔记: 原子化笔记 + 网状结构
    • 原子化笔记: 将笔记拆分成独立, 完整的小条目
    • 网状结构: 用 链接 串连笔记, 自下而上生成结构
  • 原型: 卡片盒笔记 (Zettlekasten)
    • 卢曼 (Luhmann, 1927-1988): 德国社会学家
    • 卢曼从 1952 年左右开始构建卡片盒笔记, 最后笔记包含 9 万余条目
    • 卢曼一生发表了 50 本著作与 550 篇论文
  • 现代实现方式: 双链笔记/个人知识库软件 (2020 年前后)
    • Roam Research, Obsidian, Logseq, Notion
    • 在 Emacs 中: 基于 org-roam 的知识管理系统

为什么选择 org-roam

… 而不是 Roam Research, Obsedian, Logseq, Notion, …

我个人的笔记需求

  • 简洁但强大的笔记功能, 包含 LaTeX 数学公式, 交叉引用
  • 支持双链笔记
  • 能与文献管理软件如 Zotero 整合
  • 可视化界面; 易于导出为其它形式

笔记软件比较

  • Roam Research: 创建于 2020.1., 双链+图形界面, 订阅制 (基础版 180$/年)
  • Obsidian: 创建于 2020.3. 基于 Markdown 文件, 有强大插件生态, 基础版免费, 全平台使用
  • Logseq: 创建于 2020. 开源, 基于 Markdownorg 文件. 与 Obsedian 非常像
  • org-mode + Emacs
    • 本身强大的文本处理能力与笔记生态
    • 个性化设置
    • 与其它工作流程的整合
    • 底层结构是纯文本, 易于保存和导出
    • 开源软件, 永久免费, 强大的插件生态, 上限和自由度很高
    • 终生个人知识库: 平台也需要有强大的生命力, Emacs 有近 40 年历史

个人知识库的构建

  • 核心: 笔记间的 链接
  • 笔记类型
    • 灵感笔记 (fleeting note)
    • 文献笔记 (literature note)
    • 永久笔记 (pernament note)
  • 自下而上生成结构: 一组产生密切联系的笔记自然而然形成一个主题, 一个主题可以形成一条新的永久笔记并与其它笔记产生联系. 主题的结合可以产生新的主题, 如此反复.
  • 与个人 wiki 的区别: 个人知识库不仅仅是知识的记录, 还有对知识的归纳和提炼 – 这由我们建立的 链接 体现
  • 参考资料

系列视频内容

  • 本期视频
    • 安装与基本设置
    • 今天以后大家可以用 org-roam 记录学习 org-roam 的笔记
  • Zotero 文献管理整合 (org-ref, helm-bibtex, org-roam-bibtex)
  • org-noter 整合
  • 笔记模板设置
  • org 笔记流程优化
    • 数学公式 (org-cdlatex, org-preview)
    • 自动补全 (company)
    • 中文输入法 (pyim)
    • 交叉引用 (org-ref)
    • org 界面优化 (org-modern, org-face …)
  • 项目管理
  • 任务管理 (org-agenda)
  • 日记系统 (org-roam-daily)
  • 可视化 (org-roam-ui)
  • 导出设置

……

org-roam 安装

以 Emacs 28 为准

  • gcc 编译器: 用于编译 emacsql-sqlite (Emacs 29 后应该不再需要)
    • Windows 下可通过 msys2 安装 (推荐), 并保证在系统可执行文件目录下
                pacman -S mingw-w64-x86_64-gcc
    • LinuxMacOS 下大概率系统自带
  • org-roam 插件
    • 用内置的 package-el 安装: M-x package-install <return> org-roam <return>
    • use-package 模块中 :ensure t (见后面示例)
  • org-roam-ui 插件
    • 用内置的 package-el 安装: M-x package-install <return> org-roam-ui <return>
    • use-package 模块中 :ensure t (见后面示例)
  • emacsql-sqlite
    • 安装后第一次加载 org-roam, Emacs 会提示正在编译 emacsql-sqlite, 请耐心等待.
    • 编译成功后系统中会找到类似 .emacs.d/elpa/emacsql-sqlite-XXXXX/sqlite 的目录

基本设置

  • 笔记目录: 需要提前手动创建, 要易于同步;
  • 日记设置: 目录与快捷键
  • 快捷键: 使用任何一个将会启动 org-roam
              (use-package org-roam
                :ensure t ;; 自动安装
                :custom
                (org-roam-directory "~/roam-notes/") ;; 默认笔记目录, 提前手动创建好
                (org-roam-dailies-directory "daily/") ;; 默认日记目录, 上一目录的相对路径
                (org-roam-db-gc-threshold most-positive-fixnum) ;; 提高性能
                :bind (("C-c n f" . org-roam-node-find)
                       ;; 如果你的中文输入法会拦截非 ctrl 开头的快捷键, 也可考虑类似如下的设置
                       ;; ("C-c C-n C-f" . org-roam-node-find)
                       ("C-c n i" . org-roam-node-insert)
                       ("C-c n c" . org-roam-capture)
                       ("C-c n l" . org-roam-buffer-toggle) ;; 显示后链窗口
                       ("C-c n u" . org-roam-ui-mode)) ;; 浏览器中可视化
                :bind-keymap
                ("C-c n d" . org-roam-dailies-map) ;; 日记菜单
                :config
                (require 'org-roam-dailies)  ;; 启用日记功能
                (org-roam-db-autosync-mode)) ;; 启动时自动同步数据库
    
              (use-package org-roam-ui
                :ensure t ;; 自动安装
                :after org-roam
                :custom
                (org-roam-ui-sync-theme t) ;; 同步 Emacs 主题
                (org-roam-ui-follow t) ;; 笔记节点跟随
                (org-roam-ui-update-on-save t))

创建笔记及链接

创建链接是最基础也是最重要的功能!

  • org-roam-capture (快捷键 C-c n c) 创建一条新笔记. 未来可以预设不同主题的默认模板. 每一条笔记就是一个 org 文件

  • org-roam-find (快捷键 C-c n f) 通过关键词查找笔记并跳转

  • org-roam-insert (快捷键 C-c n i) 插入一条笔记的链接

    • 在查找笔记时利用 org-roam-find 的界面
    • 若笔记不存在, 则利用 org-roam-capture 的界面创建笔记
  • 删除笔记: 直接删除笔记文件即可, 如 M-x delete-file

  • org-mode 内链接跳转: 鼠标点击或 C-c C-o (org-open-at-point)

  • 加入标签: 普通的标签可以看作一个特殊的笔记条目, 可以用 org-roam-insert 去插入一些以关键字为标题的笔记充当标签.

    一个通用惯例是在笔记头部用 org 的列表格式罗列标签, 如

    ​      - tag :: <tag-1>, <tag-2>, <tag-3>

以后我们也可以把这一行加入笔记模板

优化 minibuffer 补全界面

vertico + orderless

    (use-package vertico ;; 补全界面优化
      :ensure t
      :config
      (vertico-mode))
    (use-package orderless ;; 无序搜索
      :ensure t
      :custom
      (completion-styles '(orderless basic))
      (completion-category-defaults nil)
      (completion-category-overrides '((file (styles partial-completion)))))

同步

  • org-roam 的笔记文件是纯文本文件, 用任意网盘同步即可
    • 不同机器上要设置好 org-roam-directory 变量 (或者直接放在网盘目录下)
    • 也可以用 git 同步: 可控制文件类型, 版本控制
  • 一般不需要同步数据库文件 org-roam.db.
    • 此数据库文件保存了链接信息, 一般保存在 ./emacs.d/ 目录下
    • 当笔记条目很多的时候, 这个文件会很大
    • 在不同的机器上可以根据 org 文件自动生成的 ((org-roam-db-autosync-mode))
  • 数据库更新
    • 自动更新: 通过 (org-roam-db-autosync-mode) 实现
    • 手动更新
      • 执行 M-x org-roam-db-sync
      • Windows 下执行这一命令可能会出现 Error ....... Selecting deleted buffer 的错误信息. 只要 emacs-sqlite 已经正常安装, 无视这条错误信息并再次执行 org-roam-db-sync 即可.

结语

PDF 读书笔记插件 org-noter

org-noter 是什么?

  • 实现功能: 用一个 .org 笔记文件作为书本页边空间的延展, 记录任何信息, 包括截图, 数学公式, 代码等
  • pdf 窗口和 org 窗口双向同步
    • 在浏览 pdf 文件时, 笔记窗口光标会跟随到相应位置
    • 在笔记文件中移动光标, pdf 文件会跟随滚动

选择 org-noter 的理由

… 或者选择 org 文件的理由

  • 电子笔记: 无纸化, 便携, 可检索
  • 纯文本格式, 易于保存和同步 (如用 Git)
  • 基于 org-mode 的强大功能和生态
    • 诞生于 2003 年 (Markdown 诞生于 2004 年)
    • org-mode 是 Emacs 中最强大的插件
    • 天生为笔记而生. 核心是"大纲式浏览" + “标记语言” + “任务管理”
    • 可以输出成 markdown, html, latex, odt 等多种格式
    • 本身支持 LaTeX 公式的编辑与预览

安装

  • org-mode: Emacs 自带, 可以通过 M-x package-list-package 更新到 Melpa 最新版
  • pdf-tools: 阅读 pdf 必备, 参考往期视频: 【Emacs+LaTeX教程】Emacs最强内置pdf阅读功能pdf-tools简介
  • org-noter: M-x package-list-package 安装 Melpa 最新版
    • 2023 年 3 月更新内容
      • 2 维笔记定位
      • djvu, epub 文件支持
      • 批注小标题优化
    • 如果安装了 use-package, 可将以下代码加入 init.el 自动安装
                (use-package org-noter
                  :ensure t )

基本操作

打开 org-noter

org-noter 打开需要执行 M-x org-noter 命令. 有两种方式

  • pdf 文件打开 需要指定 org 文件名及路径. 这时会自动生成一个 org 文件, 并在 org 文件中保存 pdf 文件的路径名
  • org 文件打开 这里 org 文件里须包含 pdf 文件的路径名, 一般来说是由第一种方式生成的 org 文件.

未来的计划: 结合 org-roam + org-roam-bibtex + Zotero, 我们可以生成一个包含 pdf 路径的 org-noter 读书笔记模板

  • 作为 org-noter 的笔记文件使用
  • 作为个人知识库一个节点被引用

大纲导入及 org-mode 基本操作

在 pdf 界面, M-x org-noter-create-skeleton 可以导入 pdf 大纲

小技巧: 在 pdf-tools 中, o / q 可以显示/关闭大纲.

org-mode 常用快捷键:

  • <tab> : 展开/折叠光标下标题, 进行如下循环: “只显示标题” -> “显示子标题” -> “显示子标题内容” -> “只显示标题”
  • <shift>-<tab>: 同时展开/折叠所有标题
  • M-<left> / M-<right> : 当前标题升级/降级
  • M-S-<left> / M-S-<right>: 当前标题及其子标题升级/降级

插入笔记

org-noter 通过记录页码及位置保持笔记文件与 pdf 文件的同步. 根据定位的精度, 在 pdf 界面, 有 3 种插入笔记方式

方法 1
i 在当前页插入笔记 (page note)
方法 2
M-i 在鼠标点击位置插入笔记 (precise note)
方法 3
选中文字后, 按 <tab> / M-ii 在选中文字位置插入笔记

在笔记中还可以使用 CDLaTeX 编辑数学公式, 以及用 org-download 插入截图. 请到教程的最后查看这些功能的基本设置.

位置同步

  • pdf -> org: 自动
  • org -> pdf: 手动
    • M-p / M-. / M-n: 上一/当前/下一页笔记
    • C-M-p / C-M-. / C-M-n: 上一/当前/下一条笔记
    • 以上命令也可以在 pdf 界面中使用

高级设置

默认笔记目录

  (setq org-noter-notes-search-path '("your/path/to/note-directory/" "2nd-path"  "3rd-path"))

设置后, 从 pdf 文件中使用 org-noter 命令会自动在上述目录中寻找与文件名同名的 .org 笔记文件.

两种 org-noter 使用模式

  • (推荐) 笔记在不同的 .org 文件中
  • 所有笔记在同一个 .org 文件中: 将上述变量设置为一个文件名
          (setq org-noter-notes-search-path '("your/path/to/notes.org"))

自动保存上次位置

  (setq org-noter-auto-save-last-location t)

选中文字后插入笔记自动高亮

  (setq org-noter-highlight-selected-text t)

这是全局设置. 如果想对某一条笔记临时启用或禁用, 可以使用 C-u 前缀.

例如: 当前高亮默认设置为 t, 但当前笔记不想高亮文字, 可以使用 C-u M-e 插入 precise note

长文本和短文本

  • 短文本默认标题为全文
  • 长文本默认标题为 Note for page XXX

修改长/短文本标准:

  (setq org-noter-max-short-selected-text-length 20) ;; 默认为 80

修改短文本默认标题:

  (setq org-noter-default-heading-title "第 $p$ 页的笔记")

修改快捷键

  (global-set-key (kbd "C-c n n") 'org-noter) ;; 与 org-roam 配合
  (define-key org-noter-doc-mode-map (kbd "e") 'org-noter-insert-note) ;; 加入左手键位
  (define-key org-noter-doc-mode-map (kbd "M-e") 'org-noter-insert-precise-note) ;; 加入左手键位

代码汇总

自定义设置的全部代码

  (setq org-noter-notes-search-path '("your/path/to/note-directory/")) ;; 默认笔记路径
  (setq org-noter-auto-save-last-location t) ;; 自动保存上次阅读位置
  (setq org-noter-max-short-selected-text-length 20) ;; 默认为 80
  (setq org-noter-default-heading-title "第 $p$ 页的笔记") ;; 默认短标题格式
  (global-set-key (kbd "C-c n n") 'org-noter) ;; 与 org-roam 配合
  (define-key org-noter-doc-mode-map (kbd "e") 'org-noter-insert-note) ;; 加入左手键位
  (define-key org-noter-doc-mode-map (kbd "M-e") 'org-noter-insert-precise-note) ;; 加入左手键位

将上述代码写在 use-package 代码块中:

  (use-package org-noter
    :ensure t
    :custom
    (org-noter-notes-search-path '("your/path/to/note-directory/")) ;; 默认笔记路径
    (org-noter-auto-save-last-location t) ;; 自动保存上次阅读位置
    (org-noter-max-short-selected-text-length 20) ;; 默认为 80
    (org-noter-default-heading-title "第 $p$ 页的笔记") ;; 默认短标题格式
    :bind
    (("C-c n n" . org-noter) ;; 与 org-roam 配合
     :map org-noter-doc-mode-map ;; 加入左手键位
     ("e" . org-noter-insert-note)
     ("M-e" . org-noter-insert-precise-note)))

LaTeX 公式

org-mode 中与 LaTex 有关的设置

    (use-package org
      :defer t ;; 延迟加载
      :custom
      (org-highlight-latex-and-related '(native latex entities)) ;; LaTeX 高亮设置
      (org-pretty-entities t) ;; LaTeX 代码的 prettify
      (org-pretty-entities-include-sub-superscripts nil) ;; 不隐藏 LaTeX 的上下标更容易编辑
      (org-format-latex-options
       '(:foreground default :background default :scale 1.8 :html-foreground "Black" :html-background "Transparent" :html-scale 1.0 :matchers ("begin" "$1" "$" "$$" "\\(" "\\["))) ;; 增大公式预览的图片大小
      :config
      (add-hook 'org-mode-hook #'org-cdlatex-mode) ;; 打开 cdlatex
  )

CDLaTeX 的设置可以参考我的视频

我会在另一期教程中详细介绍 org-modeLaTeX 编辑的设置.

截图功能

需要借助 org-download 实现. 在这里只给出实现截图功能的设置代码, 以后的教程再详细讲解.

使用方法

  • 使用系统截图工具 (Windows 下用 <Win>+<shift>+s) 将截图保存到剪贴板
  • C-M-y (绑定了 org-download-clipboard 函数) 将剪贴板中图片粘贴到 org 文件中.

安装方法

  • Linux / MacOS (未测试) 用以下 use-package 代码安装及设置
          (use-package org-download
            :ensure t ;; 自动从 melpa 上安装
            :defer t ;; 延迟加载
            :bind
            (:map org-mode-map
                  ("C-M-y" . org-download-clipboard)) ;; 绑定从剪贴版粘贴截图的快捷键
            :custom
            (org-download-heading-lvl 1) ;; 用一级标题给截图文件命名
            :config
            (setq-default org-download-image-dir "./img")) ;; 用同级 ./img 目录放置截图文件
  • Windows
    • 需要安装 ImageMagick, 并保证 magick.exePATH 变量的路径中 用 msys2 安装
                pacman -S mingw-w64-x86_64-imagemagick
    • 这里或网盘下载 org-download.el 文件, 置于你的 .emacs.d 文件夹合适的路径中 (如 ~/.emacs.d/lisp/)
    • 使用以下 use-package 代码及设置
                (use-package org-download
                  :ensure async ;; 因为不是从melpa安装, 需要手动保证async安装
                  :defer t ;; 延迟加载
                  :load-path "~/.emacs.d/lisp/"
                  :bind
                  (:map org-mode-map
                        ("C-M-y" . org-download-clipboard)) ;; 绑定从剪贴版粘贴截图的快捷键
                  :custom
                  (org-download-heading-lvl 1) ;; 用一级标题给截图文件命名
                  :config
                  (setq-default org-download-image-dir "./img")) ;; 用同级 ./img 目录放置截图文件

用 org-roam 管理 Zotero 文献笔记

实现功能

  • Emcas 中读取 Zotero 文献数据库并进行引用
  • 每一条引用的 Zotero 文献建立一则 org-roam 笔记
  • 将上述笔记作为 org-noter 笔记文件, 在 Emacs 中阅读 Zotero 文献的 pdf 附件

实现方法

  • Zotero
    • Better BibTeX 导出文献数据至 bib 文件
    • ZotFile 保存 PDF 文件 (一般是网盘文件夹)
  • helm/ivy-bibtex:
    • 将上面 bib 文件的信息读入 Emacs, 并提取文献的标题, 作者, 引用键名等
    • 提供搜索文献信息的界面
  • org-roam: 准备一个符合 org-noter 的笔记格式的 org-roam 模板
  • org-roam-bibtex + org-roam
    • 利用提取的文献信息和上面的模板生成一则 org-roam 笔记
    • 其它一些操作, 如打开文献网址, 打开文献 bibtex 条目等
  • org-noter + pdf-tools 打开 org-roam 笔记中的 PDF 文件并阅读, 做读书笔记.

代码

  • org-roam 基本设置:

【从零搭建Emacs个人知识库】什么是双链笔记与 org-roam 初体验

  ;; 第一步: 告诉 Emacs 从哪里读取 Zotero 的信息
  (setq zot_bib '("~/Nutstore/1/Nutstore/Zotero-Library/Better BibTeX Export/My Library.bib"
                  "<另一个Zotero bib 文件>.bib") ; Zotero 用 Better BibTeX 导出的 .bib 文件. 可以是多个文件
        zot_pdf "~/Nutstore/1/Nutstore/Zotero-Library" ; Zotero 的 ZotFile 同步文件夹
        org_refs "~/repos/notes/ref/" ) ; 自定义的 org-roam 文献笔记目录. 我的 org-roam 根目录是 ~/repos/notes

  ;; 第二步: 让 helm-bibtex 读取 Zotero 的信息
  (use-package helm-bibtex ; 这里也可以用 ivy-bibtex 替换 helm-bibtex
    :ensure t
    :custom
    (bibtex-completion-notes-path org_refs)
    (bibtex-completion-bibliography zot_bib)
    (bibtex-completion-library-path zot_pdf))

  ;; 第三步: 让 org-roam-bibtex 使用 helm-bibtex 的信息, 并绑定 orb 的快捷键
  (use-package org-roam-bibtex
    :ensure t
    :after org-roam
    :hook (org-roam-mode . org-roam-bibtex-mode)
    :bind (("C-c n k" . orb-insert-link)
           ("C-c n a" . orb-note-action))
    :custom
    (orb-insert-interface 'helm-bibtex) ; 与上面 helm-bibtex/ivy-bibtex 的选择保持一致
    (orb-insert-link-description 'citekey) ; 默认是用标题, 但是论文的标题一般很长, 不适合作为笔记链接的名字
    (orb-preformat-keywords
     '("citekey" "title" "url" "author-or-editor" "keywords" "file"))
    (orb-process-file-keyword t)
    (orb-attached-file-extensions '("pdf")))

第四步: 加入一个文献笔记的 org-roam 模板

  (use-package org-roam
    :ensure t ;; 自动安装
    :custom
    (org-roam-directory "~/repos/notes/") ;; 默认笔记目录, 提前手动创建好
    (org-roam-dailies-directory "daily/") ;; 默认日记目录, 上一目录的相对路径
    (org-roam-db-gc-threshold most-positive-fixnum) ;; 提高性能
    :bind (("C-c n f" . org-roam-node-find)
           ("C-c n i" . org-roam-node-insert)
           ("C-c n c" . org-roam-capture)
           ("C-c n l" . org-roam-buffer-toggle) ;; 显示后链窗口
           ("C-c n u" . org-roam-ui-mode)) ;; 浏览器中可视化
    :bind-keymap
    ("C-c n d" . org-roam-dailies-map) ;; 日记菜单
    :config
    (require 'org-roam-dailies)  ;; 启用日记功能

    ;;============= 新增内容 =================
    ;; 第四步: 用 org-roam 生成文献笔记,  放在 org-roam 的 use-package 代码块 :config 关键字之后
    ;; 下面的 (setq my/ref-template ...) 可以放到 use-package 代码块之外
    (setq my/ref-template
          (concat "#+FILETAGS: reading research \n"
                  "- tags :: %^{keywords} \n"
                  "* %^{title}\n"
                  ":PROPERTIES:\n"
                  ":Custom_ID: %^{citekey}\n"
                  ":URL: %^{url}\n"
                  ":AUTHOR: %^{author-or-editor}\n"
                  ":NOTER_DOCUMENT: ~/Nutstore/1/Nutstore/Zotero-Library/%^{citekey}.pdf\n"
                  ":NOTER_PAGE:\n"
                  ":END:"))
    (add-to-list 'org-roam-capture-templates
                 `("r" "Zotero 文献模板" plain ; 文献笔记模板
                   ,my/ref-template
                   :target
                   (file+head "ref/${citekey}.org" "#+title: ${title}\n")))
    ;;============= 新增内容结束 =================
    (org-roam-db-autosync-mode)) ;; 启动时自动同步数据库

模板详解

  (setq my/ref-template
        (concat "#+FILETAGS: reading research \n"
                "- tags :: %^{keywords} \n"
                "* %^{title}\n"
                ":PROPERTIES:\n"
                ":Custom_ID: %^{citekey}\n"
                ":URL: %^{url}\n"
                ":AUTHOR: %^{author-or-editor}\n"
                ":NOTER_DOCUMENT: ~/Nutstore/1/Nutstore/Zotero-Library/%^{citekey}.pdf\n"
                ":NOTER_PAGE:\n"
                ":END:"))
  (add-to-list 'org-roam-capture-templates
               `("r" "Zotero 文献模板" plain ; 文献笔记模板
                  ,my/ref-template
                  :target
                  (file+head "ref/${citekey}.org" "#+title: ${title}\n")))
  • \n 表示换行符 (一个短字符串就是一行)
  • 需要手动保持一致的部分:
    • NOTER_DOCUMENT: 后面接 PDF 文件目录与 zot_pdf 一致
    • file-head 后面文献笔记默认文件夹与 org_refs 一致
  • * %^{title} 之后的内容是 org-noter 识别的关键字, 不建议修改
  • 自由发挥的部分:
    • #+FILETAGS: org-roam 笔记标签, 会显示在 C-c n f 搜索界面并可搜索. 以空格分隔. 可以根据自己需要设置
    • - tags :: 存放 Zotero 文献关键字

从零搭建个人知识库: Org 笔记中高效写 LaTeX

前言

在 Org-mode 中获得和 LaTeX 模式 一致 的 LaTeX 代码体验:

  • 使用 CDLaTeX
  • 语法高亮
  • 数学符号 prettify
  • 括号自动匹配
  • 快速公式预览
  • LaTeX 导言区 (preamble) 共享

默认设置

  • 输入数学公式
    • 行内公式: \(...\), $...$
    • 行间公式 \[...\], $$...$$, \begin{XXX}...\end{XXX}
  • 在行间数学公式中用 C-c ‘ 可进入 LaTeX 编辑模式. (有些版本的 org-mode 的 LaTeX 编辑模式是采用 latex-mode)
          ;; 你自己的 LaTeX 模式设置
          (defun my/latex-hook ()
            (turn-on-cdlatex)
            (turn-on-reftex))
    
          (add-hook 'LaTeX-mode-hook 'my/latex-hook)
          (add-hook 'latex-mode-hook 'my/latex-hook)
  • 数学公式预览
    • 预览当前位置/当前选中区域 C-c C-x C-l
    • 取消预览: C-u + C-c C-x C-l

打开 CDLaTeX 模式

Org-mode 有一个专属的 org-cdlatex-mode, 打开后可以使用 CDLaTeX 模式中定义的所有快捷键命令. 关于 CDLaTeX 可以参考

 (add-hook 'org-mode-hook #'org-cdlatex-mode) ;; 在 org-mode 中使用 cdlatex

主要区别:

  • Tab 要兼容 org-mode 中的展开/折叠功能
    • 环境补全只能用在行首, 如 equ + Tab = \begin{equation}\end{equation}
    • 数学环境中 Tab 的功能与 LaTeX 一致, 如 qq + Tab = \quad
    • 其余情形 Tab 执行 Org-mode 的展开/折叠
  • 反引号 ` 输入数学符号可以在非数学环境中使用 (但是符号不能被预览, 只能 prettify).
  • $, ( 等不再自动匹配.

语法高亮与美化

  (setq org-highlight-latex-and-related '(native latex entities)) ;; LaTeX 语法高亮设置
  (setq org-pretty-entities t) ;; LaTeX 代码的 prettify
  (setq org-pretty-entities-include-sub-superscripts nil) ;; 不隐藏 LaTeX 的上下标更容易编辑

括号和 $ 匹配问题

其实也可以用 cdlatex-mode 替代 org-cdlatex-mode, 但是确实有一些副作用.

折衷的解决方法: 复写 cdlatex-mode 中的括号和 $ 匹配函数.

  ;; org-cdlatex-mode 中使用 cdlatex 的自动匹配括号, 并把 $...$ 换成 \( ... \)
  (defun my/insert-inline-OCDL ()
    (interactive)
    (insert "\\(") ;; 把 "\\(" 和 "\\)" 替换成 "$" 就能实现输入成对 "$" 的功能.
    (save-excursion (insert "\\)" )))
 (defun my/insert-bra-OCDL ()
    (interactive)
    (insert "(")
    (save-excursion (insert ")" )))
  (defun my/insert-sq-bra-OCDL ()
    (interactive)
    (insert "[")
    (save-excursion (insert "]" )))
  (defun my/insert-curly-bra-OCDL ()
    (interactive)
    (insert "{")
    (save-excursion (insert "}" )))

  (define-key org-cdlatex-mode-map (kbd "$") 'my/insert-inline-OCDL)
  (define-key org-cdlatex-mode-map (kbd "(") 'my/insert-bra-OCDL)
  (define-key org-cdlatex-mode-map (kbd "[") 'my/insert-square-bra-OCDL)
  (define-key org-cdlatex-mode-map (kbd "{") 'my/insert-curly-bra-OCDL)

这里还做了一个优化: 把 $ 绑定成输入 \(?\), 原因有二:

  • $ 输入行内公式, 要求公式内紧临 $ 的字符不能是空格, 否则不能识别为数学环境. 识别失败又会导致语法高亮、 cdlatex-mode 不能触发等问题
  • $ 输入行内公式, 后面必须是空格. 这和 LaTeX 模式下输入习惯不同.

\(?\) 替代就没有以上所有问题.

公式预览

基本命令

  • C-c C-x C-l: 预览/取消预览当前光标所在数学公式, 或预览当前章节所有数学公式
  • C-u + C-c C-x C-l: 取消 预览当前 章节 所有数学公式
  • C-u C-u + C-c C-x C-l: 预览当前 缓冲区 所有数学公式
  • C-u C-u C-u + C-c C-x C-l: 取消 预览当前 缓冲区 所有数学公式.

预览图片大小调整

  (setq my/latex-preview-scale 2) ;; 一般来说这里的 scale 约等于 set-face-attribute 中的 :height /100
  (setq org-format-latex-options
        `(:foreground default :background default :scale ,my/latex-preview-scale :html-foreground "Black" :html-background "Transparent" :html-scale ,my/latex-preview-scale :matchers ("begin" "$1" "$" "$$" "\\(" "\\["))) ;; 增大公式预览的图片大小

快速编译 org-preview

org-preview 是一个非正式的包, 极大 提高 了数学公式预览速度 (异步编译, 有望加入未来版本的 Org).

  ;; 快速编译数学公式
  (use-package org-preview
    :load-path "lisp/" ; 需要手动从网盘或 https://github.com/karthink/org-preview/ 下载 org-preview.el 文件, 并置于 ~/.emacs.d/lisp/ 文件夹下
    ;; straight 用户用下一行取代上一行
    ;; :straight (:host github :repo "karthink/org-preview")
    :hook (org-mode . org-preview-mode))

自动预览 org-fragtog

  • 光标移出公式, 自动编译
  • 光标移入公式, 自动展开
  (use-package org-fragtog
    :hook (org-mode . org-fragtog-mode))

LaTeX 导言区共享

Org-mode 如何用上自定义的 LaTeX 宏命令?

方法一: 使用 #+LATEX_HEADER: 关键字

在 Org 文件开头加入 = #+LATEX_HEADER: \newcommand{\R}{\mathbb{R}} 就可以在整个文件中使用 \mathbb{R}

方法二: 使用 #+SETUPFILE: 关键字

可以创建一个叫 latex-preamble.org 放在 Org-roam 目录下, 然后给所有的笔记文件开头 (可以放在笔记模板里) 加上 #+SETUPFILE: ./latex-preamble.org 然后把所有要用的导言区命令每一行加上 #+LATEX_HEARDER: 放在 latex-preamble.org 文件中.

方法三: 通过 .sty 文件与 LaTeX 文件共享自定义命令

  • 把常用的导言区命令放在 ~/texmf/tex/latex/ 目录下的 mysymbol.sty 文件中
  • 添加设置
          ;; 在 ~/texmf/tex/latex/ 下的 .sty 文件
          (setq org-latex-packages-alist '(("" "mysymbol" t)))

原理:

  • ~/texmf 是正常情况下 $TEXMF 的目录. 可以通过命令行 kpsewhich --var-value TEXMF 确认
  • $TEXMF/tex/latex/ 目录下的 .sty 的文件可以被 \usepackage 引用.
  • 上述 Emacs 设置相当于 Org 编译 LaTeX 公式时默认加载 \usepackage{mysymbol} 命令; 而在 LaTeX 文件中也可以使用同样的命令.

如何导出 Org 笔记

  • C-c C-e h o 导出成 HTML 并打开
  • C-c C-e l o 导出成 PDF 并打开.

总结

Org-mode 和 CDLaTeX 的强强联合

  • Org 笔记的灵活性
  • Org-roam 双链笔记工作流程
  • CDLaTeX 的流畅数学公式体验

代码汇总

  ;; 你自己的 LaTeX 模式设置
  (defun my/latex-hook ()
    (turn-on-cdlatex)
    (turn-on-reftex))

  (add-hook 'LaTeX-mode-hook 'my/latex-hook)
  (add-hook 'latex-mode-hook 'my/latex-hook)
  (add-hook 'org-mode-hook #'org-cdlatex-mode) ;; 在 org-mode 中使用 cdlatex

  (setq org-highlight-latex-and-related '(native latex entities)) ;; LaTeX 语法高亮设置
  (setq org-pretty-entities t) ;; LaTeX 代码的 prettify
  (setq org-pretty-entities-include-sub-superscripts nil) ;; 不隐藏 LaTeX 的上下标更容易编辑


  ;; org-cdlatex-mode 中使用 cdlatex 的自动匹配括号, 并把 $...$ 换成 \( ... \)
  (defun my/insert-inline-OCDL ()
    (interactive)
    (insert "\\(") ;; 把 "\\(" 和 "\\)" 替换成 "$" 就能实现输入成对 "$" 的功能.
    (save-excursion (insert "\\)" )))
  (defun my/insert-bra-OCDL ()
    (interactive)
    (insert "(")
    (save-excursion (insert ")" )))
  (defun my/insert-sq-bra-OCDL ()
    (interactive)
    (insert "[")
    (save-excursion (insert "]" )))
  (defun my/insert-curly-bra-OCDL ()
    (interactive)
    (insert "{")
    (save-excursion (insert "}" )))

  (define-key org-cdlatex-mode-map (kbd "$") 'my/insert-inline-OCDL)
  (define-key org-cdlatex-mode-map (kbd "(") 'my/insert-bra-OCDL)
  (define-key org-cdlatex-mode-map (kbd "[") 'my/insert-square-bra-OCDL)
  (define-key org-cdlatex-mode-map (kbd "{") 'my/insert-curly-bra-OCDL)

  (setq my/latex-preview-scale 2) ;; 一般来说这里的 scale 约等于 set-face-attribute 中的 :height /100
  (setq org-format-latex-options
        `(:foreground default :background default :scale ,my/latex-preview-scale :html-foreground "Black" :html-background "Transparent" :html-scale ,my/latex-preview-scale :matchers ("begin" "$1" "$" "$$" "\\(" "\\["))) ;; 增大公式预览的图片大小
  ;; 快速编译数学公式
  (use-package org-preview
    :load-path "lisp/" ; 需要手动从网盘或 https://github.com/karthink/org-preview/ 下载 org-preview.el 文件, 并置于 ~/.emacs.d/lisp/ 文件夹下
    ;; straight 用户用下一行取代上一行
    ;; :straight (:host github :repo "karthink/org-preview")
    :hook (org-mode . org-preview-mode))

  (use-package org-fragtog
    :hook (org-mode . org-fragtog-mode))

  ;; 在 ~/texmf/tex/latex/ 下的 .sty 文件
  (setq org-latex-packages-alist '(("" "mysymbol" t)))

Zotero 文献管理系统简介

软件界面

  • Zotero 界面: 收藏夹 - 文献列表 - 文献信息
  • 收藏夹功能
    • 同一文献可以属于不同收藏夹
    • 同一收藏夹内文献可以导出成 .bib 或其它文件
    • 小技巧: 按 <ctrl> 并点击文献, 左侧会高亮文献所在的收藏夹
  • 文献可以有 pdf 文件作为附件.
    • 双击文献可以用 Zotero 内置 pdf 阅读器打开 pdf 文件
    • 内置阅读器支持高亮, 批注等
    • 也可以设置别的默认阅读器

向 Zotero 中加入文献

方法 1
利用浏览器的 Zotero Connector 插件 (下载链接)
  • 支持: Edge, Chrome, FireFox, Safari
  • 自动读取文献信息, 允许时自动下载 pdf 全文
  • 10 年前很有用的功能: 网页快照, 先存再看
方法 2
利用文件标识码, 如 DOI, ISBN 或者 Arxiv ID
方法 3
从本地 pdf 文件创建. Zotero 会尝试获取文献信息

为保证参考文献信息准确, 建议多使用前两种方法

手动添加 pdf 附件: 适用于非官方手段获取的 pdf 文件

合并文献信息: Duplicate Items

功能小结

Zotero 核心功能

  • 收集文献并整理文献信息
  • 导出 .bib 等多种格式引用文件
  • 管理文献 pdf

两个有用的插件

  • Better BibTeX:
    • 自定义 (更加统一的) 键名格式
    • 实时更新导出的 .bib 文件, 与 Org-roam, Obsidian, Roam Research 等笔记软件交互必备!
  • Zotfile:
    • 自动根据规则重命名 pdf 文件
    • 自动移动 pdf 文件至个人网盘同步

插件安装方法

Better BibTeX 设置

设置界面在 Edit -> Preference -> Better BibTeX

  • 引用键名格式
    • 我的设置: authorsAlpha+year+shorttitle(3,3)
      • authorsAlpha: 采用 bibtex 中的 alpha 风格; 单作者取姓的前 3 字母, 2 个以上取首字母
      • year: 4 位数字年份
      • shorttitle(m,n): 标题前 m 个单词, 其中前 n 个单词首字母大写
    • https://retorque.re/zotero-better-bibtex/citing/ 有更多设置
  • 保证键名不重复: keey citekey unique -> across all libraries
  • .bib 文件自动导出与更新
    • 选中收藏夹, 导出, 勾选自动更新
    • 更新频率设置: Automatic Export -> Automatic Export -> On change

ZotFile 同步设置

  • Zotero 登录后有 500M 的同步空间, 因此最好有第 3 方网盘同步 pdf 文件

    • Zotero 帐号负责同步文献信息
    • ZotFlie 负责用网盘同步 pdf 文件
    • Better BibTeX 的键名无法同步, 但是相同规则的键名是一样的.
    • 在新机器上建议: 先设置好 Better BibTeX, 再登录 Zotero 帐号进行同步.
  • Zotfile 设置: Tools -> ZotFile Preference

    • 同步目录: General Settings -> Custom Locations
      • 使用个人网盘目录下的文件夹
      • 无须子文件夹, 因为子文件夹不方便搜索而且多余
    • pdf 文件重命名规则: Renaming Rules
      • 建议使用 %b, 表示用 bibtex 键名命名 pdf
        • 方便查找
        • 大幅提高笔记软件交互效率
    • 自动重命名: Advance Settings -> Automatically rename new attachment -> always rename
      • 对于 Zotero 自动下载的 pdf 有效
      • 手动添加的 pdf, 右键点击并选择 rename and move 即可

下期内容

org-roam 笔记流程中整合 Zotero, 包括

  • Emcas 中读取 Zotero 文献数据库并进行引用
  • 每一条引用的 Zotero 文献建立一则 org-roam 笔记
  • 将上述笔记作为 org-noter 笔记文件, 在 Emacs 中阅读 Zotero 文献的 pdf 附件

省时省力写 LaTeX 的子部分

Emacs 快速配置与插件管理

本小节将介绍如何快速配置一个用于 LaTeX 写作的 Emacs, 包括软件安装和插件管理, 以及 Emacs 的一些必要的入门知识. 最后将推荐两组非常实用的插件: 第一组插件 Which-key + Keycast + Helpful 将帮助我们快速熟悉 Emacs 的操作和概念, 第二组插件 Vertico + Marginalia + Orderless 将有效提升我们在小缓冲区的补全体验, 并附上 AucTeXCDLaTeX 的基本设置.

Emacs 安装

Emacs 可以运行在 Windows, Linux, MacOS 上, 也可以通过 termux 运行在 Android 手机或平板上. 这里我们只介绍电脑系统上的 Emacs 安装.

在本文撰写时 (2023.4), Emacs 的最新正式版本为 28.2, 最近的测试版本为 29.090. 下面介绍的安装方法针对的是正式版. (更新: 2023.8 已经可以下载 Emacs 29 正式版.)

在多数平台上, 我们是利用合适的开源软件管理器来安装 Emacs. 在 Linux 中这是自带的, 在 Windows 和 MacOS 中大家需要自行安装. 我们下面详细说明.

Linux

Linux 系统中可以用自带的软件管理器安装 Emacs. 例如, 在 Ubuntu 中使用 apt 安装 Emacs, 只需要在命令行中输入

  sudo apt install emacs

MacOS

MacOS 中需要先安装 Homebrew. 方法是在命令行中输入

 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

当 Homebrew 安装成功后会有提示, 然后就可以使用 Homebrew 安装 Emacs 了: 在命令行中输入

 brew install emacs

Windows

Windows 下安装 Emacs 有两种常见方法. 第一种是安装 MSYS2 (下载链接). 安装后打开 C:/msys64/mingw64.exe (C:/msys64/ 是 MSYS2 的默认安装目录, 根据实际情况调整). 在命令行中输入

  pacman -S mingw-w64-x86_64-emacs

通过 MSYS2 安装的一个好处是我们可以通过 Pacman 管理 Emacs 的更新. 另一个好处是在上面还可以很方便安装其它开源软件, 例如 Git, Epdfinfo 等. Epdfinfo 是在 Windows 下使用 Emacs 的 PDF-tools 插件的必需软件 (BV1pg4y1s7Z9).

另一种安装方法是直接从官网上下载安装包. Emacs 28 的安装包已经优化了不少, 会自动把程序安装至 C:/Program Files/emacs 目录下, 并附带卸载程序. 通过安装包安装的 Emacs 需要我们手动更新.

安装 Emacs 后的额外设置

Ctrl 键设置

安装完 Emacs 之后, 我 强烈建议 大家交换 Caps LockLeft Ctrl. Emacs 常常使用以 Ctrl 开始的快捷键, 因此把 Ctrl 与不常用的大写锁定 Caps Lock 交换是每个 Emacs 使用者对电脑 做的第一件事. Ctrl 键的广泛使用是因为在 Emacs 诞生之初, 当时通用的键盘 Ctrl 确实在当今的 Caps Lock 位置上. 再啰嗦一句: 交换 Caps lockCtrl 绝不是一件可有可无的事情, 它在我们日常使用 Emacs 中真的非常重要! 大家千万不要怕麻烦.

交换 CtrlCaps Lock 的方法在不同系统上也不一样.

Windows

Windows 中更改键位可以通过注册表或者最新的 PowerToys 软件.

  • 注册表方法 (适用于 Win 10 以前)

    方法如下:

    • Win + r 并输入 regedit 打开注册表
    • 找到目录 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
    • 新建 Scancode 文件, 把内容修改为
           00 00 00 00 00 00 00 00
           03 00 00 00 1d 00 3a 00
           3a 00 1d 00 00 00 00 00
    • 保存 Scancode 文件并重启电脑
  • PowerToys (适用于 Win 10, Win 11)

    • 将系统中 “微软商店” 更新到最新版本 (Win 10 不更新可能会找不到 PowerToys)
    • 在 “微软商店”= 中搜索 PowerToys 并安装
    • 在 PowerToys 中找到键位设置, 并交换 Caps LockLeft Control

Ubuntu 及其它 Linux 系统

在 Ubuntu 下, 可以安装 Gnome-tweaks:

 sudo apt install gnome-tweaks

然后打开 Gnome-tweaks 的键盘设置, 在高级选项里有关于 Ctrl 键的设置. 你不仅仅可以交换它与 Caps Lock, 也可以进行许多别的设置.

又或者, 在很多 Linux 系统的命令行下输入

  setxkbmap -option ctrl:swapcaps

也可以交换 CtrlCaps Lock .

MacOS

在 MacOS 中, 大家可以在 system -> keyboard -> functional keys 中调整所有功能键的键位.

家目录与系统路径

剩下两个设置只有 Windows 用户需要进行.

第一是把 Emacs 的家目录, 即 Emacs 中通过 ~ 访问的目录, 改成 C:/Users/<用户名>/. 默认的家目录是 C:/Users/<用户名>/AppData/Roaming/. 从这个目录出发不方便我们访问像 “我的文档” 这种常用文件夹, 所以我们需要手动修改家目录为 C:/Users/<用户名>/, 与 Linux 和 MacOS 的使用习惯保持一致.

修改家目录的方法是在环境变量的设置中 (可以在 Windows 搜索栏中搜索 Edit system variables 打开), 增加一个用户的环境变量 HOME, 把它设置为 C:/Users/<用户名>/.

第二是保证你的 Emacs 安装目录在系统变量 PATH 上. 如果不在, 还是在同一个界面, 把包含你 emacs.exe 的文件夹路径手动添加到 PATH 变量中.

Emacs 基本知识讲解与必知快捷键

作为 Emacs 新手, 大家需要理解的一个核心概念就是命令. 在 Emacs 中所有的操作, 无论简单复杂都是命令. Emacs 这个单词就来自于 macro, 即宏命令. 比如说我们想执行打开一个文件的操作, 有 3 种方式等价的操作方式:

  1. 在菜单栏里选择 file -> visit new file, 然后和普通的软件一样选择你要打开的文件.
  2. 执行 open-file 命令. 方法是按下 M-x (M = Alt), 然后在最下面的小缓冲区输入 open-file, 然后输入文件名.
  3. 按下 C-x C-f 快捷键, 并输入文件名.

Emacs 中有许多有用的命令. 你未来也可以自己通过 Elisp 语言编写自己命令. Emacs 把其中最常用的命令都绑定了快捷键, 用户自己也可以设置自己的快捷键. 当我们用熟了之后, 很多快捷键就会像打字一样形成肌肉记忆. 作为新手, 我们有很多键盘的快捷操作可以用鼠标代替, 因此你不必急于一下子掌握全部 Emacs 的快捷键.

但是, 仍有一些快捷键是大家最好尽快熟悉的. 下面这张表我给大家总结了新手必知的几个快捷键. 在表中, C 表示 Ctrl, M 表示 Alt. 这也是 Emacs 快捷键通用写法. 最右边一列是快捷键对应的命令名, 也就是第一列所有的快捷键都等价于 M-x 加上第三列.

快捷键操作命令名
C-g中止当前一!切!命!令!keyboard-quit
C-/撤销命令undo
文件操作
C-x C-f打开文件find-file
C-x C-s保存文件save-buffer
C-x b切换文件 (缓冲区)switch-to-buffer
C-x 1关闭其它窗口delete-other-window
C-h f/v/k查询命令/变量/快捷键describe-function/variable/key
文本处理
M-x复制kill-ring-save
C-w剪切kill-region
C-y粘贴yank

中止命令与撤销命令

在 Emacs 中发生误操作时, 你需要知道如何中止与撤销命令. 当你的快捷键输入一半想反悔时 (是的, Emacs 的快捷键可以很长!), 可以使用 C-g 重新来输入, 又或者 Emacs 在执行命令时卡住了, 你可以通过 C-g 来让它恢复正常.

如果你需要撤回上一条命令, 则需要使用 C-/. 但值得注意的是, 撤回撤回命令的命令也是同一个键; 这偶尔会让人抓狂.

文件与窗口相关命令

下面我们介绍 Emacs 中最基础的几个管理界面的快捷键.

首先是打开文件, C-x C-f, 命令名是 find-file. 这里的 find 隐含 Emacs 会根据不同情况执行不同操作: 若文件存在, 则是普通的打开文件; 若文件不存在, 则是打开一个新文件.

第二个是保存文件, C-x C-s, 对应 save-buffer, 即把当前缓冲区 (更新后) 的内容写进文件里.

大多数情况将缓冲区 (buffer) 等同于文件不会影响你的 Emacs 使用. 这里简单讲讲它们的不同. 文件存在于电脑硬盘上, 而 Emacs 的缓冲区只显示文件内容. 当你把文件内容读入缓冲区以后, 又在 Emacs 外修改了文件的内容, 缓冲区中的内容并不会改变, 除非你明确指示 Emacs 重新读取. 而在 Windows 中, 一个文件同时只能被一个 Windows 程序打开. Emacs 的缓冲区也不一定对应着文件, 在模式栏大家可以看到当前缓冲区的名字. 名字被两个 * 号包含的一般是非文件的缓冲区, 例如 *Message* 用于显示 Emacs 给用户的信息, 编译 LaTeX 时 *Output* 会存放编译输出结果等.

第三个命令是切换缓冲区/文件, C-x b, 对应 switch-to-buffer. 执行后在最下方的小缓冲区会提示输入你想要切换的缓冲区名字, 默认是上一个显示的缓冲区, 直接回车就行.

在 Emacs 中同时显示多个缓冲区的方法是打开多个窗口 (window), 然后在每个窗口中显示一个缓冲区. 有时 Emacs 自动创建新的窗口, 例如展示帮助信息时. 新手最常用的操作是保留当前光标所在窗口, 而关掉其它所有窗口. 这可以通过, C-x 1, 即 delete-other-window 实现. 我们可以用鼠标辅助我们在不同窗口间切换.

帮助命令

Emacs 中查询帮助信息的快捷键是 C-h <字母>. 常用的有 C-h f, 查询命令, C-h v, 查询变量, 以及 C-h k, 查询快捷键. 通常 C-h 命令会自动创建新的窗口显示帮助信息. 我们可以先把光标移到我们工作的缓冲区, 然后用 C-x 1 关闭掉帮助信息窗口. 注意此时帮助信息的缓冲区并没有关闭, 重新显示可以通过 C-x b 并查找以 *help* 命名的缓冲区.

复制/剪切/粘贴

Emacs 有自己一套复制/剪切/粘贴的快捷键: M-w / C-w / C-y. 这和一般程序的 C-c / C-x / C-v 不同, 需要大家习惯. 所有复制或剪切的内容都会进入一个叫 kill-ring 的地方, 它相当于一个剪粘版的历史记录. 粘贴快捷键 C-y 会粘贴最近一条记录, 如果你想访问之前的记录, 可以紧跟着 C-y 再按下一次或多次 M-y.

Emacs 插件管理

接下来我们介绍如何更好地管理 Emacs 插件. Emacs 插件也叫 Emacs 包 (package). 插件可以给我们带来更多的功能, 是 Emacs 使用中不可缺少的一环. 插件的安装和设置与其它的 Emacs 设置一样, 都放在 Emacs 的启动文件 ~/.emacs.d/init.el 中. 关于插件安装与设置, 我推荐大家使用现在常用的 use-package 语法, 它的语法更简洁, 还可以很方便地自动安装插件.

Emacs 中下载新的插件可以通过不同的方式 (这也是由某些插件提供的). 常用的有两种, 一种是用内置的 package.el, 这个插件名字就叫 package.el, .el 后缀来自于 Emacs 的编程语言 Elisp. 第二种是用 Straight. package.el 会从官方的插件库 (ELPA, MELPA) 或镜像网站上下载新插件, 而 Straight 用下载插件的源代码并编译, 一般是利用 Git 从 Github 上下载. 为了使用 Straight, 你需要系统上已经安装了 Git 程序, 并且能正常地访问 github.com. 以下我们介绍两种安装方式如何设置.

我们在 package.el 和 Straight 的设置示例中都手动检查并安装了 use-package. Emacs 29 后 use-package 已经是内置插件, 相关代码可以省去.

package.el 设置示例

以下我们提供了 package.el 的一个设置示例. 大家需要把如下代码放入设置文件 ./.emacs.d/init.el 中.

    ;; -*- lexical-binding: t; -*-
    ;; 静态作用域声明必须放在首行
    ;; 把 Emacs 自动添加的代码放到 custom.el 中
    (setq custom-file (expand-file-name "custom.el" user-emacs-directory))
    ;;========================================
    ;; 使用 package.el 设置 Emacs 插件管理
    ;;========================================
    (require 'package) ; 加载 package.el
    (setq package-check-signature nil) ; 如果检查签名有问题可以加入这一行
    ;; 添加仓库位置
    (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
    (add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
    ;; 国内用户也可以使用清华的镜像网站. 用下面的代码代替上面两行
    ;; (setq package-archives
    ;;       '(("gnu" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/")
    ;;         ("nongnu" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/nongnu/")
    ;;         ("melpa-stable" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/stable-melpa/")
    ;;         ("melpa"  . "http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/")))
    ;; 刷新插件列表
    (unless package-archive-contents
      (package-refresh-contents))
    ;; 自动安装 use-package. 在Emacs 29中已内置故可省略
    (unless (package-installed-p 'use-package)
      (package-install 'use-package))
    ;; 自动安装所有使用 use-package 声明的插件
    (require 'use-package-ensure)
    (setq use-package-always-ensure t)
    ;;========================================
    ;; Emacs 插件管理设置完毕
    ;;========================================


    ;;========================================
    ;; 这段代码放在最后, 加载 Emacs 自动设置的变量
    (if (file-exists-p custom-file) (load-file custom-file))
    ;;========================================

这段代码的第一部分启用了 package.el, 然后通过 package-archives 变量设置了下载插件的网址. 在国内也可以使用清华的软件源. 接下来 package-refresh-contents 刷新了插件列表. 然后我们自动检测 use-package 是否安装, 如果没有安装则自动下载安装. 最后, 我们设置了 use-package-always-ensure 变量为 t, 这样以后我们所有用 use-package 声明的插件都会自动安装.

straight.el 设置示例

straight 需要用 gitgithub 等网站上下载源码. 请再三确认 git 在系统路径上 (尤其是用 msys2 安装的 Windows 用户).

因为 straight.elpackage.el 难以共存, 所以我们必须早早手动禁用内置的 package.el. 这必须修改一个我们平时很少用的文件 .emacs.d/early-init.el. 我们需要在 early-init.el 中加入

  ;; 在执行 init.el 前禁用 package.el
  (setq package-enable-at-startup nil)

接下来, 我们需要在 init.el 中加入以下代码:

  ;; -*- lexical-binding: t; -*-
  ;;========================================
  ;; 把 Emacs 自动添加的代码放到 custom.el 中
  (setq custom-file (expand-file-name "custom.el" user-emacs-directory))
  ;; 使用 straight.el 设置 Emacs 插件管理
  ;;========================================
  (defvar bootstrap-version)
  ;; 修复 Emacs 29 修改了 native-compile 相关变量导致的 bug
  (unless (version<= emacs-version "28.2")
    (setq straight-repository-branch "develop"))
  ;; 以下代码从 straight.el 主页 https://github.com/radian-software/straight.el 上复制
  (let ((bootstrap-file
         (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
        (bootstrap-version 6))
    (unless (file-exists-p bootstrap-file)
      (with-current-buffer
          (url-retrieve-synchronously
           "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
           'silent 'inhibit-cookies)
        (goto-char (point-max))
        (eval-print-last-sexp)))
    (load bootstrap-file nil 'nomessage))

  (straight-use-package 'use-package) ; 用 straight.el 安装 use-package 声明的插件
  (setq straight-use-package-by-default t) ; 自动安装所有插件, 相当于加入 :straight t
  ;;========================================
  ;; Emacs 插件管理设置完毕
  ;;========================================

  ;;========================================
  ;; 这段代码放在最后, 加载 Emacs 自动设置的变量
  (if (file-exists-p custom-file) (load-file custom-file))
  ;;========================================

这里大部分的代码是 straight.elGithub主页上提供的下载与安装 straight 的代码, 然后再用 straight 安装 use-package. 最后我用把 straight-use-package-by-default 变量设为 t, 这是在使用 straight 时进行插件自动安装的设置.

推荐插件

下面我给大家推荐两组非常实用的插件. 在进行好 package.el 或者 straight.el 的设置后 (注意: 不能同时使用), 安装与设置插件只需要把相关的 use-package 代码块复制到 init.el 即可. 而且在两个体系下的代码块基本是通用的.

我们之前也都设置了自动安装插件. 当你第一次执行 init.el 时 (通常是第一次重启 Emacs 的时候), Emacs 会自动检测你在 init.el 中声明的插件是否已经安装, 若没有则通过指定的方法 (package.elstraight.el) 自动下载安装. 如果大家在一台新的机器上使用 Emacs, 把 init.el 文件复制到新机器上就可以直接获得一模一样的使用体验!

你也可以在修改完 init.el 后, 执行 M-x eval-buffer 命令手动加载新加的 use-package 代码块.

在复制代码块中最常见的问题是某个地方在复制的过程中漏了括号. 大家已经发现 elisp 语言中括号是必须配对的. 我们可以在修改 init.el 后手动的用 M-x match-paren 检查括号是否匹配. 如果有不匹配的括号, 那么光标就会跳过没有匹配成功的括号上, 否则这个命令不会用任何效果.

插件组合 1: 更多的帮助信息

我们首先介绍 Which-key + Keycast + Helpful. 安装代码如下

  (use-package which-key
    :custom (which-key-idle-delay 0.5) ; 延迟时间, 以秒为单位
    :config (which-key-mode)) ; 启用 which-key 模式

  (use-package keycast
    :config (keycast-header-line-mode 1)) ; 在标题显示

  (use-package helpful
    :bind
    ;; 重新定向 C-h 开始的命令
    (([remap describe-function] . #'helpful-callable)
     ([remap describe-variable] . #'helpful-variable)
     ([remap describe-key] . #'helpful-key)
     ([remap describe-command] . #'helpful-command)
     ([remap describe-symbol] . #'helpful-symbol)
     ("C-h C-d" . #'helpful-at-point)
     ("C-h F" . #'helpful-function)))

which-key 可以在按下快捷键的时候自动提示你接下来可能的快捷键. 比如按下 C-h, 就会提示接下来你按 v, f, k 等可以查看哪种类型的帮助. 如果把鼠标悬停在选项上也会在浮窗中显示对应命令的帮助.

keycast 则会显示当前你使用的快捷键及对应的命令名. 它有 4 种显示的位置. 代码中我们选择在 headrer-line , 也就是 Emacs 窗口的最上方显示. 如果大家想在别的地方显示, 比如模式栏, 可以把 (keycast-header-line-mode 1) 改成 (keycast-mode-line-mode 1).

这两个插件可以帮助新手快速熟悉 Emacs 的快捷键和命令. 对于老用户来说, 也可以帮你快速熟悉新安装的插件. 我自己平时也是常开的.

helpful 则优化了帮助界面的信息显示, 包括更多有用的信息和高亮.

插件组合 2: 更好的补全界面

在 Emacs 中输入命令或打开文件, 切换缓冲区等等都会用到小缓冲区补全. 第二组插件 Vertico + Marginalia + Orderless 是针对小缓冲区补全的. 代码如下

  (use-package vertico ; 竖式展开小缓冲区
    :custom (verticle-cycle t)
    :config (vertico-mode))

  (use-package marginalia ; 更多信息
    :config (marginalia-mode))

  (use-package orderless ; 乱序补全
    :custom
    (completion-styles '(orderless basic))
    (completion-category-defaults nil)
    (completion-category-overrides '((file (styles partial-completion)))))

vertico 把每个补全选项放在单独的一行, 配合 marginalia 会在每个选项的右边加入更多相关信息.

在小缓冲区中输入时, 我们可以按 Tab 补全当前的输入. 加入 vertico 之后, 我们可以用 C-nC-p 或者上下移动键来选择不同的补全选项. C-nC-p 也是 Emacs 中上下移动光标的快捷键.

最后的 orderless 允许我们在小缓冲区补全时忽略单词的顺序. 例如, 如果我们输入 M-x, 想要匹配 find-file 命令, 在默认情况下必须先输入 find, 再输入 file 才能找到 find-file. 如果你用了 orderless, 则可以通过 file find 找到, 或者部分的单词 fil fin + Tab= 找到.

基本的 CDLaTeX + AucTeX 设置

最后我们提供一个简单可用的 CDLaTeXAucTeX 设置. 大家只要把这段代码复制进 init.el 就可以实现视频五分钟说服你用Emacs写LaTeX中的大部分功能.

  (defun my/latex-hook ()
    (turn-on-cdlatex)
    (turn-on-reftex))
  (use-package cdlatex
    :load-path "lisp/" ; 需要手动从网盘或 https://github.com/cdominik/cdlatex/blob/master/cdlatex.el 下载 cdlatex.el 文件, 并置于 ~/.emacs.d/lisp/ 文件夹下
    ;; 若使用 straight, 注释前一行, 并取消下一行注释:
    ;; :straight (:host github :repo "cdominik/cdlatex" )
    )
  (use-package tex
    :ensure auctex
    ;; 若使用 straight, 注释前一行, 并取消下一行注释:
    ;; :straight auctex
    :custom
    (TeX-parse-self t) ; 自动解析 tex 文件
    (TeX-PDF-mode t)
    (TeX-DVI-via-PDFTeX t)
  :config
    (setq-default TeX-master t) ; 默认询问主文件
    (add-hook 'LaTeX-mode-hook 'my-latex-hook)) ; 加载LaTeX模式设置

这个基本设置不一定能实现 PDF 正向或反向搜索, 因为这取决于操作系统与 PDF 阅读器. 如果你想在不同平台上使用 Emacs 并获得统一的 PDF 体验, 可以考虑使用 PDF-tools (见视频 BV1pg4y1s7Z9).

关于 CDLaTeX 的安装, 要注意的是, 它并不在任何的软件源时. 如果使用 package.el, 你需要手动下载这个文件 (Github链接 或者我网盘里的备份), 并用 :load-path 关键字指定文件的目录. 如果是 Straight, 则需要我们指定 Github 仓库的地址 cdominik/cdlatex.

AucTeX 是通过 (use-package tex) 激活的. 因为包名不统一的问题, 我们要额外加入 :ensure auctex:straight auctex. 其实 Emacs 已经内置了 AucTeX, 但不一定是最新版本, 我们这里的 use-package 则会把它更新到最新版.

有用的链接

CDLaTeX 中快速输入数学符号和字体与自定义设置

大家好, 我是小米, 欢迎大家来到我的省时省力写 LaTeX 系列. 本期我们开始介绍 Emacs 的 CDLaTeX 插件. 这次讲解如何使用 CDLaTeX 快速插入数学字母, 符号和字体的功能, 以及如何自定义新的快捷键.

AucTeX 和 CDLaTeX 基本设置

Emacs 中的 LaTeX 编辑主要是依赖 AucTeXCDLaTeX 这两个插件. AucTeX 提供了编辑 LaTeX 的基本功能, 而 CDLaTeX 主要提供了大量简化和易设置的输入方式. 为了安装并在 LaTeX 编辑时启用这两个插件, 我们需要在 init.el 中加入代码:

  (defun my/latex-hook ()
    (turn-on-cdlatex)
    (turn-on-reftex))

  (use-package tex
    :ensure auctex
    ;; 若使用 straight, 注释前一行, 并取消下一行注释:
    ;; :straight auctex
    :custom
    (TeX-parse-self t) ; 自动解析 tex 文件
    (TeX-PDF-mode t)
    (TeX-DVI-via-PDFTeX t)
    :config
    (setq-default TeX-master t) ; 默认询问主文件
    (add-hook 'LaTeX-mode-hook 'my/latex-hook)) ; 加载LaTeX模式钩子

  (use-package cdlatex
    :after tex ; 保证 cdlatex 在 auctex 之后加载
    :load-path "lisp/" ; 需要手动从网盘或 https://github.com/cdominik/cdlatex/blob/master/cdlatex.el 下载 cdlatex.el 文件, 并置于 ~/.emacs.d/lisp/ 文件夹下
    ;; 若使用 straight, 注释前一行, 并取消下一行注释:
    ;; :straight (:host github :repo "cdominik/cdlatex" )
    )

使用 straight.el 的用户需要根据注释内容适当调整. 在 (use-package cdlatex ...) 中我们指定了 :after tex, 是为了保证 cdlateXauctex 之后加载. 把 cdlatexuse-package 代码块置于 auctex 之后也实现了相同效果; 而加了这一行后, 代码块次序可以随意调整.

LaTeX-mode-hook 是我们打开 LaTeX 文件时需要加载的设置, 这里我们定义了一个新的函数 my/latex-hook (名字可随意), 这样方便我们日后加入更多的功能. 函数的第一行 (turn-on-cdlatex) 就是打开 tex 文件时加载 cdlatex-mode 的命令.

成功设置后, 当我们打开 tex 文件时, 大家应当可以看到模式栏中的 LaTeX/PCDL, 就表示加载了 aucteXcdlatex. 通过 C-h m (m 表示 mode) 可以查看当前加载的所有主要模式和次要模式.

数学符号输入

这里的数学符号也包括各种非拉丁字母如 \alpha, \aleph 等. 输入方法是用反引号 (Tab 上方) 加另一个键组成的快捷键输入.

插入希腊字母

希腊字母可以用 ` + 对应拉丁字母插入, 包括大小写. 例如

  • ` + a: \alpha
  • ` + b: \beta
  • ` + g: \gamma
  • ` + G: \Gamma
  • ` + S: \Sigma

如果你不熟悉希腊字母对应的拉丁字母, 没有关系, 只要在按下 ` 后稍稍停顿, 就会弹出一个提示界面. 大家刚开始使用时可以多查看这个提示界面.

数学符号

大家在提示界面可以看到, 除了希腊字母以外, 我们还可以用同样的方法快速插入数学符号. CDLaTeX 预置了很多好记的默认设置. 例如, ` + 8 插入 \infty, 因为数字 8 放平就是无穷, 又如, ` + * 插入 \times 乘号, ` + + 插入 \cup (并集), ` + > 插入 \rightarrow (右箭头) 等.

第二和第三层目录

CDLaTeX 中连续按下两次反引号 ` 可以打开第二层目录. 第二层通常用于希腊字母的变体, 如

  • `e 插入 \epsilon, ``e 插入 \varepsilon
  • `r 插入 \rho, ``r 插入 \varrho

又或者是一些类似的符号, 如

  • `> 插入 \rightarrow, ``> 插入 \longrightarrow

或者是多个符号最直观的快捷键相同, 但是频率最高的放在第一层, 频率低的放在第二层, 如

  • `d 插入 \delta, ``d 插入 \partial (求偏导符号)

这个目录还有第 3 层, 这里绑定的快捷键就更少了. 默认的是一些数学函数的符号, 如 \sin, \exp

如何插入 LaTeX 左双引号 ``

反引号在 LaTeX 中写作几乎不会用到, 除了用于左双引号 `` (laTeX 的右双引号是 '' ). 这很好解决: 在 AucTeX 默认设置下, 第一个输入的双引号 " 会自动转换成为 `` 插入, 第二个输入的双引号 " 会转换为 '' . 例如, “word” 将插入 ``word''.

当然, 你也可以把反引号修改成其它的键, 但是既然无须担心双引号输入的问题, 我觉得改的意义不大. 反引号已经是很好的选择.

自定义数学符号快捷键

Emacs 的最大优势就是我们可以自由地设置. 前面反引号 ` 触发的快捷输入, 我们也可以添加自己需要的符号或调整已有的设置.

这里的所有设置保存在一个叫 cdlatex-math-symbol-alist 的变量中. 我们接下来讲解在 Emacs 如何设置一个变量, 保存设置以及加载设置. 这对其它的变量也是一样.

打开设置界面

虽然所有的变量设置都可以通过 init.el 里面的 (setq ...) 语句完成, 对于 cdlatex-math-symbol-alist 这种结构非常复杂的变量, 新手还是建议用 Emacs 自带的设置界面.

打开一个变量的设置界面主要有两种方式 (以 cdlatex-math-symbol-alist 为例)

  1. 通过 customize-variable 命令: M-x customize-variable RET M-x cdlatex-math-symbol-alist
  2. 从变量的帮助界面进入设置界面: C-h v cdlatex-math-symbol-alist 并点击 customize

设置实例

我们想调换 `e``e 原本的快捷键设置, 即实现如下效果: `e 插入 \varepsilon, ``e 插入 \epsilon. (这么做的原因是 \varepsilon 更常用).

  1. 打开 cdlatex-math-symbol-alist 的设置界面
  2. 点击 INS 插入一个新条目
  3. character 后输入 e
  4. Repeat 后按 INS, 新插入的一行输入 \varepsilon
  5. Repeat 后按 INS, 新插入的一行输入 \epsilon

这就完成了基本设置. 如果大家想绑定 ```e````e 等, 只需要再加入新的行以及你需要的 LaTeX 宏命令即可.

这里因为 `e 已经在 CDLaTeX 的默认设置中, 所以我们是覆盖了原有设置. 你可以在一开始的按下 ` 的提示界面中看到默认设置, 或者通过查看变量 cdlatex-math-symbol-alist-default.

保存与加载设置

设置完毕我们会点击 Apply and Save.

  • Apply: 改变了当前 cdlatex-math-symbol-alist 的値, 重启 Emacs 后失效
  • Save: 保存设置, 重启后仍生效.

但是已经打开的 tex 文件是看不到更新的设置的. 想要重新加载 CDLaTeX 的设置. 这有 3 种方法:

  1. 重启 Emacs
  2. 一个是打开新的 tex 文件
  3. 在原来的 .tex 文件缓冲区, 按下 C-c C-n.

第三种方法可以刷新 LaTeX 模式设置, 也适用于其它与 cdlatex 的设置. 此时, 大家按下反引号 ` 就可以看到更新后的列表了.

怎么选择快捷键

原则上这个机制可以插入任意的数学表达式, 如 \stackrel{\mathrm{a.s.}}{=}=, 但是建议只绑定原子化的数学符号. 复杂的表达式更适合用 CDLaTeX 的命令补全功能. (参考 Tab 补全快速插入LaTeX代码)

快捷键要易记, 直观, 凭你的第一感觉就能找到. 否则不能提高输入速度. 大家也可以查看默认的设置寻找灵感. 反例就是把左箭头 \leftarrow 绑到 `> 上.

如果一个键上绑定了多层快捷键, 要考虑不同命令使用的频率, 把最常用的放在第一层, 次常用的放在第二层, 依此类推. 像上面的 \epsilon\varepsilon 的例子.

你也可以绑定自己定义的宏命令. 例如, 我的 `e 绑定的是 \eps, 而在我的 LaTeX 文档引言区中会定义 \newcommand{\eps}{\varepsilon}. 这样的好处可以提高代码的可读性, 方便交流. 毕竟你的导师, 你的合作者未必用 Emacs, 长长的 \varepsilon 会让人眼花. 但是我输入时想到的是希腊字母 epsilon 就应该用 `e 输入.

这里有很大的发挥空间, 因为第二层和第三层基本都是空的, 每个键还分大小写, 可以自由设置 100 多个快捷键. 所以尽情发挥吧.

数学字体修饰

数学字体

CDLaTeX 还可以快速插入不同的数学字体, 像 \mathrm{}, \mathbf{} 等等. 例如, 我们常常用粗体 R 表示实数域, 也就是 \mathbf{R}. 我们可以按 3 个键完成输入: R + ' + b

  • R: 输入字母 R
  • ' (单引号): 打开数学字体列表. 作用相当于前面的 `
  • b: 在字母 R 外面插入表示粗体的 LaTeX 宏命令 \mathbf{}

按单引号 ' 默认会改变前一个字母的字体, 也包括希腊字母, 但只是前面一个字母. 例:

  • `a'b 插入 \mathbf{\alpha}
  • ab'b 插入 a\mathbf{b}.

如果需要改变多个字母的字体可以先选择字体, 再输入文本. 这就是第二种方法. 但是输入单引号时前面要是空格或者 $, { 这种功能性字符. 例:

  • $\'babc 插入 `$\mathbf{abc}$`.
  • $a\'bc 插入 `$\mathbf{a}c$`.

可以用于改变多个字符的字体.

其它修饰

这种插入方式也可以推广到一切 LaTeX 宏命令 + 一对花括号内一段文本的结构. 除了像 \mathbf{}, \mathrm{} 这种数学字体, 还可以输入

  • 数学公式中对字母的其它修饰, 如

    • '> 插入 \vec{}
    • '^ 插入 \hat{}
    • '- 插入 \bar{}

    这里默认的快捷捷非常直观, 大家也可以按下单引号 ' 稍等以查看提示界面.

  • 非数学公式中的文本字体, 如

    • 'b 插入 \textbf{}
    • 'i 插入 \textit{}

这里同样的 'b, 用在数学公式内就是 \mathbf{}, 用在文本中就是 \textbf{}. CDLaTeX 会自动检测当前环境是否为数学环境.

嵌套修饰

触发字体修饰的第三种方法是选先高亮选中一段文本, 再选择修饰. 例如, 选中数学环境外的 blabla, 然后按 ‘b, 则 blabla 会变成 \textbf{blabla}. 如果 blabla 在数学环境内, 则变成 \mathbf{blabla}

第一种方法只能修饰一个字母, 所以嵌套修饰只能使用第二种或第三种方法. 例:

  • '-'bR 插入 \bar{\mathbf{R}}.
  • R'b 插入 \mathbf{R}, 然后高亮选中按下 '-, 变成 \bar{\mathbf{R}}

自定义字体修饰

这里需要设置的变量是 cdlatex-math-modify-alist. 打开设置界面的方法和前面一样, 输入 M-x customize-variable RET cdlatex-math-modify-alist

现在我们举一个例子. 假设我们想用 't 在数学公式中插入空心粗体 \mathbb{}. 操作如下

  • 打开 cdlatex-math-modify-alist 设置界面
  • 点击 INS 新建一个条目
  • 第一行 character 输入 t
  • 第二行: \mathbb
  • 第三行: 保持空白, 因为文本模式下没有空心粗体, 或者输入 \text, 这是 CDLaTeX 的默认设置.
  • 第四行: Type 改成 command. 两种方式几乎等价但是 command 现在更常用.
  • 第五, 第六行: 不变

我们修改完之后, 按 Apply and Save 保存, 然后在 tex 文件缓冲区中用 C-c C-n 刷新设置, 这样我们在数学环境中按下 ' 就能插入空心粗体 \mathbb{} 了.

customize-variable 设置保存位置

我们的 init.el 设置里面有这样两行:

    (setq custom-file (expand-file-name "custom.el" user-emacs-directory))
    ;; .....
    ;; .....
    (if (file-exists-p custom-file) (load-file custom-file))

这样 Emacs 会把通过 customize-variable 设置的变量保存在我们自定义的 custom.el 的文件中. 内容大概像这样:

  (custom-set-variables
   ;; custom-set-variables was added by Custom.
   ;; If you edit it by hand, you could mess it up, so be careful.
   ;; Your init file should contain only one such instance.
   ;; If there is more than one, they won't work right.
   '(cdlatex-math-modify-alist '((116 "\\mathbb" "" t nil nil)))
   '(cdlatex-math-symbol-alist '((101 ("\\varepsilon" "\\epsilon")))))
  ;; ......

这里包含了我们前面对 cdlatex-math-modify-alistcdlatex-math-symbol-alist 的设置.

如果没有特别的设置, customize-variable 设置的变量默认会由 Emacs 保存到 init.el 文件的最后. 我们的设置可以区分自己的设置和 Emacs 保存的设置.

当然, 你也可以手动把 custom-set-variables 中的内容用 (setq ...) 语句写在你的 init.el 当中, 尤其可以放在相应插件的 use-package 代码块中. 这样的好处是方便单独管理每个插件的设置, 并且利用 use-package 的延迟加载功能加快打开 Emacs 的时间. 当我们的 CDLaTeX 设置很长的时候, 这样做可以把 Emacs 的启动时间从 10 多秒减少到 1 秒以下. 大家可以在熟悉了 Emacs 的设置后再做尝试, 新手不推荐这么做.

总结

Emacs 中的 CDLaTeX 插件利用反引号 ` 和单引号 ' 开始的快捷键可以快速插入数学字母, 符号和字体. 我们可以通过设置 cdlatex-math-symbol-alistcdlatex-math-modify-alist 这两个变量修改和增加自己喜欢的快捷键.

在下期视频中我们将介绍 CDLaTeX 中 Tab 的命令/模板补全功能. 它可以帮助我们输入一些更复杂的宏命令, 或者插入环境模板等等.

Tab 补全快速插入 LaTeX 代码

大家好, 我是小米. 本期我们将介绍如何在 CDLaTeX 中用 Tab 补全命令快速地输入复杂的宏命令和环境模板.

Tab 补全插入宏命令

补全原理很简单, 用几个字母组合加 Tab 生成一些复杂的命令. 例如, fr + Tab 就会生成 \frac{}{}, 这里光标会停留在第一个括号内; 在第一个括号内完成输入后, 按 Tab 光标就会跳到下一个括号中. 因此, 输入一个常见的分数 \frac{1}{2} 只需要输入 f + r + Tab + 1 + Tab + 2.

内置命令举例

CDLaTeX 内置了一些可补全的命令, 可以在 cdlatex-command-alist-default 变量中查看 (C-h v). 我们举一些例子 (以下 ? 所在位置表示补全后光标停留的位置.)

  • 分数 fr + Tab = \frac{?}{}, 根号 sq + Tab = \sqrt{?}
  • 空格 qq + Tab = \quad, 大空格 qqq + Tab = \qquad
  • 括号 lr( + Tab = \left(?\right), lr[ = \left[?\right]
  • 章节标题 sn + Tab = \section{?}, ss + Tab = \subsection{?}, sss + Tab = \subsubsubsection{?}

一些自定义例子

  • te + Tab = \text{}
  • se + Tab = \{ \} (set)
  • st + Tab = \stackover{}{}
  • hl + Tab = \hline, hhl + Tab = \\ \hline (表格中常用)
  • big( + Tab = \big(?\big), Big( + Tab = \Big(?\Big), bigg( + Tab = \bigg(?\bigg) (\big, \Big, \bigg 等是 amsmath 中调整括号大小的命令)
  • lr< + Tab = \langle?\rangle, 一对尖括号 \(\langle \rangle\).

显然, 这里的关键字选择都是用命令中最开始的两到三个字母, 这样非常好记, 也很容易使用.

Tab 补全环境模板

大家可以看到这里的 Tab 补全其实就是一个替换字符串的过程. 当然字符串中也可以包括换行, 因此同样的机制也可以输入形如 \begin{XXX} ... \end{XXX} 的环境.

内置命令举例

equation 环境

equ + Tab 插入如下模板:

其中, \label{eq:XXX}CDLaTeX 调用 reftex 自动生成的数字标签.

类似的数学公式环境还有如

  • ali + Tab 插入 align 环境 (自动生成标签), ali* + Tab 插入 align* 环境 (无标签)
  • gat + Tab 插入 gather 环境 (自动生成标签), gat* + Tab 插入 gather* 环境 (无标签)

列表环境

enu + Tab 插入

此时, 在 enumerate 环境中:

  • it + Tab = \item
  • C-<enter> 会换行并生成 \item

这里, enu + Tab 等同于用 cdlatex-environment (C-c { ) 插入 enumerate 环境

类似的还有

  • ite + Tab 插入 itemize 环境
  • fg + Tab 插入 figure 环境

自定义补全命令

现在我们介绍如何自定义你自己需要的补全命令. 默认的补全命令都在 cdlatex-command-alist-default 中, 而现有的所有命令, 包括内置的和自定义的, 都可以通过 C-c ? 查看.

在用 C-c ? 查看时, 我们会在最右一列看到 TEXTMATH 关键字:

  • MATH 关键字表示补全可以在 数学环境 中触发
  • TEXT 关键字表示补全可以在 文本环境 中触发

加入自定义新的补全命令通过修改变量 cdlatex-command-alist. 方法是调用 M-x customize-variable =, 然后输入变量名 =cdlatex-command-alist.

带参数的宏命令

例子: te + Tab 输入 \text{?} (光标停在括号内). 我们需要填入如下参数

  • keyword: te
  • Docstring: 随便填, 只是用于说明的解释性文字, 例如 insert \text{}
  • Replacement: \text{?} (? 表示光标停留的位置)
  • Hook: cdlatex-position-cursor (如果需要指定光标则必填!)
  • Argument: nil (这是上面 hook 的参数)
  • Text Mode: nil, Math mode: t

保存设置 (Apply and Save) 之后, 在已经打开的 tex 文件中用 C-c C-n 可以刷新设置, 就可以开始使用了.

插入匹配的括号

例子: big{ + Tab 插入 \big\{? \big\}

  • keyword: big{
  • Docstring: insert \big\{? \big\}
  • Replacement: \big\{? \big\
  • Hook: cdlatex-position-cursor
  • Argument: nil
  • Text Mode: nil, Math mode: t

这里有两个细节. 第一是我们在 ? 后面手动多加了一个空格, 这里因为在 LaTeX 编辑模式下, 按 Tab 会自动跳到一个空格位置, 因此我们尽量用空格把代码分隔开来, 便于以后的修改; 既然如此, 我们干脆在模板中加入这个空格.

第二个细节时我们的替换字符串最后少了一个 }. 这是因为 CDLaTeX 中默认会自动匹配输入一对括号 {}. 因此我们只需要补全除了右花括号 } 以外的部分就可以. CDLaTeX 中自动匹配的括号可以通过 cdlatex-paired-parens 设置, 只针对 $([{<| 6 个字符. 我一般会自动匹配 $([{ . 这里大家只需要注意你在 cdlatex-command-alist 中的设置与 cdlatex-paired-parens 保持一致就可以了.

插入环境

例子: case + Tab 插入

  • keyword: case
  • Docstring: insert \begin{cases} \end{cases}
  • Replacement: 输入框内用 C-j 换行, 然后正常输入需要替换的文本即可
  • Hook: cdlatex-position-cursor
  • Argument: nil
  • Text Mode: nil, Math mode: t

插入环境 II

插入环境除了直接在 cdlatex-command-alistReplacement 中写入环境模板以外, 还可以通过调用函数 cdlatex-environment 的方式实现. 在 LaTeX 编辑模式中, 有两种用环境名插入环境的方法

  • M-x LaTeX-environment (C-c C-e) + description: 这会调用 AucTeX 的环境模板
  • M-x cdlatex-environment (C-c { ) + description: 这会调用 CDLaTeX 的环境模板.

两种模板略有不同. 这第二种插入环境的方法就是用 Tab 补全触发第二个命令.

例子: des + Tab 插入 description 环境

  • keyword: des
  • Docstring: insert \begin{description} \end{description}
  • Replacement: nil
  • Hook: cdlatex-environment
  • Argument: ("description")
  • Text Mode: t, Math mode: nil

这里需要注意的是我们用了一个不同的 hook! 所插入的模板是由 cdlatex-env-alist, cdlatex-env-alist-default 控制的.

使用这种方式插入环境的好处:

  • 支持自动插入标签: AUTOLABEL 关键字 (equ + Tab 生成带标签的环境的实现方式)
  • 支持多行环境的 item 模板 (C-<enter> 触发)

不过, 在一般情况下, 第一种方法直接把环境模板写进 cdlatex-command-alist 也能实现大部分的功能了.

我的一些设置分享

我的 cdlatex-command-alist 变量, 仅做抛砖引玉之用.

  (setq cdlatex-command-alist
        '(("eq" "insert pairs of \\[ \\]" "\\[ ? \\]" cdlatex-position-cursor nil t t)
          ("Big(" "insert Big ()" "\\Big( ? \\Big" cdlatex-position-cursor nil nil t)
          ("Big[" "insert Big[" "\\Big[ ? \\Big" cdlatex-position-cursor nil nil t)
          ("Big\\|" "insert Big \\|" "\\Big\\| ? \\Big\\|" cdlatex-position-cursor nil nil t)
          ("Big{" "insert Big{}" "\\Big\\{ ? \\Big\\" cdlatex-position-cursor nil nil t)
          ("Big|" "insert Big|" "\\Big| ? \\Big|" cdlatex-position-cursor nil nil t)
          ("aali" "insert equation" "\\left\\{\\begin{aligned}\n? \n\\end{aligned}\\right." cdlatex-position-cursor nil nil t)
          ("alb" "Insert beamer alert block with overlay" "\\begin{alertblock}<+->{ ? } \n\n\\end{alertblock}" cdlatex-position-cursor nil t nil)
          ("alb*" "Insert beamer alert block without overlay" "\\begin{alertblock}{ ? } \n\n\\end{alertblock}" cdlatex-position-cursor nil t nil)
          ("big(" "insert big ()" "\\big( ? \\big" cdlatex-position-cursor nil nil t)
          ("big[" "insert big []" "\\big[ ? \\big" cdlatex-position-cursor nil nil t)
          ("big\\|" "insert big \\|" "\\big\\| ? \\big\\|" cdlatex-position-cursor nil nil t)
          ("bigg(" "insert bigg()" "\\bigg( ? \\bigg" cdlatex-position-cursor nil nil t)
          ("bigg[" "insert bigg[" "\\bigg[ ? \\bigg" cdlatex-position-cursor nil nil t)
          ("bigg\\|" "insert bigg\\|" "\\bigg\\| ? \\bigg\\|" cdlatex-position-cursor nil nil t)
          ("bigg{" "insert bigg{}" "\\bigg\\{ ? \\bigg\\" cdlatex-position-cursor nil nil t)
          ("bigg|" "insert bigg|" "\\bigg| ? \\bigg|" cdlatex-position-cursor nil nil t)
          ("big{" "insert big {}" "\\big\\{ ? \\big\\" cdlatex-position-cursor nil nil t)
          ("big|" "insert big|" "\\big| ? \\big|" cdlatex-position-cursor nil nil t)
          ("blo" "Insert beamer block with overlay" "\\begin{block}<+->{ ? } \n\n\\end{block}" cdlatex-position-cursor nil t nil)
          ("blo*" "Insert beamer block WITHOUT overlay" "\\begin{block}{ ? } \n\n\\end{block}" cdlatex-position-cursor nil t nil)
          ("bn" "binomial" "\\binom{?}{}" cdlatex-position-cursor nil nil t)
          ("capl" "insert \\bigcap\\limits_{}^{}" "\\bigcap\\limits_{?}^{}" cdlatex-position-cursor nil nil t)
          ("case" "insert cases" "\\begin{cases}\n? & \\\\\n &\n\\end{cases}" cdlatex-position-cursor nil nil t)
          ("cd" "insert cdots" "\\cdots" nil nil t t)
          ("cupl" "insert \\bigcup\\limits_{}^{}" "\\bigcup\\limits_{?}^{}" cdlatex-position-cursor nil nil t)
          ("dd" "insert ddots" "\\ddots" nil nil t t)
          ("def" "insert definition env" "" cdlatex-environment ("definition") t nil)
          ("des" "insert description" "" cdlatex-environment ("description") t nil)
          ("enu*" "insert enu" "\\begin{enumerate}\n\\item ?\n\\end{enumerate}" cdlatex-position-cursor nil t nil)
          ("equ*" "insert unlabel equation" "" cdlatex-environment ("equation*") t nil)
          ("exb" "Insert beamer example block with overlay" "\\begin{exampleblock}<+->{ ? } \n\n\\end{exampleblock}" cdlatex-position-cursor nil t nil)
          ("exb*" "Insert beamer example block without overlay" "\\begin{exampleblock}{ ? } \n\n\\end{exampleblock}" cdlatex-position-cursor nil t nil)
          ("exe" "Insert exercise" "\\begin{exercise}\n? \n\\end{exercise}" cdlatex-position-cursor nil t nil)
          ("fra" "insert frame (for beamer)" "" cdlatex-environment ("frame") t nil)
          ("hhl" "insert \\ \\hline" "\\\\ \\hline" ignore nil t nil)
          ("hl" "insert \\hline" "\\hline" ignore nil t nil)
          ("ipenu" "insert in paragraph enumerate" "" cdlatex-environment ("inparaenum") t nil)
          ("ipite" "insert in paragraph itemize" "" cdlatex-environment ("inparaitem") t nil)
          ("it" "insert \\item" "\\item?" cdlatex-position-cursor nil t nil)
          ("ld" "insert ldots" "\\ldots" nil nil t t)
          ("lem" "insert lemma env" "" cdlatex-environment ("lemma") t nil)
          ("liml" "insert \\lim\\limits_{}" "\\lim\\limits_{?}" cdlatex-position-cursor nil nil t)
          ("lr<" "insert bra-ket" "\\langle ? \\rangle" cdlatex-position-cursor nil nil t)
          ("myenu" "insert in my enumerate for beamer" "" cdlatex-environment ("myenumerate") t nil)
          ("myite" "insert in my itemize for beamer" "" cdlatex-environment ("myitemize") t nil)
          ("ons" "" "\\onslide<?>{ }" cdlatex-position-cursor nil t t)
          ("pa" "insert pause" "\\pause" ignore nil t nil)
          ("pro" "insert proof env" "" cdlatex-environment ("proof") t nil)
          ("prodl" "insert \\prod\\limits_{}^{}" " \\prod\\limits_{?}^{}" cdlatex-position-cursor nil nil t)
          ("prop" "insert proposition" "" cdlatex-environment ("proposition") t nil)
          ("se" "insert \\{\\}" "\\{ ? \\}" cdlatex-position-cursor nil nil t)
          ("spl" "insert split" "" cdlatex-environment ("split") nil t)
          ("st" "stackrel" "\\stackrel{?}{}" cdlatex-position-cursor nil nil t)
          ("te" "insert text" "\\text{?}" cdlatex-position-cursor nil nil t)
          ("thm" "insert theorem env" "" cdlatex-environment ("theorem") t nil)
          ("vd" "insert vdots" "\\vdots" nil nil t t)))

RefTeX 管理交叉引用

RefTeX 解决的痛点

你是否遇到过以下困扰:

  • 标签数量过多,不知该如何命名?
  • 标签过短担心重复,过长又难以记忆?
  • 在引用时抓狂不已:在 tex 文件中翻来覆去,只为找到公式并复制标签到引用位置?

RefTeX 让你轻松创建、引用和管理标签,随心所欲,毫无压力。

插入标签

M-x + reftex-label, 或者 lbl + Tab (CDLaTeX 的命令补全) 均可以生成形如 \label{type:XXXX} 的标签.

标签的内容根据标签的类型, 有以下的方式决定.

  • 自动生成的数字标签. 这适用于公式环境 \label{eq:NNN} 或者列表环境 \label{it:NNN}, 这里 NNN 表示自动生成的数字. 它是 RefTeX 在当前文档中找到的可以使用的最小数字.
    • 在写数学证明的过程中, 很多需要引用的公式只是在证明过程中引用, 这时自动编号就剩去了想标签名字的烦恼.
    • 我常配合 C-u C-c C-e 改变环境使用, 把从无标签公式变有标签公式.
  • 根据上下文生成标签: 章节. 例如, \section{First Second Third} 自动生成 \label{sec:first-second-third}
  • 如果以上方法均不适用, 则可以手动输入标签.

引用标签

M-x + reftex-reference, C-c [ 或者 ref + Tab (CDLaTeX 命令补全) 均可以触发引用交叉标签.

  • 第一步: 选择引用的宏命令, 如 \ref, \pageref
  • 第二步: 选择标签类型, 默认类型包括 (? 查看帮助):
    • e: 公式标签 (eq:), 包括 equation, 以及 align, gatheramsmath 定义的数学公式环境
    • i: 列表标签 (it:), 列表环境 item 的标签
    • s: 章节标签 (sec:), f: 图片标签 (fig:), 等等
    • SPC 可以不指定标签类型
  • 第三步: 进入标签选择界面 (? 查看帮助).
    • 移动光标
      • 上下: p / n, Up / Down
      • 上一个/下一个章节标题: C-c C-p / C-c C-n
      • 跳到第 N 个 section: N z
    • 上下文: SPC
    • 刷新标签列表: r
    • 插入单个标签: RET 插入当前标签
    • 插入多个标签: m (+, - ) 进行标记, 再用 RETa 插入. a 可以把标签插入到同一个 \ref{} 命令中
    • 搜索标签: Tab

基本设置

开启 RefTeX

包含在我们一开始的 AucTeX 配置代码中:

(defun my/latex-hook ()
  (turn-on-cdlatex)
  (turn-on-reftex))

(add-hook 'LaTeX-mode-hook 'my/latex-hook)

关闭引用宏命令的询问

默认使用 \ref{} 格式, ref + Tab 直接进入标签选择

(setq reftex-ref-macro-prompt nil)

在标签选择界面, 可以用 vV 去改变引用格式.

magic word 功能

reftex-reference 会识别光标前的文字自动选择标签类型. 例如, equation 后插入标签会默认类型为 e

可以由变量 reftex-guess-label-type 控制

(setq reftex-guess-label-type t) ; 默认值

自定义标签类型和 magic word

可以通过 M-x customize-variable 界面修改 reftex-label-alist

例 1: 新增标签类型 t, 绑定 theorem 环境, 标签以 thm: 开头

  • Environment or \macro: theorem
  • Type specification: t
  • Label prefix string: thm:
  • magic word: Theorem, 定理

当我们选择某个标签类型如 t

  • (默认) 若 reftex-trust-label-prefixnil, 只会用环境名 theorem 来决定标签类型
  • reftex-trust-label-prefixt, 也会用 thm: 来识别标签类型.
(setq reftex-trust-label-prefix t)

例 2: 给 e 类型加入更多的 magic word

  • Type specification: e
  • magic word: 公式
  • 其它 留空

生成标签的方式

可以通过 M-x customize-variable 界面修改 reftex-insert-label-flag

  • Derive label from context: 使用上下文生成标签的类型. 比如我们可以把 t 加进去.
  • Prompt for label string: 插入前在小缓冲区提示.

如果一个类型没有在两个列表中出现, 则使用数字作为标签.

自动打开跟随模式

(setq reftex-label-menu-flags '(t t nil nil t nil t nil)) ; 在标签选择界面

代码总结

;; 为 LaTeX 模式加载 RefTeX
(defun my/latex-hook ()
  (turn-on-cdlatex)
  (turn-on-reftex))

(add-hook 'LaTeX-mode-hook 'my/latex-hook)

(setq reftex-ref-macro-prompt nil)
(setq reftex-guess-label-type t) ; 默认值
(setq reftex-trust-label-prefix t)
(setq reftex-label-menu-flags '(t t nil nil t nil t nil)) ; 标签选择界面跟随界面
;; 以及通过 customize-variable 对 reftex-label-alist 和 reftex-insert-label-flag 的设置

Emacs 最强内置 pdf 阅读器 pdf-tools 简介

使用 pdf-tools 的理由

在用 Emacs 编写 LaTeX 文档的过程中, 你是否…

  • 预览 pdf 需要来回在编辑器和 pdf 阅读器之间切换?
  • pdf 阅读器想实现一些新功能?
  • 想给 pdf 阅读器的常用功能定义新的快捷键?

又或者, 你想用 Emacs 做读书笔记, 需要同时:

  • 输入大量的数学符号
  • 对 pdf 文件进行批注
  • 同步 Emacs 笔记文件和 pdf 文件批注的位置

pdf-tools 可以完美实现这些目标.

pdf-tools 的优点

DocView (Emacs 中内置的 pdf 阅读器) 比较

  • DocView: 不清晰, 阅读效果差, 读取速度慢
  • pdf-tools:
    • 速度快, 图片渲染效果好
    • 正常鼠标操作 + 大量 (可自定义) 快捷键

演示

功能:

  • 基础的 pdf 阅读功能应有尽有, 包括超链接跳转和返回, 展开目录等
  • auctex 配合使用, 支持对编译后 pdf 进行正向/反向搜索
  • pdf 批注, 高亮, 下划线 (可保存在 pdf 文件上)

使用场景

  • 编写 latex 文档
  • 配合 org-noter 在 pdf 上做读书笔记

安装流程

分为两部分

Emacs 包的安装

  • 保证 melpa-stable 在 Emacs 包的列表中 可以通过查看 package-archives 变量进行确认
          (require 'package) ;; Emacs 包管理器
          (setq package-check-signature nil) ;; 如果有签名验证问题, 可以设置不检查签名
          (setq package-archives '(("elpa" . "http://tromey.com/elpa/")
                                   ("melpa-stable" . "https://stable.melpa.org/packages/") ;; 下载 pdf-tools 只需要这个
                                   ("melpa" . "https://melpa.org/packages/")
                                   ("gnu" . "http://elpa.gnu.org/packages/")))
  • M-x package-list-package 打开 Emacs 包的列表
  • C-s pdf-tools 找到 pdf-tools
  • 安装 melpa-stable 版本 (2023.3: melpa 版本仍有 bug)

epdfinfo.exe 的安装

epdfinfo.exe 及其它一些依赖文件 (例如 libpopper-<version>.dll) 可以帮助 Emacs 读取 pdf 文件

两种方法

msys2 安装 epdfinfo

什么是 msys2?

可以将许多开源程序本地化编译为 Windows 程序的平台

优点

  • 软件管理和升级方便
  • Emacs 一些高阶功能依赖的不少开源程序都能在上面下载
  • 其它可以安装的开源软件: Git, Emacs, texlive, gcc, python

步骤

  • msys2 官网上 https://www.msys2.org/ 下载安装程序 msys2-x86_x64-<date>.exe. 默认安装目录为 C:/msys64/.
  • 打开 C:/msys64/mingw64.exe. 会弹出一个命令行终端
  • 在命令行终端中输入
          pacman -S mingw-w64-x86_64-emacs-pdf-tools-server
    以上命令可以在这里找到.
  • 确认并安装所有依赖包.
  • 安装完成后, 你应该能在 C:\msys64\mingw64\bin 中找到 epdfinfo.exe.
  • C:\msys64\mingw64\bin 加入环境变量 PATH

基本配置

启动 pdf-tools

init.el 文件中加入

  (pdf-tools-install)

如果想延迟启动 (如打开 pdf 文件后再启动, 节省 Emacs 启动时间), 可以用下面的代码替换

  (pdf-loader-install)

配合 AucTeX 使用的配置

保持不变的设置

  (setq TeX-PDF-mode t)
  (setq TeX-source-correlate-mode t) ;; 编译后开启正反向搜索
  (setq TeX-source-correlate-method 'synctex) ;; 正反向搜索的执行方式
  (setq TeX-source-correlate-start-server t) ;; 不再询问是否开启服务器以执行反向搜索

使用 Sumatra PDF 的配置

  (setq TeX-view-program-list
   '(("Sumatra PDF" ("\"C:/Program Files/SumatraPDF/SumatraPDF.exe\" -reuse-instance" (mode-io-correlate " -forward-search %b %n ") " %o"))))
  (assq-delete-all (quote output-pdf) TeX-view-program-selection)
  (add-to-list 'TeX-view-program-selection '(output-pdf "Sumatra PDF")

pdf-tools 的配置

  (setq TeX-view-program-selection '((output-pdf "PDF Tools"))) ;; 用pdf-tools 打开 pdf
  (add-hook 'TeX-after-compilation-finished-functions
            #'TeX-revert-document-buffer) ;; 在完成编译后刷新 pdf 文件

操作与个性化: 移动

  • 向下/上小滑动: 鼠标滚轮, C-n / C-p
  • 向下/上大滑动: <space> / S-<space>
  • 向后/前翻页: n / p

我的设置: 尽量把移动绑定在左手 (awsd), 空出右手进行鼠标操作.

          (define-key pdf-view-mode-map
            "d" 'pdf-view-next-page-command) ;; 向后翻页
          (define-key pdf-view-mode-map
            "a" 'pdf-view-previous-page-command) ;; 向前翻页
          (define-key pdf-view-mode-map
            "s" 'pdf-view-scroll-up-or-next-page) ;; 向下滑动
          (define-key pdf-view-mode-map
            "w" 'pdf-view-scroll-down-or-previous-page) ;; 向上滑动

操作与个性化: 批注

  • 高亮: 右键菜单, 或 C-C C-a h (h=highlight)
  • 直线下划线: 右键菜单, 或 C-c C-a u (u=underline)
  • 波浪下划线: 右键菜单, 或 C-c C-a s (s=squiggly)
  • 文字批注: 右键菜单, 或 C-c C-a t (t=text)
  • 删除批注: 右键菜单, 或 C-c C-a D (d=delete)

我的设置:

  (require 'pdf-annot)
  (define-key pdf-annot-minor-mode-map (kbd "C-a a") 'pdf-annot-add-highlight-markup-annotation) ;; 高亮
  (define-key pdf-annot-minor-mode-map (kbd "C-a s") 'pdf-annot-add-squiggly-markup-annotation) ;; 波浪线
  (define-key pdf-annot-minor-mode-map (kbd "C-a u") 'pdf-annot-add-underline-markup-annotation) ;; 下划线
  (define-key pdf-annot-minor-mode-map (kbd "C-a d") 'pdf-annot-delete) ;; 删除

操作与个性化: 文档跳转

  • 展示目录: o
    • 跳到目录位置: <enter> / M-<enter>
  • 关闭目录: q
  • 返回上一个位置: l
  • 跳到下一个位置: r

这里重新绑定常用的返回功能 (小知识: 在 Sumatra PDF 里对应 Alt-<right>)

  (require 'pdf-history)
  (define-key pdf-history-minor-mode-map "b" 'pdf-history-backward)

操作与个性化: 放缩

  • 放大/缩小: + / -
  • 放大到页宽/页高/屏幕: W / H / P
  • 重置: 0

打开 pdf 文件时自动放缩

    (add-hook 'pdf-view-mode-hook 'pdf-view-fit-width-to-window) ;; 自动放大到页宽

其它可能出现的 bug

无法进行高亮/划线等

这可能是安装了 2023 年后 pdf-tools 的版本导致的. 可以从 M-x package-list-package 界面中确认是从 melpa-stable 中安装的

形同 (invalid-function pdf-view-current-page) 的错误信息

这是因为在 28.x 以后的 Emacs 版本中会开启本地化编译 (native compilation), 而 pdf-tools 中有一些语法过时了, 在本地化编译时会报错. 如果这个 bug 不解决的话, 不影响 pdf-tools 的使用, 但是会稍微降低 pdf 渲染的速度.

  • 如何确认你的 Emacs 版本支持本地化编译

C-h v <enter> system-configuration-options <enter> 查询, 如果变量包含字段 --with-native-compilation, 则说明当前版本支持本地化编译

本地化编译后的文件会放在 .emacs.d/eln-cache/ 中, 以 .elc 结尾.

  • 解决方法

如果在上面的目录下已经产生了 pdf-*.elc 文件, 请先删除.

  • 完全禁用本地化编译
           (setq no-native-compile t)
  • 只禁止 pdf-tools 的本地化编译
           (setq native-comp-deferred-compilation-deny-list '(".*pdf.*"))

完整配置:

  (pdf-tools-install)

  (setq native-comp-deferred-compilation-deny-list '(".*pdf.*"))
  (setq TeX-view-program-selection '((output-pdf "PDF Tools"))) ;; 用pdf-tools 打开 pdf
  (add-hook 'TeX-after-compilation-finished-functions
            #'TeX-revert-document-buffer) ;; 在完成编译后刷新 pdf 文件

  (define-key pdf-view-mode-map "d" 'pdf-view-next-page-command) ;; 向后翻页
  (define-key pdf-view-mode-map "a" 'pdf-view-previous-page-command) ;; 向前翻页
  (define-key pdf-view-mode-map "s" 'pdf-view-scroll-up-or-next-page) ;; 向下滑动
  (define-key pdf-view-mode-map "w" 'pdf-view-scroll-down-or-previous-page) ;; 向上滑动

  (require 'pdf-annot)
  (define-key pdf-annot-minor-mode-map (kbd "C-a a") 'pdf-annot-add-highlight-markup-annotation) ;; 高亮
  (define-key pdf-annot-minor-mode-map (kbd "C-a s") 'pdf-annot-add-squiggly-markup-annotation) ;; 波浪线
  (define-key pdf-annot-minor-mode-map (kbd "C-a u") 'pdf-annot-add-underline-markup-annotation) ;; 下划线
  (define-key pdf-annot-minor-mode-map (kbd "C-a d") 'pdf-annot-delete) ;; 删除

  (require 'pdf-history)
  (define-key pdf-history-minor-mode-map "b" 'pdf-history-backward)

  (add-hook 'pdf-view-mode-hook 'pdf-view-fit-width-to-window) ;; 自动放大到页宽

相关资源

使用 pdf-tools 的理由

在用 Emacs 编写 LaTeX 文档的过程中, 你是否…

  • 预览 pdf 需要来回在编辑器和 pdf 阅读器之间切换?
  • pdf 阅读器想实现一些新功能?
  • 想给 pdf 阅读器的常用功能定义新的快捷键?

又或者, 你想用 Emacs 做读书笔记, 需要同时:

  • 输入大量的数学符号
  • 对 pdf 文件进行批注
  • 同步 Emacs 笔记文件和 pdf 文件批注的位置

pdf-tools 可以完美实现这些目标.

pdf-tools 的优点

DocView (Emacs 中内置的 pdf 阅读器) 比较

  • DocView: 不清晰, 阅读效果差, 读取速度慢
  • pdf-tools:
    • 速度快, 图片渲染效果好
    • 正常鼠标操作 + 大量 (可自定义) 快捷键

演示

功能:

  • 基础的 pdf 阅读功能应有尽有, 包括超链接跳转和返回, 展开目录等
  • auctex 配合使用, 支持对编译后 pdf 进行正向/反向搜索
  • pdf 批注, 高亮, 下划线 (可保存在 pdf 文件上)

使用场景

  • 编写 latex 文档
  • 配合 org-noter 在 pdf 上做读书笔记

安装流程

分为两部分

Emacs 包的安装

  • 保证 melpa-stable 在 Emacs 包的列表中 可以通过查看 package-archives 变量进行确认
          (require 'package) ;; Emacs 包管理器
          (setq package-check-signature nil) ;; 如果有签名验证问题, 可以设置不检查签名
          (setq package-archives '(("elpa" . "http://tromey.com/elpa/")
                                   ("melpa-stable" . "https://stable.melpa.org/packages/") ;; 下载 pdf-tools 只需要这个
                                   ("melpa" . "https://melpa.org/packages/")
                                   ("gnu" . "http://elpa.gnu.org/packages/")))
  • M-x package-list-package 打开 Emacs 包的列表
  • C-s pdf-tools 找到 pdf-tools
  • 安装 melpa-stable 版本 (2023.3: melpa 版本仍有 bug)

epdfinfo.exe 的安装

epdfinfo.exe 及其它一些依赖文件 (例如 libpopper-<version>.dll) 可以帮助 Emacs 读取 pdf 文件

两种方法

msys2 安装 epdfinfo

什么是 msys2?

可以将许多开源程序本地化编译为 Windows 程序的平台

优点

  • 软件管理和升级方便
  • Emacs 一些高阶功能依赖的不少开源程序都能在上面下载
  • 其它可以安装的开源软件: Git, Emacs, texlive, gcc, python

步骤

  • msys2 官网上 https://www.msys2.org/ 下载安装程序 msys2-x86_x64-<date>.exe. 默认安装目录为 C:/msys64/.
  • 打开 C:/msys64/mingw64.exe. 会弹出一个命令行终端
  • 在命令行终端中输入
          pacman -S mingw-w64-x86_64-emacs-pdf-tools-server
    以上命令可以在这里找到.
  • 确认并安装所有依赖包.
  • 安装完成后, 你应该能在 C:\msys64\mingw64\bin 中找到 epdfinfo.exe.
  • C:\msys64\mingw64\bin 加入环境变量 PATH

基本配置

启动 pdf-tools

init.el 文件中加入

  (pdf-tools-install)

如果想延迟启动 (如打开 pdf 文件后再启动, 节省 Emacs 启动时间), 可以用下面的代码替换

  (pdf-loader-install)

配合 AucTeX 使用的配置

保持不变的设置

  (setq TeX-PDF-mode t)
  (setq TeX-source-correlate-mode t) ;; 编译后开启正反向搜索
  (setq TeX-source-correlate-method 'synctex) ;; 正反向搜索的执行方式
  (setq TeX-source-correlate-start-server t) ;; 不再询问是否开启服务器以执行反向搜索

使用 Sumatra PDF 的配置

  (setq TeX-view-program-list
   '(("Sumatra PDF" ("\"C:/Program Files/SumatraPDF/SumatraPDF.exe\" -reuse-instance" (mode-io-correlate " -forward-search %b %n ") " %o"))))
  (assq-delete-all (quote output-pdf) TeX-view-program-selection)
  (add-to-list 'TeX-view-program-selection '(output-pdf "Sumatra PDF")

pdf-tools 的配置

  (setq TeX-view-program-selection '((output-pdf "PDF Tools"))) ;; 用pdf-tools 打开 pdf
  (add-hook 'TeX-after-compilation-finished-functions
            #'TeX-revert-document-buffer) ;; 在完成编译后刷新 pdf 文件

操作与个性化: 移动

  • 向下/上小滑动: 鼠标滚轮, C-n / C-p
  • 向下/上大滑动: <space> / S-<space>
  • 向后/前翻页: n / p

我的设置: 尽量把移动绑定在左手 (awsd), 空出右手进行鼠标操作.

          (define-key pdf-view-mode-map
            "d" 'pdf-view-next-page-command) ;; 向后翻页
          (define-key pdf-view-mode-map
            "a" 'pdf-view-previous-page-command) ;; 向前翻页
          (define-key pdf-view-mode-map
            "s" 'pdf-view-scroll-up-or-next-page) ;; 向下滑动
          (define-key pdf-view-mode-map
            "w" 'pdf-view-scroll-down-or-previous-page) ;; 向上滑动

操作与个性化: 批注

  • 高亮: 右键菜单, 或 C-C C-a h (h=highlight)
  • 直线下划线: 右键菜单, 或 C-c C-a u (u=underline)
  • 波浪下划线: 右键菜单, 或 C-c C-a s (s=squiggly)
  • 文字批注: 右键菜单, 或 C-c C-a t (t=text)
  • 删除批注: 右键菜单, 或 C-c C-a D (d=delete)

我的设置:

  (require 'pdf-annot)
  (define-key pdf-annot-minor-mode-map (kbd "C-a a") 'pdf-annot-add-highlight-markup-annotation) ;; 高亮
  (define-key pdf-annot-minor-mode-map (kbd "C-a s") 'pdf-annot-add-squiggly-markup-annotation) ;; 波浪线
  (define-key pdf-annot-minor-mode-map (kbd "C-a u") 'pdf-annot-add-underline-markup-annotation) ;; 下划线
  (define-key pdf-annot-minor-mode-map (kbd "C-a d") 'pdf-annot-delete) ;; 删除

操作与个性化: 文档跳转

  • 展示目录: o
    • 跳到目录位置: <enter> / M-<enter>
  • 关闭目录: q
  • 返回上一个位置: l
  • 跳到下一个位置: r

这里重新绑定常用的返回功能 (小知识: 在 Sumatra PDF 里对应 Alt-<right>)

  (require 'pdf-history)
  (define-key pdf-history-minor-mode-map "b" 'pdf-history-backward)

操作与个性化: 放缩

  • 放大/缩小: + / -
  • 放大到页宽/页高/屏幕: W / H / P
  • 重置: 0

打开 pdf 文件时自动放缩

    (add-hook 'pdf-view-mode-hook 'pdf-view-fit-width-to-window) ;; 自动放大到页宽

其它可能出现的 bug

无法进行高亮/划线等

这可能是安装了 2023 年后 pdf-tools 的版本导致的. 可以从 M-x package-list-package 界面中确认是从 melpa-stable 中安装的

形同 (invalid-function pdf-view-current-page) 的错误信息

这是因为在 28.x 以后的 Emacs 版本中会开启本地化编译 (native compilation), 而 pdf-tools 中有一些语法过时了, 在本地化编译时会报错. 如果这个 bug 不解决的话, 不影响 pdf-tools 的使用, 但是会稍微降低 pdf 渲染的速度.

  • 如何确认你的 Emacs 版本支持本地化编译

C-h v <enter> system-configuration-options <enter> 查询, 如果变量包含字段 --with-native-compilation, 则说明当前版本支持本地化编译

本地化编译后的文件会放在 .emacs.d/eln-cache/ 中, 以 .elc 结尾.

  • 解决方法

如果在上面的目录下已经产生了 pdf-*.elc 文件, 请先删除.

  • 完全禁用本地化编译
           (setq no-native-compile t)
  • 只禁止 pdf-tools 的本地化编译
           (setq native-comp-deferred-compilation-deny-list '(".*pdf.*"))

完整配置:

  (pdf-tools-install)

  (setq native-comp-deferred-compilation-deny-list '(".*pdf.*"))
  (setq TeX-view-program-selection '((output-pdf "PDF Tools"))) ;; 用pdf-tools 打开 pdf
  (add-hook 'TeX-after-compilation-finished-functions
            #'TeX-revert-document-buffer) ;; 在完成编译后刷新 pdf 文件

  (define-key pdf-view-mode-map "d" 'pdf-view-next-page-command) ;; 向后翻页
  (define-key pdf-view-mode-map "a" 'pdf-view-previous-page-command) ;; 向前翻页
  (define-key pdf-view-mode-map "s" 'pdf-view-scroll-up-or-next-page) ;; 向下滑动
  (define-key pdf-view-mode-map "w" 'pdf-view-scroll-down-or-previous-page) ;; 向上滑动

  (require 'pdf-annot)
  (define-key pdf-annot-minor-mode-map (kbd "C-a a") 'pdf-annot-add-highlight-markup-annotation) ;; 高亮
  (define-key pdf-annot-minor-mode-map (kbd "C-a s") 'pdf-annot-add-squiggly-markup-annotation) ;; 波浪线
  (define-key pdf-annot-minor-mode-map (kbd "C-a u") 'pdf-annot-add-underline-markup-annotation) ;; 下划线
  (define-key pdf-annot-minor-mode-map (kbd "C-a d") 'pdf-annot-delete) ;; 删除

  (require 'pdf-history)
  (define-key pdf-history-minor-mode-map "b" 'pdf-history-backward)

  (add-hook 'pdf-view-mode-hook 'pdf-view-fit-width-to-window) ;; 自动放大到页宽

相关资源

如何优雅地预览公式

所见即所得的实现方式

文本编辑中的两个要素

  • 文本本身
  • 文本的格式

pdf 文件预览: 正向与逆向搜索

【Emacs+LaTeX教程】Emacs最强内置pdf阅读功能pdf-tools简介 缺点

  • 需要大屏幕
  • 如果编译错误就无法预览

使用 preview-latex

【教程】LaTeX+Emacs从零开始2-6节:所见即所得之Preview-latex 缺点

  • 需要手动执行编译: 常用键 C-c C-p C-p
  • 代码的可读性不强

使用 prettify-symbols-mode

优点

  • 不需要手动触发
  • 没有编译过程, 不会报错
  • 提高了代码的可读性

基本设置

版本要求

  • Emacs >= 25
  • AucTex >= 13.1.10 (可通过 M-x package-list-package 中查找 auctex 查看)

临时打开

M-x prettify-symbols-mode

init.el 文件设置

  (defun my-latex-hook ()
    (prettify-symbols-mode t))
  (add-hook 'LaTeX-mode-hook 'my-latex-hook)

字体设置

保证 Unicode 数学符号可以正确显示

  (set-fontset-font "fontset-default" 'mathematical "Cambria Math")

自动展开

设置自动展开光标附近的宏命令.

  (setq prettify-symbols-unprettify-at-point t)

tips: 如果只想删除刚输入的一个宏命令, 最快的方法是用 C-/ 撤消, 而不是一个个字符删除.

加入自己的符号

  (require 'tex-mode)
  (defun my/more-prettified-symbols ()
    (mapc (lambda (pair) (cl-pushnew pair tex--prettify-symbols-alist))
          '(("\\Z" . 8484) ;; 大多数人在latex中会用 \Z, \Q, \N, \R 表示数域
            ("\\Q" . 8474)
            ("\\N" . 8469)
            ("\\R" . 8477)
            ("\\eps" . 949)
            ("\\ONE" . #x1D7D9)
            ("\\mathbb{S}" . #x1D54A)
            ("\\PP" . #x2119) ;; 个人需要, 经常要使用P和E的数学字体
            ("\\P" . #x1D5AF )
            ("\\Pp" . #x1D40F)
            ("\\E" . #x1D5A4)
            ("\\Ee" . #x1D404)
            ("\\EE" . #x1D53C )
            ("\\Fc" . #x2131)
            ("\\Nc" . #x1D4A9))))
  (my/more-prettified-symbols)

("<latex 宏命令>" . <unicode 编码>) 加入列表中

个人加入编码的原则

  • 原列表中没有的编码
  • \N , \Z 等大多数人使用的宏命令, 这样可以减少与他人合作的障碍
  • 进一步简化自己的常用命令, 像上面的各种 E, P.

LaTeX 相关设置汇总

  ;; 以下为LaTeX mode相关设置
  (setq-default TeX-master nil) ;; 编译时问询主文件名称
  (setq TeX-parse-selt t) ;; 对新文件自动解析(usepackage, bibliograph, newtheorem等信息)
  ;; PDF正向搜索相关设置
  (setq TeX-PDF-mode t)
  (setq TeX-source-correlate-mode t)
  (setq TeX-source-correlate-method 'synctex)

  (setq TeX-view-program-selection '((output-pdf "PDF Tools"))) ;; 用pdf-tools 打开 pdf
  (add-hook 'TeX-after-compilation-finished-functions
            #'TeX-revert-document-buffer) ;; 在完成编译后刷新 pdf 文件

  ;; 打开TeX文件时应该加载的mode/执行的命令
  (defun my-latex-hook ()
    (turn-on-cdlatex) ;; 加载cdlatex
    (outline-minor-mode) ;; 加载outline mode
    (prettify-symbols-mode t)
    (turn-on-reftex)  ;; 加载reftex
    (outline-hide-body)) ;; 打开文件时只显示章节标题

  (add-hook 'LaTeX-mode-hook 'my-latex-hook)

  (setq prettify-symbols-unprettify-at-point t)
  (set-fontset-font "fontset-default" 'mathematical "Cambria Math")

  (require 'tex-mode)
  (defun my/more-prettified-symbols ()
    (mapc (lambda (pair) (cl-pushnew pair tex--prettify-symbols-alist))
          '(("\\Z" . 8484) ;; 大多数人在latex中会用 \Z, \Q, \N, \R 表示数域
            ("\\Q" . 8474)
            ("\\N" . 8469)
            ("\\R" . 8477)
            ("\\eps" . 949)
            ("\\ONE" . #x1D7D9)
            ("\\mathbb{S}" . #x1D54A)
            ("\\PP" . #x2119) ;; 个人需要, 经常要使用P和E的数学字体
            ("\\P" . #x1D5AF )
            ("\\Pp" . #x1D40F)
            ("\\E" . #x1D5A4)
            ("\\Ee" . #x1D404)
            ("\\EE" . #x1D53C )
            ("\\Fc" . #x2131)
            ("\\Nc" . #x1D4A9))))
  (my/more-prettified-symbols)