Skip to content

Latest commit

 

History

History
4010 lines (3489 loc) · 158 KB

config.org

File metadata and controls

4010 lines (3489 loc) · 158 KB

Doom Emacs Configuration

让我们改变对程序构建的传统态度: 与其想象我们的主要任务是指导计算机做什么, 不如专注于向人们解释我们想让计算机做什么。 @@latex:\mbox{@@— 高德纳@@latex:}@@

基础配置

创建词法绑定可以 (稍微) 加速配置文件的运行。 (更多内容可以查看 这篇博客)

;;; config.el -*- lexical-binding: t; -*-

配置有用且基础的个人信息

(setq user-full-name "GinShio"
      user-mail-address "[email protected]"
      org-directory "~/cyberlive"
      )

显然这可以被 GPG 或其他程序使用。

设置默认值

尝试一下别人的默认值,比如 angrybacon/dotemacs

(setq-default
 delete-by-moving-to-trash t        ; 将文件删除到回收站
 window-combination-resize t        ; 从其他窗口获取新窗口的大小
 x-stretch-cursor t                 ; 将光标拉伸到字形宽度
 )

(setq! undo-limit 104857600         ; 重置撤销限制到 100 MiB
       auto-save-default t          ; 没有人喜欢丢失工作,我也是如此
       truncate-string-ellipsis "" ; Unicode 省略号相比 ascii 更好
                                    ; 同时节省 /宝贵的/ 空间
       password-cache-expiry nil    ; 我能信任我的电脑 ... 或不能?
       ; scroll-preserve-screen-position 'always
                                    ; 不要让 `点' (光标) 跳来跳去
       scroll-margin 2              ; 适当保持一点点边距
       gc-cons-threshold 1073741824
       read-process-output-max 1048576
       which-func-unknown ""
       )

(remove-hook! text-mode #'visual-line-mode)
(add-hook! text-mode #'auto-fill-mode)
(add-hook! window-setup #'toggle-frame-maximized)
                                    ; 设置最大化启动
(global-subword-mode 1)             ; 识别驼峰,而不是傻瓜前进
(global-unset-key (kbd "C-z"))      ; 关闭 "C-z" 最小化
(global-unset-key (kbd "C-s"))      ; 关闭 "C-s" 搜索功能,该功能由 "C-c s s" 替代

(when IS-WINDOWS
  (setq-default buffer-file-coding-system 'utf-8-unix)
  (set-default-coding-systems 'utf-8-unix)
  (prefer-coding-system 'utf-8-unix))
                                    ; 将 Windows 上的编码改为 UTF-8 Unix 换行

(custom-set-variables '(delete-selection-mode t) ;; delete when you select region and modify
                      '(delete-by-moving-to-trash t) ;; delete && move to transh
                      '(inhibit-compacting-font-caches t) ;; don’t compact font caches during GC.
                      '(gc-cons-percentage 1))

(add-hook! prog-mode (lambda () (setq show-trailing-whitespace 1)))
                                    ; 编程模式下让结尾的空白符亮起
(add-hook! prog-mode #'which-function-mode)

定义一个自己的 key leader,或许没什么用

(after! general
  (general-create-definer ginshio/leader :prefix "s-y"))

默认情况下通过自定义界面所做的修改会被添加到 init.el 中。 不过正常的方法是将它们放在 .custom.el 中。

(setq-default custom-file (expand-file-name ".custom.el" doom-user-dir))
(when (file-exists-p custom-file) (load custom-file))

设置一个方便的在 window 之间进行切换的快捷键,我选择与 kDE 的 konsole 保持一致。

(map! :map override-global-map
      "C-S-<left>"   #'windmove-left
      "C-S-<down>"   #'windmove-down
      "C-S-<up>"     #'windmove-up
      "C-S-<right>"  #'windmove-right
      )

Doom 配置

拉取 doom-emacs 仓库的分支

  • emacs-version: 29.1
  • git commit: 03d692f

模组

;;; init.el -*- lexical-binding: t; -*-

;; This file controls what Doom modules are enabled and what order they load
;; in. Remember to run 'doom sync' after modifying it!

;; NOTE Press 'SPC h d h' (or 'C-h d h' for non-vim users) to access Doom's
;;      documentation. There you'll find a link to Doom's Module Index where all
;;      of our modules are listed, including what flags they support.

;; NOTE Move your cursor over a module's name (or its flags) and press 'K' (or
;;      'C-c c k' for non-vim users) to view its documentation. This works on
;;      flags as well (those symbols that start with a plus).
;;
;;      Alternatively, press 'gd' (or 'C-c c d') on a module to browse its
;;      directory (for easy access to its source code).

(doom! :input
       <<doom-input>>

       :completion
       <<doom-completion>>

       :ui
       <<doom-ui>>

       :editor
       <<doom-editor>>

       :emacs
       <<doom-emacs>>

       :term
       <<doom-term>>

       :checkers
       <<doom-checkers>>

       :tools
       <<doom-tools>>

       :os
       <<doom-os>>

       :lang
       <<doom-lang>>

       :email
       <<doom-email>>

       :app
       <<doom-app>>

       :config
       <<doom-config>>
       )

结构

这是一篇文学编程,同时也是 Doom Emacs 的配置文件。Doom 对其支持良好,更多详情 可以通过 literate (文学) 模块了解。

literate
(default +bindings +smartparens)

接口

可以做很多事来增强 Emacs 的功能,。

输入
中日文输入与键盘布局
;;bidi              ; (tfel ot) thgir etirw uoy gnipleh
;;chinese
;;japanese
;;layout            ; auie,ctsrnm is the superior home row
    
补全
或许叫补全有点不合适,不过也就这样了。另外说一下, helmidoivy 以 及 vertico 是功能一致的,生态不同的四个包。
(company            ; the ultimate code completion backend
  +childframe)
;;helm              ; the *other* search engine for love and life
;;ido               ; the other *other* search engine...
;;(ivy              ; a search engine for love and life
;; +icons           ; ... icons are nice
;; +prescient)      ; ... I know what I want(ed)
(vertico +icons)    ; the search engine of the future
    
UI
好不好看就看你这么配置了
;;deft              ; notational velocity for Emacs
doom                ; what makes DOOM look the way it does
doom-dashboard      ; a nifty splash screen for Emacs
;;doom-quit         ; DOOM quit-message prompts when you quit Emacs
;;(emoji
;; +unicode +github); 🙂
hl-todo             ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
;;hydra
;;indent-guides     ; highlighted indent columns
;;(ligatures +extra); ligatures and symbols to make your code pretty again
;;minimap           ; show a map of the code on the side
modeline            ; snazzy, Atom-inspired modeline, plus API
;;nav-flash         ; blink cursor line after big motions
;;neotree           ; a project drawer, like NERDTree for vim
ophints             ; highlight the region an operation acts on
;;(popup            ; tame sudden yet inevitable temporary windows
;; +all             ; catch all popups that start with an asterix
;; +defaults)       ; default popup rules
;;tabs              ; a tab bar for Emacs
treemacs            ; a project drawer, like neotree but cooler
;;unicode           ; extended unicode support for various languages
(vc-gutter +pretty) ; vcs diff in the fringe
vi-tilde-fringe     ; fringe tildes to mark beyond EOB
;;window-selec      ; visually switch windows
workspaces          ; tab emulation, persistence & separate workspaces
;; zen              ; distraction-free coding or writing
    
编辑器
VI VI VI Editor of the Beast
;;(evil +everywhere); come to the dark side, we have cookies
file-templates      ; auto-snippets for empty files
fold                ; (nigh) universal code folding
format              ; automated prettiness
;;god               ; run Emacs commands without modifier keys
;;lispy             ; vim for lisp, for people who don't like vim
multiple-cursors    ; editing in many places at once
;;objed             ; text object editing for the innocent
;;parinfer          ; turn lisp into python, sort of
;;rotate-text       ; cycle region at point between text candidates
snippets            ; my elves. They type so I don't have to
;;word-wrap         ; soft wrapping with language-aware indent
    
Emacs
增强一下吧,不然真的是笔记本了 (其实不是
(dired +icons)      ; making dired pretty [functional]
electric            ; smarter, keyword-based electric-indent
(ibuffer +icons)    ; interactive buffer management
undo                ; persistent, smarter undo for your inevitable mistakes
vc                  ; version-control and Emacs, sitting in a tree
    
终端
也许我应该卸载掉我的 Konsole
;;eshell            ; the elisp shell that works everywhere
;;shell             ; simple shell REPL for Emacs
;;term              ; basic terminal emulator for Emacs
vterm               ; the best terminal emulation in Emacs
    
检测
可以告诉我哪里不对,但我觉得我应该先好好背背单词或者看看 PEP8
syntax              ; tasing you for every semicolon you forget
;;(spell +flyspell) ; tasing you for misspelling mispelling
;;grammar           ; tasing grammar mistake every you make
    
工具
Workflow in Emacs!
;;ansible
biblio              ; Writes a PhD for you (citation needed)
;;collab            ; buffers with friends
(debugger +lsp)     ; FIXME stepping through code, to help you add bugs
;;direnv
;;docker
editorconfig        ; let someone else argue about tabs vs spaces
;;ein               ; tame Jupyter notebooks with emacs
(eval +overlay)     ; run code, run (also, repls)
;;gist              ; interacting with github gists
lookup              ; helps you navigate your code and documentation
(lsp +peek)         ; M-x vscode
(magit              ; a git porcelain for Emacs
 +forge)            ; interface with git forges
make                ; run make tasks from Emacs
;;pass              ; password manager for nerds
;;pdf               ; pdf enhancements
;;prodigy           ; FIXME managing external services & code builders
rgb                 ; creating color strings
;;taskrunner        ; taskrunner for all your projects
;;terraform         ; infrastructure as code
;;tmux              ; an API for interacting with tmux
tree-sitter         ; syntax and parsing, sitting in a tree...
;;upload            ; map local to remote projects via ssh/ftp
    
OS
有个问题,我会用 MAC 吗
(:if IS-MAC macos)  ; improve compatibility with macOS
tty                 ; improve the terminal Emacs experience
    

编程语言支持

最爽的事情就是,我可以在 Emacs 中编写任何语言 (的 Hello World)

;;agda                ; types of types of types of types...
;;beancount           ; mind the GAAP
(cc                   ; C > C++ == 1
 +lsp                 ; smart C but still memory leak
 +tree-sitter)
;;clojure             ; java with a lisp
;;common-lisp         ; if you've seen one lisp, you've seen them all
;;coq                 ; proofs-as-programs
;;crystal             ; ruby at the speed of c
;;csharp              ; unity, .NET, and mono shenanigans
data                  ; config/data formats
;;(dart +flutter)     ; paint ui and not much else
;;dhall
;;elixir              ; erlang done right
;;elm                 ; care for a cup of TEA?
emacs-lisp            ; drown in parentheses
;;erlang              ; an elegant language for a more civilized age
;;ess                 ; emacs speaks statistics
;;factor
;;faust               ; dsp, but you get to keep your soul
;;fortran             ; in FORTRAN, GOD is REAL (unless declared INTEGER)
;;fsharp              ; ML stands for Microsoft's Language
;;fstar               ; (dependent) types and (monadic) effects and Z3
;;gdscript            ; the language you waited for
;;(go +lsp)           ; the hipster dialect
;;(graphql +lsp)      ; Give queries a REST
;;(haskell +lsp)      ; a language that's lazier than I am
;;hy                  ; readability of scheme w/ speed of python
;;idris               ; a language you can depend on
;;json                ; At least it ain't XML
;;(java +lsp)         ; the poster child for carpal tunnel syndrome
;;(javascript +lsp)   ; all(hope(abandon(ye(who(enter(here))))))
;;julia               ; a better, faster MATLAB
;;kotlin              ; a better, slicker Java(Script)
(latex                ; writing papers in Emacs has never been so fun
 +latexmk             ; what else would you use?
 +cdlatex             ; quick maths symbols
 +fold)               ; fold the clutter away nicities
;;lean                ; for folks with too much to prove
;;ledger              ; be audit you can be
;;lua                 ; one-based indices? one-based indices
markdown              ; writing docs for people to ignore
;;nim                 ; python + lisp at the speed of c
;;nix                 ; I hereby declare "nix geht mehr!"
;;ocaml               ; an objective camel
(org                  ; organize your plain life in plain text
 +dragndrop           ; drag & drop files/images into org buffers
 +hugo                ; use Emacs for hugo blogging
 +pandoc)             ; export-with-pandoc support
;;php                 ; perl's insecure younger brother
;;plantuml            ; diagrams for confusing people more
;;purescript          ; javascript, but functional
(python               ; beautiful is better than ugly
 +cython
 +lsp
 +pyenv
 +pyright
 +tree-sitter)
;;qt                  ; the 'cutest' gui framework ever
;;racket              ; a DSL for DSLs
;;raku                ; the artist formerly known as perl6
;;rest                ; Emacs as a REST client
;;rst                 ; ReST in peace
;;(ruby +tree-sitter) ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
(rust                 ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
 +lsp
 +tree-sitter)
;;scala               ; java, but good
(scheme +guile)       ; a fully conniving family of lisps
sh                    ; she sells {ba,z,fi}sh shells on the C xor
;;sml
;;solidity            ; do you need a blockchain? No.
;;swift               ; who asked for emoji variables?
;;terra               ; Earth and Moon in alignment for performance.
;;web                 ; the tubes
;;yaml                ; JSON, but readable
;;zig                 ; C, but simpler

Everything in Emacs

leave Emacs

邮件
说实话,我想用 Thunderbird
;;(mu4e +org +gmail)
;;notmuch
;;(wanderlust +gmail)
    
应用
可以在 Emacs 中上网看新闻。或许我可以用 irc 聊天
;;calendar
;;emms
;;everywhere        ; *leave* Emacs!? You must be joking
;;irc               ; how neckbeards socialize
;;(rss +org)        ; emacs as an RSS reader
;;twitter           ; twitter client https://twitter.com/vnought
    

视觉设置

字体设置

‘Source Code Pro’ 和 ‘Fira Code’ 的效果都很不错,’JetBrains Mono’ 和 ‘IBM Plex Mono’ 或许也不错。还是比较推荐 Mono 字体,等宽看代码舒服。

Unicode 字体为什么不试试 ‘JuliaMono’ 呢?

(setq doom-font (font-spec :family "Source Code Pro" :size 15)
      doom-big-font (font-spec :family "Source Code Pro" :size 30)
      doom-variable-pitch-font (font-spec :family "Source Code Variable" :size 15)
      doom-unicode-font (font-spec :family "JuliaMono")
      doom-serif-font (font-spec :family "Source Serif 4")
      )

不过这都是西文字体,没有考虑过 CJK 用户的感受吗!!在后面的 杂项 中,将详细说一下 CJK 字体的配置。

除了这些字体外,字体 Merriweather 还被用于 nov.el 中,字体 Alegreya 作为衬线比 例字体被用于 Org 文件的 writeroom-mode 中的 =mixed-pitch-mode=​。

主题和 modeline

doom-one 是 Doom 自带的大而全的主题,里面实在太多好看的主题了,干嘛还要自己找。 这里我想在众多我喜欢的主题中,启动时随机选取一款。

(setq doom-theme (let ((themes '(doom-vibrant
                                 doom-fairy-floss
                                 doom-dracula
                                 doom-Iosvkem
                                 doom-moonlight
                                 doom-monokai-pro
                                 doom-tokyo-night)))
                   (elt themes (random (length themes)))))

当然你不喜欢这样,可以直接指定一款。另外,你可以采用快捷键 C-h t 来预览并选择 各个主题(当然是一次性的)。

(setq doom-theme 'doom-vibrant)

设置一下 modeline,比如说图标、文件名称以及彩虹猫 (Nyan cat)!

(after! doom-modeline
  (custom-set-variables '(doom-modeline-buffer-file-name-style 'relative-to-project)
                        '(doom-modeline-major-mode-icon t)
                        '(doom-modeline-modal-icon nil))
  (nyan-mode t))

杂项

相对行号可以很好的知道距离目标行有多远,然后用快捷键 C-u num <UP>ESC num <UP> 到达你想去的行。

(setq display-line-numbers-type 'relative)

我想设置一下更好看的默认缓冲区名称

(setq doom-fallback-buffer-name "► Doom"
      +doom-dashboard-name "► Doom")

再来说说初始化 doom 时,UI 上其实还有很多能做的,比如说关闭丑的不行的 ~menu-bar~​, 设置光标模式,以及 CJK 字体等。

需要说明一下,字体在 GUI 下是有效的,TUI 下使用的应该是终端设置。另外,使用 mono 字体时,CJK 一般是西文字号的 1.2 倍,这样一个 CJK 符号将是西文符号的 2 倍。 比较建议西文字体设置为 5 的倍数,这样得到的 CJK 字符都能是一个整数值。

(defun ginshio/doom-init-ui-misc()
  (menu-bar-mode -1)               ;; disable menu-bar
  (tab-bar-mode -1)                ;; disable tab-bar
  (setq-default cursor-type 'box)  ;; set box style cursor
  (blink-cursor-mode -1)           ;; cursor not blink
  <<doom-dashboard-layout>>
  (if (display-graphic-p)
      (progn
        ;; NOTE: ONLY GUI
        ;; set font
        (dolist (charset '(kana han bopomofo))
          (set-fontset-font (frame-parameter nil 'font) charset
                            (font-spec :family "Source Han Mono SC")))
        (appendq! face-font-rescale-alist
                  '(("Source Han Mono SC" . 1.2)))
        <<doom-image-banner>>
        ;; random banner image from bing.com, NOTE: https://emacs-china.org/t/topic/264/33
        )
    (progn
      ;; NOTE: ONLY TUI
      <<doom-ascii-banner>>
      )))
(add-hook! doom-init-ui #'ginshio/doom-init-ui-misc)

辅助宏

这些是 doom 添加的一些非常有用的宏

  • load! 可以相对于本文件进行外部 .el 文件的加载
  • use-package! 用于配置包
  • add-load-path! 将指定目录添加到 load-path 中,可以让 Emacs 在使用 requireuse-package 时在 load-path 中进行查找
  • map! 用于绑定新的快捷键

允许 CLI 运行 org-babel 程序

在 Org 中有时会写一点代码,Org-Babel 就是各个语言在 Org-mode 中的巴别塔。大家都 可以通过它来直接运行。

但是在配置文件也会有一些代码,如果在 CLI 中执行 doom sync 之类的操作,大量的 代码块输出会直接污染输出。这不能忍!

好在 DOOM 提供了每次运行 CLI 前读取 $DOOMDIR/cli.el 的特性,我们可以不再手动 确认是否运行某个代码块 (org-confirm-babel-evaluate),并且用 org-babel-execute-src-block 来沉默这些代码块,避免污染输出。

;;; cli.el -*- lexical-binding: t; -*-
(setq! org-confirm-babel-evaluate nil)
(advice-add 'org-babel-execute-src-block
            :around #'(lambda (orig-fn &rest args)
                        (quiet! (apply orig-fn args))))

dashboard

Dashboard 是打开 Emacs 的主页,展示命令并不是很有用,移除掉它们!

(remove-hook! '+doom-dashboard-functions #'doom-dashboard-widget-shortmenu)
(add-hook! +doom-dashboard-mode (hide-mode-line-mode 1) (hl-line-mode 1))

其他设置

窗口标题

我更喜欢窗口展示缓冲区的名字,然后是项目文件夹 (如果可用)。

(setq! frame-title-format
      '("%b – Doom Emacs"
        (:eval
         (let ((project-name (projectile-project-name)))
           (unless (string= "-" project-name)
             (format "  -  [%s]" project-name))))))

启动界面

tecosaur 做了一个相当棒的启动画面,心动!但是太复杂了。我只是想简单的在每次重启时 更换 banner,仅此而已。

(setq! fancy-splash-image
       (let ((banners (directory-files (expand-file-name "banners" doom-user-dir)
                                       'full (rx ".png" eos))))
         (elt banners (random (length banners)))))

当然,不要忘记 ASCII banner

(setq! ginshio/+doom-dashbord-ascii-banner
       (split-string (with-output-to-string
                       (call-process "cat" nil standard-output nil
                                     (let ((banners (directory-files (expand-file-name "banners" doom-user-dir)
                                                                     'full (rx ".txt" eos))))
                                       (elt banners (random (length banners))))))
                     "\n" t))
(setq! +doom-dashboard-ascii-banner-fn
       #'(lambda ()
           (mapc (lambda (line)
                   (insert (propertize (+doom-dashboard--center +doom-dashboard--width line)
                                       'face 'doom-dashboard-banner) " ")
                   (insert "\n"))
                 ginshio/+doom-dashbord-ascii-banner)))

以字符串形式抓取源代码块内容

在此配置中,有几处需要以字符串形式抓取源代码块的内容的字符串。我们可以使用 noweb <<replacement>> 表单,但该表单无法使用字符串转义。

我们可以使用 noweb 执行来解决这个问题,并编写一个名为(未导出的) babel 代码块, 以字符串形式抓取另一个命名源代码块的内容。需要注意的是,这种方法目前不能扩展 嵌套的 noweb 引用。

;; (if-let ((block-pos (org-babel-find-named-block name))
;;          (block (org-element-at-point block-pos)))
;;     (format "%S" (string-trim (org-element-property :value block)))
;;   ;; look for :noweb-ref matches
;;   (let (block-contents)
;;     (org-element-cache-map
;;      (lambda (src)
;;        (when (and (not (org-in-commented-heading-p nil src))
;;                   (not (org-in-archived-heading-p nil src))
;;                   (let* ((lang (org-element-property :language src))
;;                          (params
;;                           (apply
;;                            #'org-babel-merge-params
;;                            (append
;;                             (org-with-point-at (org-element-property :begin src)
;;                               (org-babel-params-from-properties lang t))
;;                             (mapcar
;;                              (lambda (h)
;;                                (org-babel-parse-header-arguments h t))
;;                              (cons (org-element-property :parameters src)
;;                                    (org-element-property :header src))))))
;;                          (ref (alist-get :noweb-ref params)))
;;                     (equal ref name)))
;;          (push (org-babel--normalize-body src)
;;                block-contents)))
;;      :granularity 'element
;;      :restrict-elements '(src-block))
;;     (and block-contents
;;          (format "%S"
;;                  (mapconcat
;;                   #'identity
;;                   (nreverse block-contents)
;;                   "\n\n")))))

There we go, that’s all it takes! This can be used via the form <<grab("block-name")>>.

守护进程

守护进程是个好东西,但我不太会用,不过 EmacsWiki 中还是列出了各种方法

加载结构

Doom 通过 packages.el 来安装包,非常简单,只需要 package! 就可以安装。 需要注意,不应该将该文件编译为字节码。

;; -*- no-byte-compile: t; -*-
;;; $DOOMDIR/packages.el

警告: 不要禁用 ~/.emacs.d/core/packages.el 中列出的包。Doom 依赖这些,禁用它们 可能出现严重问题。

  • 从官方的源 MELPA / GNU ELPA / emacsmirror 安装
    (package! some-package)
        
  • 关闭某些包
    (package! some-package :disable t)
        
  • 从 Git Repo 安装
    ;; github
    (package! github-package :recipe (:host github :repo "username/repo"))
    ;; gitlab
    (package! gitlab-package :recipe (:host gitlab :repo "username/repo"))
    ;; other
    (package! other-package :recipe (:host nil :repo "https://example.com/repo"))
        

    如果 repo 仅中只有某个 / 某些文件是你需要的

    (package! some-package
      :recipe (:host github :repo "username/repo"
               :files ("some-file.el" "src/elisp/*.el")))
        

    如果需要指定某个 commit 或某个 branch

    ;; commit
    (package! some-package :pin "abcdefghijk")
    ;; branch
    (package! some-package :recipe (:branch "stable"))
        
  • 使用本地的 repo
    (package! some-package :recipe (:local-repo "/path/to/repo"))
        

工具

Which-key

让快捷键提示变得更快!

(setq which-key-idle-delay 0.5)

Input

String Inflection

变形汽车人! 变形字符串!

(package! string-inflection)
(use-package! string-inflection
  :defer t
  :init
  (map! :leader :prefix ("cS" . "naming convention")
        :desc "cycle" "~" #'string-inflection-all-cycle
        :desc "toggle" "t" #'string-inflection-toggle
        :desc "CamelCase" "c" #'string-inflection-camelcase
        :desc "downCase" "d" #'string-inflection-lower-camelcase
        :desc "kebab-case" "k" #'string-inflection-kebab-case
        :desc "under_score" "u" #'string-inflection-underscore
        :desc "Upper_Score" "_" #'string-inflection-capital-underscore
        :desc "UP_CASE" "U" #'string-inflection-upcase))

hungry delete

一次 backspace 吃掉所有空白符 (当前光标限定)

(package! hungry-delete :recipe (:host github :repo "nflath/hungry-delete"))

只让它应用在编程模式是最好的

(use-package! hungry-delete
  :config
  (setq-default hungry-delete-chars-to-skip " \t\v")
  (add-hook! prog-mode #'hungry-delete-mode))

Dired

emacs 自带的强大文件管理器,和之后提到的 MagitTRAMP 都是 Emacs 的杀手级应用。 还出现了很多增强性的包来增加其能力,不过对我来说,稍微修改一下也就够了。

(after! dired
  (require 'dired-async)
  (define-key! dired-mode-map "RET" #'dired-find-alternate-file)
  (define-key! dired-mode-map "C" #'dired-async-do-copy)
  (define-key! dired-mode-map "H" #'dired-async-do-hardlink)
  (define-key! dired-mode-map "R" #'dired-async-do-rename)
  (define-key! dired-mode-map "S" #'dired-async-do-symlink)
  (define-key! dired-mode-map "n" #'dired-next-marked-file)
  (define-key! dired-mode-map "p" #'dired-prev-marked-file)
  (define-key! dired-mode-map "=" #'ginshio/dired-ediff-files)
  (define-key! dired-mode-map "<mouse-2>" #'dired-mouse-find-file)
  (defun ginshio/dired-ediff-files ()
    "Mark files and ediff in dired mode, you can mark 1, 2 or 3 files and diff.
see: https://oremacs.com/2017/03/18/dired-ediff/"
    (let ((files (dired-get-marked-files)))
      (cond ((= (length files) 0))
            ((= (length files) 1)
             (let ((file1 (nth 0 files))
                   (file2 (read-file-name "file: " (dired-dwim-target-directory))))
               (ediff-files file1 file2)))
            ((= (length files) 2)
             (let ((file1 (nth 0 files)) (file2 (nth 1 files)))
               (ediff-files file1 file2)))
            ((= (length files) 3)
             (let ((file1 (car files)) (file2 (nth 1 files)) (file3 (nth 2 files)))
               (ediff-files3 file1 file2 file3)))
            (t (error "no more than 3 files should be marked")))))
  (define-advice dired-do-print (:override (&optional _))
    "show/hide dotfiles in current dired
see: https://www.emacswiki.org/emacs/DiredOmitMode"
    (cond ((or (not (boundp 'dired-dotfiles-show-p)) dired-dotfiles-show-p)
           (setq-local dired-dotfiles-show-p nil)
           (dired-mark-files-regexp "^\\.")
           (dired-do-kill-lines))
          (t (revert-buffer)
             (setq-local dired-dotfiles-show-p t))))
  (define-advice dired-up-directory (:override (&optional _))
    "goto up directory in this buffer"
    (find-alternate-file ".."))
  (define-advice dired-do-compress-to (:override (&optional _))
    "Compress selected files and directories to an archive."
    (let* ((output (read-file-name "Compress to: "))
           (command-assoc (assoc output dired-compress-files-alist 'string-match))
           (files-str (mapconcat 'identity (dired-get-marked-files t) " ")))
      (when (and command-assoc (not (string= "" files-str)))
        (let ((command (format-spec (cdr command-assoc)
                                    `((?o . ,output)
                                      (?i . ,files-str)))))
          (async-start (lambda () (shell-command command)) nil))))))

Magit

xkcd:1597

这应该是 Emacs 的杀手应用之一了,感谢 Jonas 及其他贡献者。

(after! magit
  <<magit-tweaks>>)

Delta

Delta 是用 rust 实现的 git diff 语法高亮的工具。该作者还将其挂接到了 magit 的 diff 视图上 (默认不会有语法高亮)。不过这需要 delta 二进制文件,在 cargo 安装 显得简单些,不过你也可以选择 GitHub Release

cargo install git-delta

简单地配置它就行

(package! magit-delta :recipe (:host github :repo "dandavison/magit-delta"))
(use-package! magit-delta
  :after magit
  :hook (magit-mode . magit-delta-mode))

但是它现在似乎不太好用。

冲突

在 Emacs 中处理冲突也是不错的体验,或许可以尝试自己制造一点

(defun smerge-repeatedly ()
  "Perform smerge actions again and again"
  (interactive)
  (smerge-mode 1)
  (smerge-transient))
(after! transient
  (transient-define-prefix smerge-transient ()
    [["Move"
      ("n" "next" (lambda () (interactive) (ignore-errors (smerge-next)) (smerge-repeatedly)))
      ("p" "previous" (lambda () (interactive) (ignore-errors (smerge-prev)) (smerge-repeatedly)))]
     ["Keep"
      ("b" "base" (lambda () (interactive) (ignore-errors (smerge-keep-base)) (smerge-repeatedly)))
      ("u" "upper" (lambda () (interactive) (ignore-errors (smerge-keep-upper)) (smerge-repeatedly)))
      ("l" "lower" (lambda () (interactive) (ignore-errors (smerge-keep-lower)) (smerge-repeatedly)))
      ("a" "all" (lambda () (interactive) (ignore-errors (smerge-keep-all)) (smerge-repeatedly)))
      ("RET" "current" (lambda () (interactive) (ignore-errors (smerge-keep-current)) (smerge-repeatedly)))]
     ["Diff"
      ("<" "upper/base" (lambda () (interactive) (ignore-errors (smerge-diff-base-upper)) (smerge-repeatedly)))
      ("=" "upper/lower" (lambda () (interactive) (ignore-errors (smerge-diff-upper-lower)) (smerge-repeatedly)))
      (">" "base/lower" (lambda () (interactive) (ignore-errors (smerge-diff-base-lower)) (smerge-repeatedly)))
      ("R" "refine" (lambda () (interactive) (ignore-errors (smerge-refine)) (smerge-repeatedly)))
      ("E" "ediff" (lambda () (interactive) (ignore-errors (smerge-ediff)) (smerge-repeatedly)))]
     ["Other"
      ("c" "combine" (lambda () (interactive) (ignore-errors (smerge-combine-with-next)) (smerge-repeatedly)))
      ("r" "resolve" (lambda () (interactive) (ignore-errors (smerge-resolve)) (smerge-repeatedly)))
      ("k" "kill current" (lambda () (interactive) (ignore-errors (smerge-kill-current)) (smerge-repeatedly)))
      ("q" "quit" (lambda () (interactive) (smerge-auto-leave)))]]))

Completion

没有补全怎么写代码,尤其是 =Java=​!!!

(after! company
  (setq! company-idle-delay 0.3
         company-minimum-prefix-length 2
         company-show-numbers t)
  ) ;; make aborting less annoying.

现在改进大多来自 先前选项 的历史记录,所以我们改进以下历史记录。

(setq-default history-length 1024
              prescient-history-length 1024)

还有最要紧的事,让待选选项有数字提示,方便直接 M-<num> 选择

(custom-set-variables '(company-show-numbers t))

Vertico

(after! consult
  (set-face-attribute 'consult-file nil :inherit 'consult-buffer)
  (setf (plist-get (alist-get 'perl consult-async-split-styles-alist) :initial) ";"))

LSP

这不是老色批!自从 lsp 普及开始,无论配置什么编辑器都不再复杂了。看了一圈 lsp-mode tutorial 甚至觉得不需要配置什么,估计 doom 也有相应的配置。问题就是,熟 悉配置、操作的问题。

(after! lsp-ui
  (setq lsp-ui-sideline-enable t
        lsp-ui-sideline-show-diagnostics t
        lsp-ui-sideline-show-hover nil
        lsp-ui-sideline-show-code-actions t
        lsp-ui-sideline-update-mode 'line

        lsp-ui-peek-enable t
        lsp-ui-peek-always-show t

        lsp-ui-doc-enable t
        lsp-ui-doc-use-childframe t
        lsp-ui-doc-position 'at-point
        lsp-ui-doc-delay 0.5
        ))

由于使用 tree-sitter 进行上色,那我们就不再需要 lsp 进行代码上色了

(setq! lsp-enable-semantic-highlighting t)

Tree sitter

结构化编辑似乎成为了主流,不过 combobulate 支持的太少了。

(package! combobulate :recipe (:host github :repo "mickeynp/combobulate"))

默认启用 tree-sitter,不要忘记打开折叠功能

(setq! +tree-sitter-hl-enabled-modes '(python-mode rust-mode))

但是 tree-sitter 的高亮似乎有些问题,在这样的 C++ 代码中,会错误的高亮变量中的 关键字。

int main(int argc, char* /* argv */[]) {
    int is_static_test = 10;
    int test_class = 20;
    for (int i = 0; i < argc; ++i) {
        if ( test_class + is_static_test < argc ) {
            return 2;
        }
    }
    if ( is_static_test < argc ) {
        return 1;
    }
}

格式化

格式化代码是一个很重要的事情,但是,我希望还是不要再保存的时候格式化了!这会让代 码变得奇怪,尤其是合作的项目上。当然你可以手动用 +format/buffer 在需要的时候格式 化代码。但我不知道为什么这没什么用。

(appendq! +format-on-save-disabled-modes
          '(c-mode
            c++-mode
            python-mode))

另外,不要让 lsp 污染 format!

(setq +format-with-lsp nil)

TRAMP

关于其他很有用的功能,TRAMP 算一个,它是多协议透明远程访问 (Transparent Remote Access, Multiple Protocol) 工具。简单说这是简单访问其他主机文件系统的方法。

如果你想使用 =ssh-key=​,建议开始使用 ssh config~​,并用 ~sshx: 进行 tramp 连接。

不幸的是,TRAMP 对远程连接时 SHELL 的提示格式很挑剔,尝试使用 bash 并放宽松提示 区域的识别。

(after! tramp
  (setenv "SHELL" "/bin/bash")
  (setq tramp-shell-prompt-pattern
        "\\(?:^\\|
\\)[^]#$%>\n]*#?[]#$%>] *\\(�\\[[0-9;]*[a-zA-Z] *\\)*"))  ;; default + 

VTerm

As good as terminal emulation gets in Emacs

VTerm 的安装相对麻烦一些,需要编译一些依赖。当然对于 Unix 用户,用系统库更加方便!

(setq! vterm-module-cmake-args "-DUSE_SYSTEM_LIBVTERM=yes")

YASnippet

snippets 套娃谁用谁知道!

(setq yas-triggers-in-field t)

Screenshot

让截图变得轻而易举!

(package! screenshot
  :recipe (:host github :repo "tecosaur/screenshot")
  )
(use-package! screenshot
  :defer t
  :config (setq screenshot-upload-fn "upload %s 2>/dev/null"))

作者并没有打算添加 TUI 支持。

UI

Nyan

首先添加一下彩虹猫,这不能忘!

(package! nyan-mode :recipe (:host github :repo "TeMPOraL/nyan-mode"))
(use-package! nyan-mode
  :config
  (setq nyan-animate-nyancat t
        nyan-wavy-trail t
        nyan-cat-face-number 4
        nyan-bar-length 16
        nyan-minimum-window-width 64)
  (add-hook! doom-modeline #'nyan-mode))

Eros

这个包可以修改 emacs lisp 内联执行的视觉效果,让这个结果的前缀更好看一点。

(setq eros-eval-result-prefix "") ; default =>

你可以用 C-x C-e 来对比一下前后变化

(+ 1 1 (* 2 2) 1)
return 2 ** 4

Theme Magic

非常神奇的是你可以在 Emacs 中用现有的 Theme,改变终端的 Theme,且 GUI 和 TUI 都 可用!作者说 Linux 和 Mac 可用,​=Windows Terminal= + WSL 同样适用,压力来到了 纯 Windows 下的 Emacs。

(package! theme-magic)

这个操作使用 =pywal=​,你可以通过仓库安装它,不过最简单的方式就是 =pip=​。

sudo python3 -m pip install pywal

Theme Magic 提供了一个数字界面,尝试从饱和度、色彩差异来有效的选取八个颜色。然而,它 可能会为 light 选择相同的颜色,并不总能够选取最佳颜色。我们可以用 Doom themes 提供的色彩工具来轻松获取合理的配色来生成 light 版本 — 现在就开始!

(use-package! theme-magic
  :defer t
  :after +doom-dashboard
  :config
  (defadvice! theme-magic--auto-extract-16-doom-colors ()
    :override #'theme-magic--auto-extract-16-colors
    (list
     (face-attribute 'default :background)
     (doom-color 'error)
     (doom-color 'success)
     (doom-color 'type)
     (doom-color 'keywords)
     (doom-color 'constants)
     (doom-color 'functions)
     (face-attribute 'default :foreground)
     (face-attribute 'shadow :foreground)
     (doom-blend 'base8 'error 0.1)
     (doom-blend 'base8 'success 0.1)
     (doom-blend 'base8 'type 0.1)
     (doom-blend 'base8 'keywords 0.1)
     (doom-blend 'base8 'constants 0.1)
     (doom-blend 'base8 'functions 0.1)
     (face-attribute 'default :foreground))))

Info 着色

让 info 变得更加绚丽夺目。

(package! info-colors)
(use-package! info-colors
  :after info
  :commands (info-colors-fontify-node)
  :hook (Info-selection . info-colors-fontify-node))

Tabs

如果你想像现代编辑器一样拥有 tabs,或许你可以考虑一下。如果想要 tabs 底下显示 =bar=​,需要开启 x-underline-at-descent-line

(after! centaur-tabs
  (setq! centaur-tabs-style "bar"
         centaur-tabs-set-icons t
         centaur-tabs-plain-icons nil
         centaur-tabs-set-modified-marker t
         centaur-tabs-show-navigation-buttons nil
         centaur-tabs-gray-out-icons 'buffer
         centaur-tabs-set-bar 'under
         x-underline-at-descent-line t
         centaur-tabs-label-fixed-length 9)
  (defun centaur-tabs-hide-tab (x)
    "Do no to show buffer X in tabs."
    (let ((name (format "%s" x)))
      (or
       ;; Current window is not dedicated window.
       (window-dedicated-p (selected-window))
       ;; Buffer name not match below blacklist.
       (string-prefix-p "*epc" name)
       (string-prefix-p "*helm" name)
       (string-prefix-p "*Helm" name)
       (string-prefix-p "*Compile-Log*" name)
       (string-prefix-p "*lsp" name)
       (string-prefix-p "*company" name)
       (string-prefix-p "*Flycheck" name)
       (string-prefix-p "*tramp" name)
       (string-prefix-p " *Mini" name)
       (string-prefix-p "*help" name)
       (string-prefix-p "*straight" name)
       (string-prefix-p " *temp" name)
       (string-prefix-p "*Help" name)
       (string-prefix-p "*mybuf" name)
       (string-prefix-p "► Doom" name)
       ;; Is not magit buffer.
       (and (string-prefix-p "magit" name)
            (not (file-name-extension name)))
       )))
  (centaur-tabs-group-by-projectile-project)
  (centaur-tabs-mode t))

但是别忘了,需要在配置文件接口中开启 tabs 选项。还不能忘记添加快捷键

(map! :map ctl-x-map
      :prefix ("t" . "Tab and Treemacs")
      "a"   #'centaur-tabs-select-beg-tab
      "e"   #'centaur-tabs-select-end-tab
      "f"   #'centaur-tabs-forward-tab
      "F"   #'centaur-tabs-forward-group
      "b"   #'centaur-tabs-backward-tab
      "B"   #'centaur-tabs-backward-group
      "g"   #'centaur-tabs-switch-group
      "G"   #'centaur-tabs-toggle-groups
      "l"   #'centaur-tabs-move-current-tab-to-left
      "r"   #'centaur-tabs-move-current-tab-to-right
      "k"   #'centaur-tabs-kill-other-buffers-in-current-group
      "K"   #'centaur-tabs-kill-unmodified-buffers-in-current-group
      "C-5" #'centaur-tabs-extract-window-to-new-frame
      "C-o" #'centaur-tabs-open-in-external-application
      "C-d" #'centaur-tabs-open-directory-in-external-application
      )

Nerd Icons

现在 Doom emacs 使用 =nerd-icons=​。

(after! nerd-icons
  (setcdr (assoc "m" nerd-icons-extension-icon-alist)
          (cdr (assoc "matlab" nerd-icons-extension-icon-alist))))

Treemacs

开启 git-mode=​、​=follow-mode 和 =indent-guide-mode=​,体验还是不错

(after! treemacs
  (setq! doom-themes-treemacs-theme               "doom-colors"
         treemacs-deferred-git-apply-delay        0.5
         treemacs-directory-name-transformer      #'identity
         treemacs-display-in-side-window          t
         treemacs-file-event-delay                1000
         treemacs-file-follow-delay               0.1
         treemacs-file-name-transformer           #'identity
         treemacs-hide-dot-git-directory          t
         treemacs-indent-guide-style              'block
         treemacs-show-hidden-files               t)
  (treemacs-indent-guide-mode t)
  (treemacs-follow-mode t)
  (treemacs-filewatch-mode t)
  (treemacs-fringe-indicator-mode 'always)
  (treemacs-git-mode 'deferred)
  (treemacs-hide-gitignored-files-mode t)
  )

hl todo

hl-todo 允许你设置一些关键字,这些关键字将高亮并且便于查找。往往用于代码注释中 强调某些内容。

(custom-set-variables
 '(hl-todo-keyword-faces '(("NOTE" font-lock-builtin-face bold) ;; needs discussion or further investigation.
                           ("REVIEW" font-lock-keyword-face bold) ;; review was conducted.
                           ("HACK" font-lock-variable-name-face bold) ;; workaround a known problem.
                           ("DEPRECATED" region bold) ;; why it was deprecated and to suggest an alternative.
                           ("XXX+" font-lock-constant-face bold) ;; warn other programmers of problematic or misguiding code.
                           ("TODO" font-lock-function-name-face bold) ;; tasks/features to be done.
                           ("FIXME" font-lock-warning-face bold) ;; problematic or ugly code needing refactoring or cleanup.
                           ("KLUDGE" font-lock-preprocessor-face bold )
                           ("BUG" error bold) ;; a known bug that should be corrected.
                           )))

编程语言配置

文件模板

Snippet 可以很好的帮助我们初始化一些文件。

;;(set-file-template! "\\.org$" :trigger "__" :mode 'org-mode)
(set-file-template! "/LICEN[CS]E$" :trigger '+file-templates/insert-license)

纯文本

我不介意左侧没有任何边距的 buffer,但是一旦剥离行号,buffer 就会感觉有点不对劲。

(defvar +text-mode-left-margin-width 1
  "The `left-margin-width' to be used in `text-mode' buffers.")

(defun +setup-text-mode-left-margin ()
  (when (and (derived-mode-p 'text-mode)
             (eq (current-buffer) ; Check current buffer is active.
                 (window-buffer (frame-selected-window))))
    (setq left-margin-width (if display-line-numbers
                                0 +text-mode-left-margin-width))
    (set-window-buffer (get-buffer-window (current-buffer))
                       (current-buffer))))

现在我们只需要将它连接到所有可能表明条件发生变化或需要重新应用设置的事件。

(add-hook! (window-configuration-change display-line-numbers-mode)
           #'+setup-text-mode-left-margin)
(add-hook! text-mode #'+setup-text-mode-left-margin)

Doom 有一个小问题,因为 doom/toggle-line-numbers 不运行 ~display-line-numbers-mode-hook~​,所以需要一些设置。

(defadvice! +doom/toggle-line-numbers--call-hook-a ()
  :after #'doom/toggle-line-numbers
  (run-hooks 'display-line-numbers-mode-hook))

最后,我想我真的很喜欢这个,我会继续在文本模式下删除行号。

(remove-hook! text-mode #'display-line-numbers-mode)

Org Mode

因为这部分初始化时相当费时,我们需要将其放在 src_elisp{(after! …)} 中。

(after! org
  <<org-conf>>
  )

功能增强

(setq! org-use-property-inheritance t         ; it's convenient to have properties inherited
       org-log-done 'time                     ; having the time a item is done sounds convenient
       org-list-allow-alphabetical t          ; have a. A. a) A) list bullets
       ;; org-export-in-background t             ; run export processes in external emacs process
       org-catch-invisible-edits 'smart       ; try not to accidently do weird stuff in invisible regions
       org-export-with-sub-superscripts '{}   ; don't treat lone _ / ^ as sub/superscripts, require _{} / ^{}
       org-export-allow-bind-keywords t       ; Bind keywords can be handy
       org-image-actual-width '(0.9)          ; Make the in-buffer display closer to the exported result..
       )

I also like the src_elisp{:comments} header-argument, so let’s make that a default.

(setq org-babel-default-header-args
      '((:session . "none")
        (:results . "replace")
        (:exports . "code")
        (:cache . "no")
        (:noweb . "no")
        (:hlines . "no")
        (:tangle . "no")
        (:comments . "link")))

零宽空格

偶尔在用 Org 是你希望将两个分开的块放在一起,这点有点烦人。比如将加​*重*​一个单词 的一部分,或者说在内联源码块之前放一些符号。有一个可以解决的方法 — 零宽空格。 由于这是 Emacs,我们可以为 org-mode 做一个很小的改动将其添加到快捷键上 🙂。

(map! :map org-mode-map
      :leader
      :desc "zero-width-space" "SPC" (cmd! (insert "\u200B")))

目录生成

生成目录的需求并不大,但是像 GitHub 的环境下 TOC 可能成为必要,采用 toc-org 来生成。

(use-package! toc-org
  :defer t
  :after (:any org markdown)
  :config
  (toc-org-mode t)
  (add-hook! (org-mode markdown-mode) #'toc-org-mode)
  (define-key! org-mode-map "C-c C-i" #'toc-org-insert-toc)
  (define-key! markdown-mode-map "C-c M-t" #'toc-org-insert-toc))

toc-org 会清空带有 TOC 标签的 heading,并生成目录。

我不确定真的需要它嘛,因此我将它关闭了。

加密块

org-crypt 可以用 GPG 加密 Org Mode 的某些 heading,当然是带有 crypt 标签的。 现在来设置一下。

(use-package! org-crypt
  :custom
  (org-crypt-key "0xA173AD0063A4E2DFAB4F9EAE65F09D172E677525")
  (org-tags-exclude-from-inheritance '("crypt")) ;; avoid repeated encryption
  :config
  (org-crypt-use-before-save-magic) ;; encrypt when writing back to the hard disk
  (map! :map org-mode-map
        :localleader
        :desc "org-encrypt" "C" nil
        :desc "encrypt current" "C e" #'org-encrypt-entry
        :desc "encrypt all" "C E" #'org-encrypt-entries
        :desc "decrypt current" "C d" #'org-decrypt-entry
        :desc "decrypt all" "C D" #'org-decrypt-entries))

如果想用其他密钥加密,可以设置 cryptkey 属性。

* Totally secret :crypt:
:properties:
:cryptkey: 0x0123456789012345678901234567890123456789
:end:

列表顺序

(setq org-list-demote-modify-bullet '(("+" . "-") ("-" . "+") ("*" . "+") ("1." . "a.")))

引用

;;; (org-cite-global-bibliography '("~/library/ebooks/catalog.bib" "~/library/papers/catalog.bib"))
(after! citar
  (setq! citar-symbol-separator "  "
         citar-bibliography org-cite-global-bibliography
         citar-symbols
           `((file ,(nerd-icons-faicon "nf-fa-file_o" :face 'nerd-icons-green :v-adjust -0.1) . " ")
             (note ,(nerd-icons-octicon "nf-oct-note" :face 'nerd-icons-blue :v-adjust -0.3) . " ")
             (link ,(nerd-icons-octicon "nf-oct-link" :face 'nerd-icons-orange :v-adjust 0.01) . " ")))
  (org-cite-follow-processor 'citar)
  (org-cite-activate-processor 'citar)
  (add-to-list 'citar-major-mode-functions
               '((gfm-mode)
                 (insert-keys . citar-markdown-insert-keys)
                 (insert-citation . citar-markdown-insert-citation)
                 (insert-edit . citar-markdown-insert-edit)
                 (key-at-point . citar-markdown-key-at-point)
                 (citation-at-point . citar-markdown-citation-at-point)
                 (list-keys . citar-markdown-list-keys))))

主要为了引用的灵活性,这里并没有设置全局 bib,如果想在 Org 里引用某些 bib 文件可 以采用以下方法。

#+bibliography: ~/library/ebooks/catalog.bib
#+bibliography: ~/library/papers/catalog.bib

当然这配置很简单,只不过功能很强大,关于 org-citecitar 要学的还有很多。 可以看看 这篇

lsp 支持的源码块

默认情况下,lsp 并不支持应用在 src 块中。

;; Enable LSP in org babel
;; need to add `:file test.xx' in the header
;; https://github.com/emacs-lsp/lsp-mode/issues/377
(cl-defmacro lsp-org-babel-enable (lang)
  "Support LANG in org source code block."
  (setq centaur-lsp 'lsp-mode)
  (cl-check-type lang string)
  (let* ((edit-pre (intern (format "org-babel-edit-prep:%s" lang)))
         (intern-pre (intern (format "lsp--%s" (symbol-name edit-pre)))))
    `(progn
       (defun ,intern-pre (info)
         (let ((file-name (->> info caddr (alist-get :file))))
           (unless file-name
             (setq file-name (make-temp-file "babel-lsp-")))
           (setq buffer-file-name file-name)
           (lsp-deferred)))
       (put ',intern-pre 'function-documentation
            (format "Enable lsp-mode in the buffer of org source block (%s)."
                    (upcase ,lang)))
       (if (fboundp ',edit-pre)
           (advice-add ',edit-pre :after ',intern-pre)
         (progn
           (defun ,edit-pre (info)
             (,intern-pre info))
           (put ',edit-pre 'function-documentation
                (format "Prepare local buffer environment for org source block (%s)."
                        (upcase ,lang))))))))
(defvar org-babel-lang-list
  '("python"))
(dolist (lang org-babel-lang-list)
  (eval `(lsp-org-babel-enable ,lang)))

Agenda

(defvar org-agenda-dir (concat org-directory "/" "agenda"))
(defvar org-agenda-todo-file (expand-file-name "todo.org" org-agenda-dir))
(defvar org-agenda-project-file (expand-file-name "project.org" org-agenda-dir))
(after! org-agenda
  ;;urgancy|soon|as soon as possible|at some point|eventually
  ;;
  (setq! org-agenda-files `(,org-agenda-todo-file
                            ,org-agenda-project-file)
         org-agenda-skip-scheduled-if-done t
         org-agenda-skip-deadline-if-done t
         org-agenda-include-deadlines t
         org-agenda-block-separator nil
         org-agenda-tags-column 100 ;; from testing this seems to be a good value
         org-agenda-compact-blocks t))

Capture

开始设置 Org-capture 模板吧,快速记录!

(after! org-capture
  (defun ginshio/find-project-tree(priority)
    "find or create project headline
https://www.zmonster.me/2018/02/28/org-mode-capture.html"
    (let* ((hl (let ((headlines (org-element-map (org-element-parse-buffer 'headline) 'headline
                                  (lambda (hl) (and (= (org-element-property :level hl) 1)
                                               (org-element-property :title hl))))))
                 (completing-read "Project Name: " headlines))))
      (goto-char (point-min))
      (if (re-search-forward
           (format org-complex-heading-regexp-format (regexp-quote hl)) nil t)
          (goto-char (point-at-bol))
        (progn
          (or (bolp) (insert "\n"))
          (if (/= (point) (point-min)) (org-end-of-subtree))
          (insert (format "* %s :project:%s:\n:properties:\n:homepage: %s\n:repo: \
%s\n:end:\n\n** urgancy :urgancy:\n\n** soon :soon:\n\n** as soon as\
 possible :asap:\n\n** at some point :asp:\n\n** eventually :eventually:\n"
                          hl hl (read-string "homepage: ") (read-string "repo: ")))
          (beginning-of-line 0)
          (org-up-heading-safe))))
    (re-search-forward
     (format org-complex-heading-regexp-format
             (regexp-quote priority))
     (save-excursion (org-end-of-subtree t t)) t)
    (org-end-of-subtree))
  (setq! org-capture-dir (expand-file-name "capture" org-directory)
         org-capture-snippet-file (expand-file-name "snippets.org" org-capture-dir)
         org-capture-comment-file (expand-file-name "comments.org" org-capture-dir)
         org-capture-note-file (expand-file-name "notes.org" org-capture-dir)
         org-capture-blog-file (expand-file-name "blogs.org" org-capture-dir)
         )
  ;; http://www.howardism.org/Technical/Emacs/journaling-org.html
  ;; https://www.zmonster.me/2018/02/28/org-mode-capture.html
  (setq org-capture-templates
        `(("B" "Blog TODO List" entry (file ,org-capture-blog-file)
           "* TODO [#%^{priority|D|A|B|C|E}] %^{blog_title}\n:properties:\n:categories: %^{categories}\n:tags: %^{tags}\n:title: %\\1\n:file_name: %^{file_name}\n:end:\n%?"
           :empty-lines 1)
          ("c" "Comment")
          ("cb" "Book" entry (file+weektree ,org-capture-comment-file)
           "* %^{book} :book:%\\1:\n%?" :empty-lines 1)
          ("cm" "Movie" entry (file+weektree ,org-capture-comment-file)
           "* %^{movie} :movie:%\\1:\n%?" :empty-lines 1)
          ("g" "GTD")
          ("gt" "Todo" entry (file+headline org-agenda-todo-file "Personal")
           "* TODO [#%^{priority|A|B|C|D|E}] %^{task}\n  SCHEDULED: %^T DEADLINE: %^T\n:properties:\n:end:\n%?"
           :empty-lines 1)
          ("gi" "Interview" entry (file+headline ,org-agenda-todo-file "Interview")
           "* WAIT [#%^{priority|B|A|C|D}] %^{company} - %^{position}\t:%\\2:\nSCHEDULED: %^T DEADLINE: %^T\n:properties:\n:url: %^{link}\n:end:\n%?"
           :prepend t :empty-lines 1)
          ("gd" "Daily" entry (file+headline ,org-agenda-todo-file "Daily")
           "* TODO [#%^{priority|C|A|B|D|E}] %^{task}\n SCHEDULED:  %<<%Y-%m-%d %a %H:%M ++1d>>\n:properties:\n:end:\n%?"
           :empty-lines 1)
          ("gw" "Weekly" entry (file+headline ,org-agenda-todo-file "Weekly")
           "* TODO [#%^{priority|B|A|C|D|E}] %^{task}\n SCHEDULED: %<<%Y-%m-%d %a %H:%M ++1w>>\n:properties:\n:end:\n%?"
           :empty-lines 1)
          ("gm" "Monthly" entry (file+headline ,org-agenda-todo-file "Monthly")
           "* TODO [#%^{priority|C|A|B|D|E}] %^{task}\n SCHEDULED: %<<%Y-%m-%d %a %H:%M ++1m>>\n:properties:\n:end:\n%?"
           :empty-lines 1)
          ("n" "Note")
          ("nc" "Computer" entry (file+headline ,org-capture-note-file "Computer")
           "* %^{heading} %^g\n%?\n" :empty-lines 1)
          ("ne" "Emacs" entry (file+headline ,org-capture-note-file "Emacs")
           "* %^{heading} %^g\n%?\n" :empty-lines 1)
          ("ng" "Game" entry (file+headline ,org-capture-note-file "Game")
           "* %^{heading} %^g\n%?\n" :empty-lines 1)
          ;; ("p" "Project")
          ;; ("pa" "Urgance" entry (file+function ,org-agenda-project-file
          ;;                                      (lambda () (ginshio/find-project-tree "urgancy")))
          ;;  "*** TODO [#A] %^{task}\n SCHEDULED: %<<%Y-%m-%d %a %H:%M>> DEADLINE: %^T\n    :properties:\n    :end:\n%?"
          ;;  :empty-lines 1)
          ;; ("pb" "Soon" entry (file+function ,org-agenda-project-file
          ;;                                   (lambda () (ginshio/find-project-tree "soon")))
          ;;  "*** TODO [#B] %^{task}\n SCHEDULED: %<<%Y-%m-%d %a %H:%M>> DEADLINE: %^T\n    :properties:\n    :end:\n%?"
          ;;  :empty-lines 1)
          ;; ("pc" "As Soon As Possiple" entry (file+function ,org-agenda-project-file
          ;;                                                  (lambda () (ginshio/find-project-tree "as soon as possiple")))
          ;;  "*** TODO [#C] %^{task}\n SCHEDULED: %<<%Y-%m-%d %a %H:%M>> DEADLINE: %^T\n    :properties:\n    :end:\n%?"
          ;;  :empty-lines 1)
          ;; ("pd" "At Some Point" entry (file+function ,org-agenda-project-file
          ;;                                            (lambda () (ginshio/find-project-tree "at some point")))
          ;;  "*** TODO [#D] %^{task}\n SCHEDULED: %<<%Y-%m-%d %a %H:%M>> DEADLINE: %^T\n    :properties:\n    :end:\n%?"
          ;;  :empty-lines 1)
          ;; ("pe" "Eventually" entry (file+function ,org-agenda-project-file
          ;;                                         (lambda () (ginshio/find-project-tree "eventually")))
          ;;  "*** TODO [#E] %^{task}\n SCHEDULED: %<<%Y-%m-%d %a %H:%M>> DEADLINE: %^T\n    :properties:\n    :end:\n%?"
          ;;  :empty-lines 1)
          ("s" "Code Snippet" entry (file ,org-capture-snippet-file)
           "* %^{heading} :code:%\\2:\n:properties:\n:language: %^{language}\n:end:\n\n#+begin_src %\\2\n%?\n#+end_src"
           :empty-lines 1)
          )))

视觉

Org Modern

使 org-mode buffer 尽可能漂亮是很重要的,Minad 的 org-modern 在这方面大有帮助。

(package! org-modern)
(use-package! org-modern
  :hook
  (org-mode . org-modern-mode)
  (org-agenda-finalize . org-modern-agenda)
  :config
  (setq org-modern-star 'replace
        org-modern-replace-stars "♇♆♅♄♃♂♀☿" ;;  "◉○✸✿✤✜◆▶"
        org-modern-table-vertical 1
        org-modern-table-horizontal 0.2
        org-modern-list '((43 . "")
                          (45 . "")
                          (42 . ""))
        org-modern-todo-faces '(("TODO" . (:inherit org-verbatim :weight semi-bold :foreground "white" :background "goldenrod"))
                                ("NEXT" . (:inherit org-verbatim :weight semi-bold :foreground "white" :background "IndianRed1"))
                                ("STRT" . (:inherit org-verbatim :weight semi-bold :foreground "white" :background "OrangeRed"))
                                ("WAIT" . (:inherit org-verbatim :weight semi-bold :foreground "white" :background "coral"))
                                ("KILL" . (:inherit org-verbatim :weight semi-bold :foreground "white" :background "DarkGreen"))
                                ("PROJ" . (:inherit org-verbatim :weight semi-bold :foreground "white" :background "LimeGreen"))
                                ("HOLD" . (:inherit org-verbatim :weight semi-bold :foreground "white" :background "orange"))
                                ("DONE" . (:inherit org-verbatim :weight semi-bold :foreground "black" :background "LightGray")))
        org-modern-footnote (cons nil (cadr org-script-display))
        org-modern-block-fringe nil
        org-modern-block-name '((t . t)
                                ("src" "»" "«")
                                ("example" "»–" "–«")
                                ("quote" "" "")
                                ("export" "" ""))
        org-modern-progress nil
        org-modern-priority nil
        org-modern-horizontal-rule (make-string 36 ?─)
        org-modern-keyword '((t . t)
                             ("title" . "𝙏")
                             ("subtitle" . "𝙩")
                             ("author" . "𝘼")
                             ("email" . "")
                             ("date" . "𝘿")
                             ("property" . "󰠳")
                             ("options" . #("󰘵" 0 1 (display (height 0.75))))
                             ("startup" . "")
                             ("macro" . "𝓜")
                             ("bind" . "󰌷")
                             ("bibliography" . "")
                             ("print_bibliography" . "󰌱")
                             ("cite_export" . "⮭")
                             ("print_glossary" . "󰌱ᴬᶻ")
                             ("glossary_sources" . "󰒻")
                             ("include" . "")
                             ("setupfile" . "")
                             ("html_head" . "🅷")
                             ("html" . "🅗")
                             ("latex_class" . "🄻")
                             ("latex_class_options" . "🄻󰒓")
                             ("latex_header" . "🅻")
                             ("latex_header_extra" . "🅻⁺")
                             ("latex" . "🅛")
                             ("beamer_theme" . "🄱")
                             ("beamer_color_theme" . "🄱󰏘")
                             ("beamer_font_theme" . "🄱𝐀")
                             ("beamer_header" . "🅱")
                             ("beamer" . "🅑")
                             ("attr_latex" . "🄛")
                             ("attr_html" . "🄗")
                             ("attr_org" . "")
                             ("call" . "󰜎")
                             ("name" . "")
                             ("header" . "")
                             ("caption" . "")
                             ("results" . "🠶"))
        org-auto-align-tags nil
        org-tags-column 0
        org-catch-invisible-edits 'show-and-error
        org-special-ctrl-a/e t
        org-hide-emphasis-markers t
        org-agenda-tags-column 0
        org-agenda-block-separator ?─
        org-agenda-time-grid '((daily today require-timed)
                               (800 1000 1200 1400 1600 1800 2000)
                               " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄")
        org-agenda-current-time-string "⭠ now ─────────────────────────────────────────────────"
        )
  (custom-set-variables '(org-modern-statistics :inherit org-checkbox-statistics-todo)))

强调标记

虽然 org-hide-emphasis-markers 非常好,但有时它会使边界处的编辑变得更加繁琐。 我们可以使用 org-appear 包在不牺牲视觉便利的情况下改善这种情况。

(package! org-appear
  :recipe (:host github :repo "awth13/org-appear"))
(use-package! org-appear
  :hook (org-mode . org-appear-mode)
  :config
  (setq! org-appear-autoemphasis t
         org-appear-autosubmarkers t
         org-appear-autolinks nil)
  ;; for proper first-time setup, `org-appear--set-elements'
  ;; needs to be run after other hooks have acted.
  (run-at-time nil nil #'org-appear--set-elements))

符号

本身不用加这个包的,不过移除了 doom 配置里的 `(org +pretty)` 的选项,只好自己手 动来做这件事了。

(package! org-fancy-priorities
  :recipe (:host github :repo "harrybournis/org-fancy-priorities"))

更改用于折叠项目的字符也很好 (默认情况下 ),我认为用 更适合指示 「折叠部 分」。并在默认的四个列表中添加一个额外的 org-bullet 。对了,别忘记优先级也要修 改。

(use-package! org-fancy-priorities
  :ensure t
  :hook (org-mode . org-fancy-priorities-mode)
  :custom (org-lowest-priority ?E)
  :config (setq! org-fancy-priorities-list '("" "" "" "" "")))
(setq! org-ellipsis ""
       org-hide-leading-stars t
       org-priority-highest ?A
       org-priority-lowest ?E
       org-priority-faces
       '((?A . 'nerd-icons-red)
         (?B . 'nerd-icons-orange)
         (?C . 'nerd-icons-yellow)
         (?D . 'nerd-icons-green)
         (?E . 'nerd-icons-blue)))
(setq! +ligatures-extra-symbols
          (list :list_property ""
                :em_dash       ""
                :ellipses      ""
                :arrow_right   ""
                :arrow_left    ""
                :arrow_lr      ""
                :properties    ""
                :end           ""
                :priority_a    #("" 0 1 (face nerd-icons-red))
                :priority_b    #("" 0 1 (face nerd-icons-orange))
                :priority_c    #("" 0 1 (face nerd-icons-yellow))
                :priority_d    #("" 0 1 (face nerd-icons-green))
                :priority_e    #("" 0 1 (face nerd-icons-blue))))

(defadvice! +org-init-appearance-h--no-ligatures-a ()
  :after #'+org-init-appearance-h
  (set-ligatures! 'org-mode nil)
  (set-ligatures! 'org-mode
    :list_property "::"
    :em_dash       "---"
    :ellipsis      "..."
    :arrow_right   "->"
    :arrow_left    "<-"
    :arrow_lr      "<->"
    :properties    ":PROPERTIES:"
    :end           ":END:"
    :priority_a    "[#A]"
    :priority_b    "[#B]"
    :priority_c    "[#C]"
    :priority_d    "[#D]"
    :priority_e    "[#E]"))

LaTeX 片段

让公式稍稍好看一点点

(setq! org-highlight-latex-and-related '(native script entities))

理想情况下 org-src-font-lock-fontify-block 不会添加 org-block ,但我 们可以通过添加带有 :inherit default 面来避免整个功能,这将覆盖背景颜色。

检查 org-do-latex-and-related 显示 "latex" 是传递的语言参数,因此我们 可以如上所述覆盖背景。

(require 'org-src)
(add-to-list 'org-src-block-faces '("latex" (:inherit default :extend t)))

比语法高亮的 LaTeX 更好的是 呈现 LaTeX。我们可以使用 org-fragtog 自动执 行此操作。

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

自定义 LaTeX 片段的外观很舒适,这样它们就更适合文本了 — 比如这个 $\sqrt{β^2+3}-∑φ=1^∞ \frac{x^φ-1}{Γ(a)}$​。

(setq! org-preview-latex-default-process 'dvisvgm
       org-preview-latex-process-alist
       '((dvipng :programs ("latex" "dvipng")
          :description "dvi --> png"
          :message "you need to install the programs: latex and dvipng."
          :image-input-type "dvi"
          :image-output-type "png"
          :image-size-adjust (1.0 . 1.0)
          :latex-compiler ("lualatex --shell-escape --output-format=dvi --interaction=nonstopmode --output-directory=%o %f")
          :image-converter ("dvipng -D %D -T tight -bg Transparent -o %O %f"))
         (dvisvgm :programs ("latex" "dvisvgm")
                  :description "dvi --> svg"
                  :message "you need to install the programs: latex and dvisvgm."
                  :use-xcolor t
                  :image-input-type "dvi"
                  :image-output-type "svg"
                  :image-size-adjust (1.7 . 1.5)
                  :latex-compiler ("lualatex --shell-escape --output-format=dvi --interaction=nonstopmode --output-directory=%o %f")
                  :image-converter ("dvisvgm %f --no-specials=bgcolor -n -b min -c %S -o %O"))
         (imagemagick :programs ("latex" "convert")
                      :description "pdf --> png"
                      :message "you need to install the programs: latex and imagemagick."
                      :use-xcolor t
                      :image-input-type "pdf"
                      :image-output-type "png"
                      :image-size-adjust (1.0 . 1.0)
                      :latex-compiler ("lualatex --shell-escape --output-format=pdf --interaction=nonstopmode --output-directory=%o %f")
                      :image-converter ("convert -density %D -trim -antialias %f -quality 100 %O")))
       org-format-latex-header "
\\documentclass{standalone}
[DEFAULT-PACKAGES]
[PACKAGES]
% Custom font
\\usepackage{arev}

<<latex-maths-conveniences>>
")
(plist-put org-format-latex-options :background "Transparent")
(plist-put org-format-latex-options :zoom 0.93) ; Calibrated based on the TeX font and org-buffer font.

顺便设置下背景

(defun +org-refresh-latex-images-previews-h ()
  (dolist (buffer (doom-buffers-in-mode 'org-mode (buffer-list)))
    (with-current-buffer buffer
      (+org--toggle-inline-images-in-subtree (point-min) (point-max) 'refresh)
      (unless (eq org-latex-preview-default-process 'dvisvgm)
        (org-clear-latex-preview (point-min) (point-max))
        (org--latex-preview-region (point-min) (point-max))))))

(add-hook! doom-load-theme #'+org-refresh-latex-images-previews-h)

字体化内联 src 块

Org 使用 #+begin_src 块做了一些事情,比如在幕后使用 font-lock 作为语言的主要模 式并拉出良好的彩色结果。相比之下,内联 src_ 块在某种程度上被忽略了。

我不是第一个有这种感觉的人, 幸好他们已经开始在 stackexchange 上开始讨论了。 我打 算直接使用他们的结果,但不幸的是,他们没有执行 true 源代码字体化,而只是将 org-code 应用在内容上。

我们可以做得比这更好!使用 org-src-font-lock-fontify-block 我们可以应用适合语 言的语法高亮。然后,继续到 {{{results(...)}}}=,它可以将 =org-block 应用相应的 规则,然后通过模仿 prettify-symbols-mode 的行为隐藏值包围结构。

(setq! org-inline-src-prettify-results '("" . ""))

Doom 主题的额外字体化问题多于帮助。

(setq! doom-themes-org-fontify-special-tags nil)

Babel

我们需要建立一个通天的巴别塔,以便我们可以在 org-mode 的任意位置编写以及运行任何 编程语言!

<<org-babel-conf>>

LaTeX

如果导出到文件,我们使用定制化的 LaTeX 导出,也就是应用所有关于我们对于 org-mode latex 的定制化设置。(HTML 除外?)

(defadvice! org-babel-execute-latex-export-file (orig-fn body params)
  "Like `org-babel-execute:latex', support org-format-latex-header for all"
  :around #'org-babel-execute:latex
  (if (string-suffix-p ".html" (cdr (assq :file params)))
      (funcall orig-fn body params)
    (let ((expanded-body (org-babel-expand-body:latex body params))
          (latex-compiler
           (list :latex-compiler (or (cadar (org-collect-keywords '("latex_compiler"))) org-latex-compiler))))
      (if (cdr (assq :file params))
          (ginshio/org-babel-execute-latex-export-file latex-compiler expanded-body params)
        expanded-body))))

(defun ginshio/org-babel-execute-latex-export-file (latex-compiler body params)
  (let* ((out-file (cdr (assq :file params)))
         (extension (file-name-extension out-file))
         (tex-file (org-babel-temp-file "latex-" ".tex"))
         (border (cdr (assq :border params)))
         (imagemagick (cdr (assq :imagemagick params)))
         (fit (or (cdr (assq :fit params)) border))
         (headers (cdr (assq :headers params))))
    (stringp nil)
    (with-temp-file tex-file
      (require 'ox-latex)
      (insert
       (org-latex--insert-compiler latex-compiler)
       (org-latex-guess-inputenc
        (org-splice-latex-header
         org-format-latex-header
         (delq nil
               (mapcar
                (lambda (el)
                  (unless (and (listp el) (string= "hyperref" (cadr el))) el))
                org-latex-default-packages-alist))
         (append (cdr (assq :packages params)) org-latex-packages-alist)
         nil))
       (if (string= "png" extension)
           (format "%s%s%s%s"
                   (if fit "\n\\usepackage[active,tightpage]{preview}\n" "")
                   (if border (format "\\setlength{\\PreviewBorder}{%s}\n" border) "")
                   (let ((height (and fit (cdr (assq :pdfheight params)))))
                     (if height (format "\\pdfpageheight %s\n" height) ""))
                   (let ((width (and fit (cdr (assq :pdfwidth params)))))
                     (if width (format "\\pdfpageheight %s\n" width) "")))
         "")
       (if headers
           (concat "\n"
                   (if (listp headers)
                       (mapconcat #'identity headers "\n")
                     headers) "\n")
         "")
       (if (and fit (string= "png" extension))
           (concat "\n\\begin{document}\n\\begin{preview}\n" body
                   "\n\\end{preview}\n\\end{document}\n")
         (concat "\n\\begin{document}\n" body "\n\\end{document}\n"))))
    (when (file-exists-p out-file) (delete-file out-file))
    (if (string= "tex" extension)
        (rename-file tex-file out-file)
      (let* ((transient-pdf-file (org-babel-latex-tex-to-pdf tex-file)))
        (cond
         ((string= "pdf" extension)
          (rename-file transient-pdf-file out-file))
         ((and (string= "png" extension) imagemagick)
          (org-babel-latex-convert-pdf
           transient-pdf-file out-file (cdr (assq :iminoptions params)) (cdr (assq :imoutoptions params))))
         ((string= "svg" extension)
          (let* ((log-buf (get-buffer-create "*Org Babel LaTeX Output*"))
                 (err-msg "org babel latex failed")
                 (img-out (org-compile-file
                           transient-pdf-file
                           (list org-babel-latex-pdf-svg-process)
                           extension err-msg log-buf)))
            (rename-file img-out out-file)))
         (t
          (error "Can not create %s files, please specify a .svg, .png or .pdf file or try the :imagemagick header argument"
                 extension)))))))

导出通用设置

默认情况下,Org 仅将前三个级别的标题导出为标题。当使用 article 时,LaTeX 标题 从 \section=​、​\subsection=​、​=\subsubsection= 和 \paragraph=​、 =\subgraph 个级别。HTML5 有六级标题 (<h1><h6>),但第一级 Org 标题被导出为 <h2> 元素 — 剩下 个可用级别。

(setq! org-export-headline-levels 5)
(after! ox
  <<org-export-conf>>
  )

我还将使用 ox-extra 中的一个项目,以便我可以在标题中添加一个 :ignore: 标记以 保留内容,但标题本身会被忽略(​=:noexport:= 它忽略了标题和内容)。当我想使用标题 来提供未出现在最终文档中的写作结构时,这很有用。

(require 'ox-extra)
(ox-extras-activate '(ignore-headlines))

由于我(大致)跟踪 Org ~HEAD~​,因此在创建者字符串中包含 git 版本是有意义的。

(setq! org-export-creator-string
       (format "Emacs %s (Org mode %s%s)" emacs-version (org-release) (org-git-version)))

在导出时,我并不想要零宽度空格,加一个简单的过滤器将其过滤掉。

(defun +org-export-remove-zero-width-space (text _backend _info)
  "Remove zero width spaces from TEXT."
  (unless (org-export-derived-backend-p 'org)
    (replace-regexp-in-string "\u200B" "" text)))
(add-to-list 'org-export-filter-final-output-functions #'+org-export-remove-zero-width-space t)

HTML 导出

(setq! org-html-style-plain org-html-style-default
       org-html-htmlize-output-type 'css
       org-html-doctype "html5"
       org-html-html5-fancy t)
(after! ox-html
  <<ox-html-conf>>
  )
(define-minor-mode org-fancy-html-export-mode
  "Toggle my fabulous org export tweaks. While this mode itself does a little bit,
the vast majority of the change in behaviour comes from switch statements in:
 - `org-html-template-fancier'
 - `org-html--build-meta-info-extended'
 - `org-html-src-block-collapsable'
 - `org-html-block-collapsable'
 - `org-html-table-wrapped'
 - `org-html--format-toc-headline-colapseable'
 - `org-html--toc-text-stripped-leaves'
 - `org-export-html-headline-anchor'"
  :global t
  :init-value t
  (if org-fancy-html-export-mode
      (setq org-html-style-default org-html-style-fancy
            org-html-meta-tags #'org-html-meta-tags-fancy
            org-html-checkbox-type 'html-span)
    (setq org-html-style-default org-html-style-plain
          org-html-meta-tags #'org-html-meta-tags-default
          org-html-checkbox-type 'html)))

额外的 head

在主体的开头添加更多信息,在页眉中添加日期和作者,实现仅 CSS 的亮/暗主题切换,以及一些 Open Graph 元数据。

(defadvice! org-html-template-fancier (orig-fn contents info)
  "Return complete document string after HTML conversion.
CONTENTS is the transcoded contents string.  INFO is a plist
holding export options. Adds a few extra things to the body
compared to the default implementation."
  :around #'org-html-template
  (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
      (funcall orig-fn contents info)
    (concat
     (when (and (not (org-html-html5-p info)) (org-html-xhtml-p info))
       (let* ((xml-declaration (plist-get info :html-xml-declaration))
              (decl (or (and (stringp xml-declaration) xml-declaration)
                        (cdr (assoc (plist-get info :html-extension)
                                    xml-declaration))
                        (cdr (assoc "html" xml-declaration))
                        "")))
         (when (not (or (not decl) (string= "" decl)))
           (format "%s\n"
                   (format decl
                           (or (and org-html-coding-system
                                    (fboundp 'coding-system-get)
                                    (coding-system-get org-html-coding-system 'mime-charset))
                               "iso-8859-1"))))))
     (org-html-doctype info)
     "\n"
     (concat "<html"
             (cond ((org-html-xhtml-p info)
                    (format
                     " xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"%s\" xml:lang=\"%s\""
                     (plist-get info :language) (plist-get info :language)))
                   ((org-html-html5-p info)
                    (format " lang=\"%s\"" (plist-get info :language))))
             ">\n")
     "<head>\n"
     (org-html--build-meta-info info)
     (org-html--build-head info)
     (org-html--build-mathjax-config info)
     "</head>\n"
     "<body>\n<input type='checkbox' id='theme-switch'><div id='page'><label id='switch-label' for='theme-switch'></label>"
     (let ((link-up (org-trim (plist-get info :html-link-up)))
           (link-home (org-trim (plist-get info :html-link-home))))
       (unless (and (string= link-up "") (string= link-home ""))
         (format (plist-get info :html-home/up-format)
                 (or link-up link-home)
                 (or link-home link-up))))
     ;; Preamble.
     (org-html--build-pre/postamble 'preamble info)
     ;; Document contents.
     (let ((div (assq 'content (plist-get info :html-divs))))
       (format "<%s id=\"%s\">\n" (nth 1 div) (nth 2 div)))
     ;; Document title.
     (when (plist-get info :with-title)
       (let ((title (and (plist-get info :with-title)
                         (plist-get info :title)))
             (subtitle (plist-get info :subtitle))
             (html5-fancy (org-html--html5-fancy-p info)))
         (when title
           (format
            (if html5-fancy
                "<header class=\"page-header\">%s\n<h1 class=\"title\">%s</h1>\n%s</header>"
              "<h1 class=\"title\">%s%s</h1>\n")
            (if (or (plist-get info :with-date)
                    (plist-get info :with-author))
                (concat "<div class=\"page-meta\">"
                        (when (plist-get info :with-date)
                          (org-export-data (plist-get info :date) info))
                        (when (and (plist-get info :with-date) (plist-get info :with-author)) ", ")
                        (when (plist-get info :with-author)
                          (org-export-data (plist-get info :author) info))
                        "</div>\n")
              "")
            (org-export-data title info)
            (if subtitle
                (format
                 (if html5-fancy
                     "<p class=\"subtitle\" role=\"doc-subtitle\">%s</p>\n"
                   (concat "\n" (org-html-close-tag "br" nil info) "\n"
                           "<span class=\"subtitle\">%s</span>\n"))
                 (org-export-data subtitle info))
              "")))))
     contents
     (format "</%s>\n" (nth 1 (assq 'content (plist-get info :html-divs))))
     ;; Postamble.
     (org-html--build-pre/postamble 'postamble info)
     ;; Possibly use the Klipse library live code blocks.
     (when (plist-get info :html-klipsify-src)
       (concat "<script>" (plist-get info :html-klipse-selection-script)
               "</script><script src=\""
               org-html-klipse-js
               "\"></script><link rel=\"stylesheet\" type=\"text/css\" href=\""
               org-html-klipse-css "\"/>"))
     ;; Closing document.
     "</div>\n</body>\n</html>")))

自定义 CSS/JS

lepisma.xyz 所做的导出风格非常讨喜。

<link rel="icon" href="https://tecosaur.com/resources/org/nib.ico" type="image/ico" />
<link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://tecosaur.com/resources/org/etbookot-roman-webfont.woff2">
<link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://tecosaur.com/resources/org/etbookot-italic-webfont.woff2">
<link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://tecosaur.com/resources/org/Merriweather-TextRegular.woff2">
<link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://tecosaur.com/resources/org/Merriweather-TextItalic.woff2">
<link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://tecosaur.com/resources/org/Merriweather-TextBold.woff2">
(defun org-html-reload-fancy-style ()
  (interactive)
  (setq org-html-style-fancy
        (with-temp-buffer
          (insert-file-contents (expand-file-name "misc/org-export-header.html" doom-user-dir))
          (goto-char (point-max))
          (insert "<script>\n")
          (insert-file-contents (expand-file-name "misc/org-css/main.js" doom-user-dir))
          (goto-char (point-max))
          (insert "</script>\n<style>\n")
          (insert-file-contents (expand-file-name "misc/org-css/main.min.css" doom-user-dir))
          (goto-char (point-max))
          (insert "</style>")
          (buffer-string)))
  (when org-fancy-html-export-mode
    (setq org-html-style-default org-html-style-fancy)))
(org-html-reload-fancy-style)

可折叠的 src 和示例块

通过将 <pre> 元素包装在 <details> 块中,我们可以得到没有 CSS 的可折叠块, 尽管我们会稍微折腾一下,让这看起来有点漂亮。

由于默认情况下对某些代码块启用可折叠性似乎很有用,因此如果您可以使用 #+attr_html: :collapsed t 设置它会更好。

如果有一个相应的全局/会话本地方式来设置它会很好,但我还没能让它正常工作。

(defvar org-html-export-collapsed nil)
(eval '(cl-pushnew '(:collapsed "COLLAPSED" "collapsed" org-html-export-collapsed t)
                   (org-export-backend-options (org-export-get-backend 'html))))
(add-to-list 'org-default-properties "EXPORT_COLLAPSED")

我们可以进一步修改 src 块,并在 src 块的一侧添加一个块,其中包含引用当前块的锚点和 复制块内容的按钮。

(defadvice! org-html-src-block-collapsable (orig-fn src-block contents info)
  "Wrap the usual <pre> block in a <details>"
  :around #'org-html-src-block
  (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
      (funcall orig-fn src-block contents info)
    (let* ((properties (cadr src-block))
           (lang (mode-name-to-lang-name
                  (plist-get properties :language)))
           (name (plist-get properties :name))
           (ref (org-export-get-reference src-block info))
           (collapsed-p (member (or (org-export-read-attribute :attr_html src-block :collapsed)
                                    (plist-get info :collapsed))
                                '("y" "yes" "t" t "true" "all"))))
      (format
       "<details id='%s' class='code'%s><summary%s>%s</summary>
<div class='gutter'>
<a href='#%s'>#</a>
<button title='Copy to clipboard' onclick='copyPreToClipbord(this)'>⎘</button>\
</div>
%s
</details>"
       ref
       (if collapsed-p "" " open")
       (if name " class='named'" "")
       (concat
        (when name (concat "<span class=\"name\">" name "</span>"))
        "<span class=\"lang\">" lang "</span>")
       ref
       (if name
           (replace-regexp-in-string (format "<pre\\( class=\"[^\"]+\"\\)? id=\"%s\">" ref) "<pre\\1>"
                                     (funcall orig-fn src-block contents info))
         (funcall orig-fn src-block contents info))))))

(defun mode-name-to-lang-name (mode)
  (or (cadr (assoc mode
                   '(("asymptote" "Asymptote")
                     ("awk" "Awk")
                     ("C" "C")
                     ("clojure" "Clojure")
                     ("css" "CSS")
                     ("D" "D")
                     ("ditaa" "ditaa")
                     ("dot" "Graphviz")
                     ("calc" "Emacs Calc")
                     ("emacs-lisp" "Emacs Lisp")
                     ("fortran" "Fortran")
                     ("gnuplot" "gnuplot")
                     ("haskell" "Haskell")
                     ("hledger" "hledger")
                     ("java" "Java")
                     ("js" "Javascript")
                     ("latex" "LaTeX")
                     ("ledger" "Ledger")
                     ("lisp" "Lisp")
                     ("lilypond" "Lilypond")
                     ("lua" "Lua")
                     ("matlab" "MATLAB")
                     ("mscgen" "Mscgen")
                     ("ocaml" "Objective Caml")
                     ("octave" "Octave")
                     ("org" "Org mode")
                     ("oz" "OZ")
                     ("plantuml" "Plantuml")
                     ("processing" "Processing.js")
                     ("python" "Python")
                     ("R" "R")
                     ("ruby" "Ruby")
                     ("sass" "Sass")
                     ("scheme" "Scheme")
                     ("screen" "Gnu Screen")
                     ("sed" "Sed")
                     ("sh" "shell")
                     ("sql" "SQL")
                     ("sqlite" "SQLite")
                     ("forth" "Forth")
                     ("io" "IO")
                     ("J" "J")
                     ("makefile" "Makefile")
                     ("maxima" "Maxima")
                     ("perl" "Perl")
                     ("picolisp" "Pico Lisp")
                     ("scala" "Scala")
                     ("shell" "Shell Script")
                     ("ebnf2ps" "ebfn2ps")
                     ("cpp" "C++")
                     ("abc" "ABC")
                     ("coq" "Coq")
                     ("groovy" "Groovy")
                     ("bash" "bash")
                     ("csh" "csh")
                     ("ash" "ash")
                     ("dash" "dash")
                     ("ksh" "ksh")
                     ("mksh" "mksh")
                     ("posh" "posh")
                     ("ada" "Ada")
                     ("asm" "Assembler")
                     ("caml" "Caml")
                     ("delphi" "Delphi")
                     ("html" "HTML")
                     ("idl" "IDL")
                     ("mercury" "Mercury")
                     ("metapost" "MetaPost")
                     ("modula-2" "Modula-2")
                     ("pascal" "Pascal")
                     ("ps" "PostScript")
                     ("prolog" "Prolog")
                     ("simula" "Simula")
                     ("tcl" "tcl")
                     ("tex" "LaTeX")
                     ("plain-tex" "TeX")
                     ("verilog" "Verilog")
                     ("vhdl" "VHDL")
                     ("xml" "XML")
                     ("nxml" "XML")
                     ("conf" "Configuration File"))))
      mode))
(defun org-html-block-collapsable (orig-fn block contents info)
  "Wrap the usual block in a <details>"
  (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
      (funcall orig-fn block contents info)
    (let ((ref (org-export-get-reference block info))
          (type (pcase (car block)
                  ('property-drawer "Properties")))
          (collapsed-default (pcase (car block)
                               ('property-drawer t)
                               (_ nil)))
          (collapsed-value (org-export-read-attribute :attr_html block :collapsed))
          (collapsed-p (or (member (org-export-read-attribute :attr_html block :collapsed)
                                   '("y" "yes" "t" t "true"))
                           (member (plist-get info :collapsed) '("all")))))
      (format
       "<details id='%s' class='code'%s>
<summary%s>%s</summary>
<div class='gutter'>\
<a href='#%s'>#</a>
<button title='Copy to clipboard' onclick='copyPreToClipbord(this)'>⎘</button>\
</div>
%s\n
</details>"
       ref
       (if (or collapsed-p collapsed-default) "" " open")
       (if type " class='named'" "")
       (if type (format "<span class='type'>%s</span>" type) "")
       ref
       (funcall orig-fn block contents info)))))

(advice-add 'org-html-example-block   :around #'org-html-block-collapsable)
(advice-add 'org-html-fixed-width     :around #'org-html-block-collapsable)
(advice-add 'org-html-property-drawer :around #'org-html-block-collapsable)

HTML 化的字体锁

Org 使用 htmlize.el 导出带有语法高亮的缓冲区。 在大多数情况下这些格式非常棒。不需要加载提供字体锁定的次要模式,因此不会影响结果。 通过在 htmlize-before-hook 中启用这些模式,我们可以纠正这种行为。

(require 'htmlize)
(add-hook! htmlize-before #'highlight-numbers--turn-on)

处理表溢出

为了适应宽表 — 尤其是在移动设备上 — 我们想要设置最大宽度和滚动溢出。不幸的是, 这不能直接应用于 表格 元素,所以我们必须将它包装在一个 div 中。

当我们这样做时,我们可以像我们对 src 块所做的那样,设置一个链接块,并显示 #+name (如果有的话)。

(defadvice! org-html-table-wrapped (orig-fn table contents info)
  "Wrap the usual <table> in a <div>"
  :around #'org-html-table
  (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
      (funcall orig-fn table contents info)
    (let* ((name (plist-get (cadr table) :name))
           (ref (org-export-get-reference table info)))
      (format "<div id='%s' class='table'>
<div class='gutter'><a href='#%s'>#</a></div>
<div class='tabular'>
%s
</div>\
</div>"
              ref ref
              (if name
                  (replace-regexp-in-string (format "<table id=\"%s\"" ref) "<table"
                                            (funcall orig-fn table contents info))
                (funcall orig-fn table contents info))))))

可展开的目录树

TOC 作为可折叠树更易于导航。不幸的是,我们不能单独使用 CSS 来实现这一点。值得庆幸 的是,我们可以通过调整 TOC 生成代码来为每个项目使用一个 标签 ,以及一个隐藏的 复选框 来跟踪状态,从而避免使用 JS。

要添加它,我们需要在 org-html--format-toc-headline 中更改一行。

因为我们实际上可以通过在函数周围添加建议来实现所需的效果,而无需覆盖它 — 让我们这 样做来减少这个配置的错误表面。

(defadvice! org-html--format-toc-headline-colapseable (orig-fn headline info)
  "Add a label and checkbox to `org-html--format-toc-headline's usual output,
to allow the TOC to be a collapseable tree."
  :around #'org-html--format-toc-headline
  (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
      (funcall orig-fn headline info)
    (let ((id (or (org-element-property :CUSTOM_ID headline)
                  (org-export-get-reference headline info))))
      (format "<input type='checkbox' id='toc--%s'/><label for='toc--%s'>%s</label>"
              id id (funcall orig-fn headline info)))))

现在,叶子 (没有子标题的标题) 不应该有 标签项 。实现这一点的明显方法是在 org-html--format-toc-headline-colapseable 中包含一些 (如果没有子项) 逻辑 。不幸的是,我的 elisp 无法从 org 提供的大量信息中提取子标题的数量。

(defadvice! org-html--toc-text-stripped-leaves (orig-fn toc-entries)
  "Remove label"
  :around #'org-html--toc-text
  (if (or (not org-fancy-html-export-mode) (bound-and-true-p org-msg-export-in-progress))
      (funcall orig-fn toc-entries)
    (replace-regexp-in-string "<input [^>]+><label [^>]+>\\(.+?\\)</label></li>" "\\1</li>"
                              (funcall orig-fn toc-entries))))

使 verbatim 与 code 不同 (HTML)

让我们为 varbatimcode 添加一些差异。

我们可以将 code 专门用于代码片段和命令,例如:在 Emacs 的批处理模式中调用 src_elisp{(message “Hello”)} 会像 echo 一样打印到标准输出。 可以对 verbatim 使用各种 ‘其他等宽’,例如键盘快捷键: C-c C-cC-g 可能是 Emacs 中最有用的快捷键,或文件名:我将我的配置保存在 ~/.config/doom/ 中,其他 情况则使用正常样式。

(setq org-html-text-markup-alist
      '((bold . "<b>%s</b>")
        (code . "<code>%s</code>")
        (italic . "<i>%s</i>")
        (strike-through . "<del>%s</del>")
        (underline . "<span class=\"underline\">%s</span>")
        (verbatim . "<kbd>%s</kbd>")))

改变复选框类型

我们也想使用 HTML 复选框,但是我们想比默认的更漂亮

(appendq! org-html-checkbox-types
          '((html-span
             (on . "<span class='checkbox'></span>")
             (off . "<span class='checkbox'></span>")
             (trans . "<span class='checkbox'></span>"))))
(setq! org-html-checkbox-type 'html-span)
  • [ ] I’m yet to do this
  • [-] Work in progress
  • [X] This is done

额外的特殊字符串

org-html-special-string-regexps 变量定义了以下内容的替换:

  • \-, shy 连字符
  • ---, 增强的破折号
  • --, 破折号
  • ..., (水平的) 省略号

但我认为如果还可以替换左/右箭头 (-><-) 那就更好了。 这是一个 defconst ,但正如你可以从这个配置中的大量建议中看出的那样,我并没有放弃我不 ‘应 该’ 做的事情。

唯一的小麻烦是在这个阶段的输出处理之前 <> 被转换为 &lt; 和 =&gt;=​。

(pushnew! org-html-special-string-regexps
          '("-&gt;" . "&#8594;")
          '("&lt;-" . "&#8592;"))

标题锚点

一个 GitHub 风格的标题链接

(defun org-export-html-headline-anchor (text backend info)
  (when (and (org-export-derived-backend-p backend 'html)
             (not (org-export-derived-backend-p backend 're-reveal))
             org-fancy-html-export-mode)
    (unless (bound-and-true-p org-msg-export-in-progress)
      (replace-regexp-in-string
       "<h\\([0-9]\\) id=\"\\([a-z0-9-]+\\)\">\\(.*[^ ]\\)<\\/h[0-9]>" ; this is quite restrictive, but due to `org-reference-contraction' I can do this
       "<h\\1 id=\"\\2\">\\3<a aria-hidden=\"true\" href=\"#\\2\">#</a> </h\\1>"
       text))))
(add-to-list 'org-export-filter-headline-functions
             'org-export-html-headline-anchor)

LaTeX Rendering

如果使用 MathJax,就用 3 而不是 2。通过对比我们发现它似乎快了 5 倍,而且它使用单 个文件而不是多个文件,就是有点大而已。值得庆幸的是,这可以通过添加 async 属性 来延迟加载来缓解。

\(D(N) = D(i) + D(N - i - 1) + \frac{1}{N}.\)

\[D(N) = \frac{2}{N}[∑j=0N-1{D(j)}] + N - 1.\]

(setcdr (assoc 'path org-html-mathjax-options)
        (list "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"))

(setq! org-html-mathjax-template
      "<script>
window.MathJax = {
  loader: {
    load: ['[tex]/ams', '[tex]/upgreek', '[tex]/mathtools'],
  },
  tex: {
    ams: {
      multlineWidth: '%MULTLINEWIDTH'
    },
    tags: '%TAGS',
    tagSide: '%TAGSIDE',
    tagIndent: '%TAGINDENT',
    packages: {'[+]': ['ams', 'upgreek', 'mathtools']},
    mathtools: {
      pairedDelimiters: {
        abs: ['\\\\lvert', '\\\\rvert'],
        norm: ['\\\\lVert', '\\\\rVert'],
        ceil: ['\\\\lceil', '\\\\rceil'],
        floor: ['\\\\lfloor', '\\\\rfloor'],
        round: ['\\\\lfloor', '\\\\rceil'],
      }
    }
  },
  chtml: {
    scale: %SCALE,
    displayAlign: '%ALIGN',
    displayIndent: '%INDENT'
  },
  svg: {
    scale: %SCALE,
    displayAlign: '%ALIGN',
    displayIndent: '%INDENT'
  },
  output: {
    font: '%FONT',
    displayOverflow: '%OVERFLOW'
  }
};
</script>
<script id=\"MathJax-script\" async
        src=\"%PATH\"></script>")

LaTeX 导出

(defvar org-latex-maths-preamble
  "
<<latex-maths-conveniences>>
"
  "Preamble that sets up a bunch of mathematical conveniences.")
(defvar org-latex-caption-preamble
  "
<<org-latex-caption-preamble>>
"
  "Preamble that improves captions.")
(defvar org-latex-checkbox-preamble
  "
<<org-latex-checkbox-preamble>>
"
  "Preamble that improves checkboxes.")
(defvar org-latex-box-preamble
  "
<<org-latex-box-preamble>>
"
  "Preamble that provides a macro for custom boxes.")
(defvar org-latex-use-microtype nil
  "Use the microtype pakage.")
(defvar org-latex-italic-quotes t
  "Make \"quote\" environments italic.")
(defvar org-latex-par-sep t
  "Vertically seperate paragraphs, and remove indentation.")
(defvar org-export-latex--preamble-info nil)
(defvar org-latex-conditional-features nil
  "Org feature tests and associated LaTeX feature flags.

Alist where the car is a test for the presense of the feature,
and the cdr is either a single feature symbol or list of feature symbols.

When a string, it is used as a regex search in the buffer.
The feature is registered as present when there is a match.

The car can also be a
- symbol, the value of which is fetched
- function, which is called with info as an argument
- list, which is `eval'uated

If the symbol, function, or list produces a string: that is used as a regex
search in the buffer. Otherwise any non-nil return value will indicate the
existance of the feature.")
(defvar org-latex-feature-implementations nil
  "LaTeX features and details required to implement them.

List where the car is the feature symbol, and the rest forms a plist with the
following keys:
- :snippet, which may be either
  - a string which should be included in the preamble
  - a symbol, the value of which is included in the preamble
  - a function, which is evaluated with the list of feature flags as its
    single argument. The result of which is included in the preamble
  - a list, which is passed to `eval', with a list of feature flags available
    as \"features\"

- :requires, a feature or list of features that must be available
- :when, a feature or list of features that when all available should cause this
    to be automatically enabled.
- :prevents, a feature or list of features that should be masked
- :order, for when ordering is important. Lower values appear first.
    The default is 0.
- :eager, when non-nil the feature will be eagerly loaded, i.e. without being detected.")

(after! ox-latex
  <<ox-latex-conf>>
  )

编译

默认情况下,Org 使用 pdflatex × 3 + bibtex=​。 这在现代世界根本行不通。 =latexmk + biber (自动与 latexmk 一起使用) 是一个简单且优越组合。

;; org-latex-compilers = ("pdflatex" "xelatex" "lualatex"), which are the possible values for %latex
(setq! org-latex-pdf-process '("LC_ALL=en_US.UTF-8 latexmk -f -pdf -%latex --shell-escape --interaction=nonstopmode --output-directory=%o %f")
       org-latex-logfiles-extensions (append org-latex-logfiles-extensions
                                             '("acn" "acr" "alg" "bbl" "blg" "glg" "ist" "listing" "fdb_latexmk") nil))

虽然上面的 -%latex 有点 hacky (-pdflatex 期望被赋予一个值),但它允许我们保持 org-latex-compilers 不变。如果我打开一个使用 #+LATEX_COMPILER 的 org 文件, 这很好,它应该仍然可以工作。

复选框

我们可以假设在序言部分,定义了下面的各种自定义 \checkbox... 命令。

(defun +org-export-latex-fancy-item-checkboxes (text backend info)
  (when (org-export-derived-backend-p backend 'latex)
    (replace-regexp-in-string
     "\\\\item\\[{$\\\\\\(\\w+\\)$}\\]"
     (lambda (fullmatch)
       (concat "\\\\item[" (pcase (substring fullmatch 9 -3) ; content of capture group
                             ("square"   "\\\\checkboxUnchecked")
                             ("boxminus" "\\\\checkboxTransitive")
                             ("boxtimes" "\\\\checkboxChecked")
                             (_ (substring fullmatch 9 -3))) "]"))
     text)))
(add-to-list 'org-export-filter-item-functions
             '+org-export-latex-fancy-item-checkboxes)

类模板

页边空白处的节号,这可以用下面的 LaTeX 来完成。

\\renewcommand\\sectionformat{\\llap{\\thesection\\autodot\\enskip}}
\\renewcommand\\subsectionformat{\\llap{\\thesubsection\\autodot\\enskip}}
\\renewcommand\\subsubsectionformat{\\llap{\\thesubsubsection\\autodot\\enskip}}
\\makeatletter
\\g@addto@macro\\tableofcontents{\\clearpage\\setcounter{page}{1}\\pagenumbering{arabic}}
\\makeatother
\\setcounter{page}{1}
\\pagenumbering{Roman}

超大的 ~\chapter~​。

\\RedeclareSectionCommand[afterindent=false, beforeskip=0pt, afterskip=0pt, innerskip=0pt]{chapter}
\\setkomafont{chapter}{\\normalfont\\Huge}
\\renewcommand*{\\chapterheadstartvskip}{\\vspace*{0\\baselineskip}}
\\renewcommand*{\\chapterheadendvskip}{\\vspace*{0\\baselineskip}}
\\renewcommand*{\\chapterformat}{%
  \\fontsize{60}{30}\\selectfont\\rlap{\\hspace{6pt}\\thechapter}}
\\renewcommand*\\chapterlinesformat[3]{%
  \\parbox[b]{\\dimexpr\\textwidth-0.5em\\relax}{%
    \\raggedleft{{\\large\\scshape\\bfseries\\chapapp}\\vspace{-0.5ex}\\par\\Huge#3}}%
    \\hfill\\makebox[0pt][l]{#2}}

现在在 Org LaTeX 类中添加一些 =KOMA-Script=​。

(let* ((article-sections '(("\\section{%s}" . "\\section*{%s}")
                           ("\\subsection{%s}" . "\\subsection*{%s}")
                           ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                           ("\\paragraph{%s}" . "\\paragraph*{%s}")
                           ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
       (book-sections (append '(("\\chapter{%s}" . "\\chapter*{%s}"))
                              article-sections))
       (hanging-secnum-preamble "
<<latex-hanging-secnum>>
")
       (big-chap-preamble "
<<latex-big-chapter>>
"))
  (setcdr (assoc "article" org-latex-classes)
          `(,(concat "\\documentclass{scrartcl}" hanging-secnum-preamble)
            ,@article-sections))
  (add-to-list 'org-latex-classes
               `("report" ,(concat "\\documentclass{scrartcl}" hanging-secnum-preamble)
                 ,@article-sections))
  (add-to-list 'org-latex-classes
               `("book" ,(concat "\\documentclass[twoside=false]{scrbook}"
                                 big-chap-preamble hanging-secnum-preamble)
                 ,@book-sections))
  (add-to-list 'org-latex-classes
               `("blank" "[NO-DEFAULT-PACKAGES]\n[NO-PACKAGES]\n[EXTRA]"
                 ,@article-sections))
  (add-to-list 'org-latex-classes
               `("bmc-article" "\\documentclass[article,code,maths]{bmc}\n[NO-DEFAULT-PACKAGES]\n[NO-PACKAGES]\n[EXTRA]"
                 ,@article-sections))
  (add-to-list 'org-latex-classes
               `("bmc" "\\documentclass[code,maths]{bmc}\n[NO-DEFAULT-PACKAGES]\n[NO-PACKAGES]\n[EXTRA]"
                 ,@book-sections)))

(setq! org-latex-tables-booktabs t
       org-latex-hyperref-template "
<<latex-fancy-hyperref>>
"
       org-latex-reference-command "\\cref{%s}")

然而 hyperref 设置需要单独处理。

\\providecolor{url}{HTML}{0077bb}
\\providecolor{link}{HTML}{882255}
\\providecolor{cite}{HTML}{999933}
\\hypersetup{
  pdfauthor={%a},
  pdftitle={%t},
  pdfkeywords={%k},
  pdfsubject={%d},
  pdfcreator={%c},
  pdflang={%L},
  breaklinks=true,
  colorlinks=true,
  linkcolor=link,
  urlcolor=url,
  citecolor=cite
}
\\urlstyle{same}
%% hide links styles in toc
\\NewCommandCopy{\\oldtoc}{\\tableofcontents}
\\renewcommand{\\tableofcontents}{\\begingroup\\hypersetup{hidelinks}\\oldtoc\\endgroup}

智能序言

功能优化

Caption 可以做一些调整,比如

  • 可以轻松拥有多个 Caption
  • 指向图的链接带到图的顶部 (而不是底部)
  • Caption 标签可以稍微加粗一点
  • 只有当跨越多行时,多行 Caption 应该向右一点
\\usepackage{subcaption}
\\usepackage[hypcap=true]{caption}
\\setkomafont{caption}{\\sffamily\\small}
\\setkomafont{captionlabel}{\\upshape\\bfseries}
\\captionsetup{justification=raggedright,singlelinecheck=true}
\\usepackage{capt-of} % required by Org

默认的复选框太丑了,用一些好看的替代品吧。

\\newcommand{\\checkboxUnchecked}{$\\square$}
\\newcommand{\\checkboxTransitive}{\\rlap{\\raisebox{-0.1ex}{\\hspace{0.35ex}\\Large\\textbf -}}$\\square$}
\\newcommand{\\checkboxChecked}{\\rlap{\\raisebox{0.2ex}{\\hspace{0.35ex}\\scriptsize \\ding{52}}}$\\square$}

“消息块” 是一个不错的想法,就像 info/warning/error/success。LaTeX 中的宏可 以创建它们。

\\ExplSyntaxOn
\\NewCoffin\\Content
\\NewCoffin\\SideRule
\\NewDocumentCommand{\\defsimplebox}{O{\\ding{117}} O{0.36em} m m m}{%
  % #1 ding, #2 ding offset, #3 name, #4 colour, #5 default label
  \\definecolor{#3}{HTML}{#4}
  \\NewDocumentEnvironment{#3}{ O{#5} }
  {
    \\vcoffin_set:Nnw \\Content { \\linewidth }
    \\noindent \\ignorespaces
    \\par\\vspace{-0.7\\baselineskip}%
    \\textcolor{#3}{#1}~\\textcolor{#3}{\\textbf{##1}}%
    \\vspace{-0.8\\baselineskip}
    \\begin{addmargin}[1em]{1em}
    }
    {
    \\end{addmargin}
    \\vspace{-0.5\\baselineskip}
    \\vcoffin_set_end:
    \\SetHorizontalCoffin\\SideRule{\\color{#3}\\rule{1pt}{\\CoffinTotalHeight\\Content}}
    \\JoinCoffins*\\Content[l,t]\\SideRule[l,t](#2,-0.7em)
    \\noindent\\TypesetCoffin\\Content
    \\vspace*{\\CoffinTotalHeight\\Content}\\bigskip
    \\vspace{-2\\baselineskip}
  }
}
\\ExplSyntaxOff
内容-特征-序言关联

每个检测到的特征都会给出一个所需的「特征标志」列表。只需合并功能标志列表,不再需 要避免 LaTeX 的重复。然后额外的层在特征标志和可用于实现该特征的规范之间形成双射。

首先,我们将实现该模型的特征检测组件。我希望它能够使用尽可能多的状态信息,因此功 能测试应该非常通用。

(setq! org-latex-conditional-features
       '(("\\[\\[\\(?:file\\|https?\\):\\(?:[^]]\\|\\\\\\]\\)+?\\.\\(?:eps\\|pdf\\|png\\|jpeg\\|jpg\\|jbig2\\)\\]\\]\\|\\\\includegraphics[\\[{]" . image)
         ("\\[\\[\\(?:file\\|https?\\):\\(?:[^]]+?\\|\\\\\\]\\)\\.svg\\]\\]\\|\\\\includesvg[\\[{]" . svg)
         ("\\\\(\\|\\\\\\[\\|\\\\begin{\\(?:math\\|displaymath\\|equation\\|align\\|flalign\\|multiline\\|gather\\)[a-z]*\\*?}" . maths)
         ("^[ \t]*|" . table)
         ("cref:\\|\\cref{" . cleveref)
         ("[;\\\\]?\\b[A-Z][A-Z]+s?[^A-Za-z]" . acronym)
         ("\\+[^ ].*[^ ]\\+\\|_[^ ].*[^ ]_\\|\\\\uu?line\\|\\\\uwave\\|\\\\sout\\|\\\\xout\\|\\\\dashuline\\|\\dotuline\\|\\markoverwith" . underline)
         (":float wrap" . float-wrap)
         (":float sideways" . rotate)
         (org-latex-use-microtype . microtype)
         ("[\u2500-\u259F]" . box-drawing)
         ("^[ \t]*#\\+caption:\\|\\\\caption" . caption)
         ((and org-latex-italic-quotes "^[ \t]*#\\+begin_quote\\|\\\\begin{quote}") . italic-quotes)
         (org-latex-par-sep . par-sep)
         ("^[ \t]*\\(?:[-+*]\\|[0-9]+[.)]\\|[A-Za-z]+[.)]\\) \\[[ -X]\\]" . checkbox)
         ("^[ \t]*#\\+begin_warning\\|\\\\begin{warning}" . box-warning)
         ("^[ \t]*#\\+begin_info\\|\\\\begin{info}"       . box-info)
         ("^[ \t]*#\\+begin_notes\\|\\\\begin{notes}"     . box-notes)
         ("^[ \t]*#\\+begin_success\\|\\\\begin{success}" . box-success)
         ("^[ \t]*#\\+begin_error\\|\\\\begin{error}"     . box-error)))

然后我们提供一种方法来生成提供这些功能的序言。除了 org-latex-conditional-features 中命名的特性之外,我们还将创建元特性,这些 特性可能是其他特性所需要的 (:requires),或者默认情况下是启用的 (:eager t)。为了进一步控制,我只能在某些其他功能处于启用状态 (:when) 并被 其他功能屏蔽 (:prevents) 时使用某些功能。我将使用以 . 开头的元功能的约定 ,以及以 ! 开头的 :eager 功能,使它们的性质更明显。

LaTeX 中的另一个考虑因素是加载顺序,这在某些情况下很重要。除此之外,有某种合理的 排序是很好的。为此我将介绍一个 :order 关键字。使用它将按如下方式排列片段。

  • 0 排版
    • 0 字体本身
    • 0.2 数学设置
    • 0.3 数学字体
    • 0.4 额外的文字整形 (\acr)
    • 0.5-0.9 其他文本修改,尝试将较短的片段放在首位
  • 1 (default)
  • 2 表和图
  • 3 其他短内容
  • 4 各种 boxes
(setq! org-latex-feature-implementations
       '((image         :snippet "\\usepackage{graphicx}" :order 2)
         (svg           :snippet "\\usepackage[inkscapelatex=false]{svg}" :order 2)
         (maths         :snippet org-latex-maths-preamble :order 0.2)
         (table         :snippet "\\usepackage{longtable}\n\\usepackage{booktabs}" :order 2)
         (cleveref      :snippet "\\usepackage{biber}\n\\usepackage[capitalize]{cleveref}"
                        :order 1) ; after bmc-maths
         (underline     :snippet "\\usepackage[normalem]{ulem}" :order 0.5)
         (float-wrap    :snippet "\\usepackage{wrapfig}" :order 2)
         (rotate        :snippet "\\usepackage{rotating}" :order 2)
         (caption       :snippet org-latex-caption-preamble :order 2.1)
         (microtype     :snippet "\\usepackage[activate={true,nocompatibility},final,tracking=true,kerning=true,spacing=true,factor=2000]{microtype}"
                        :order 0.1)
         (acronym       :snippet "\\newcommand{\\acr}[1]{\\protect\\textls*[110]{\\scshape #1}}\n\\newcommand{\\acrs}{\\protect\\scalebox{.91}[.84]{\\hspace{0.15ex}s}}"
                        :order 0.4)
         (box-drawing   :snippet "\\usepackage{pmboxdraw}" :order 0.05)
         (italic-quotes :snippet "\\renewcommand{\\quote}{\\list{}{\\rightmargin\\leftmargin}\\item\\relax\\em}\n"
                        :order 0.5)
         (par-sep       :snippet "\\setlength{\\parskip}{\\baselineskip}\n\\setlength{\\parindent}{0pt}"
                        :order 0.5)
         (.pifont       :snippet "\\usepackage{pifont}")
         (.xcoffins     :snippet "\\usepackage{xcoffins}")
         (checkbox      :requires .pifont
                        :order 3
                        :snippet (concat (unless (memq 'maths features)
                                           "\\usepackage{amssymb} % provides \\square")
                                         org-latex-checkbox-preamble))
         (.fancy-box    :requires (.pifont .xcoffins) :snippet org-latex-box-preamble :order 3.9)
         (box-warning   :requires .fancy-box
                        :snippet "\\defsimplebox{warning}{e66100}{Warning}"
                        :order 4)
         (box-info      :requires .fancy-box
                        :snippet "\\defsimplebox{info}{3584e4}{Information}"
                        :order 4)
         (box-notes     :requires .fancy-box
                        :snippet "\\defsimplebox{notes}{26a269}{Notes}"
                        :order 4)
         (box-success  :requires .fancy-box
                       :snippet "\\defsimplebox{success}{26a269}{\\vspace{-\\baselineskip}}"
                       :order 4)
         (box-error     :requires .fancy-box
                        :snippet "\\defsimplebox{error}{c01c28}{Important}"
                        :order 4)))
无条件添加的包
(setq! org-latex-packages-alist
       '(("svgnames" "xcolor" t)
         ("" "tikz" t)))
特征确定

现在我们已经定义了 org-latex-conditional-features ,我们需要使用它来提取 在 Org 缓冲区中找到的特征列表。

(defun org-latex-detect-features (&optional buffer info)
  "List features from `org-latex-conditional-features' detected in BUFFER."
  (let ((case-fold-search nil))
    (with-current-buffer (or buffer (current-buffer))
      (delete-dups
       (mapcan (lambda (construct-feature)
                 (when (let ((out (pcase (car construct-feature)
                                    ((pred stringp) (car construct-feature))
                                    ((pred functionp) (funcall (car construct-feature) info))
                                    ((pred listp) (eval (car construct-feature)))
                                    ((pred symbolp) (symbol-value (car construct-feature)))
                                    (_ (user-error "org-latex-conditional-features key %s unable to be used" (car construct-feature))))))
                         (if (stringp out)
                             (save-excursion
                               (goto-char (point-min))
                               (re-search-forward out nil t))
                           out))
                   (if (listp (cdr construct-feature)) (cdr construct-feature) (list (cdr construct-feature)))))
               org-latex-conditional-features)))))
序言生成

一旦确定了所需功能的列表,我们希望使用 org-latex-feature-implementations 来生成 LaTeX,该 LaTeX 应该插入到序言中以提供这些功能。

首先,我们要在 org-latex-feature-implementations 中处理我们的关键字,以 生成扩展的功能列表。我们将通过执行以下步骤来做到这一点。

  • 每个列出的功能的依赖项都添加到功能列表 (src_elisp{:requires}) 中。
  • 每个特性的 src_elisp{:when} 条件,以及带有 src_elisp{:eager t} 的可用特 性,都会被评估,并相应地添加 / 删除
  • src_elisp{:prevents} 值中存在的任何特性都将被删除
  • 功能列表清除重复项
  • 特征列表按 src_elisp{:order} (升序) 排序
(defun org-latex-expand-features (features)
  "For each feature in FEATURES process :requires, :when, and :prevents keywords and sort according to :order."
  (dolist (feature features)
    (unless (assoc feature org-latex-feature-implementations)
      (message "Feature %s not provided in org-latex-feature-implementations, ignoring." feature)
      (setq features (remove feature features))))
  (setq current features)
  (while current
    (when-let ((requirements (plist-get (cdr (assq (car current) org-latex-feature-implementations)) :requires)))
      (setcdr current (if (listp requirements)
                          (append requirements (cdr current))
                        (cons requirements (cdr current)))))
    (setq current (cdr current)))
  (dolist (potential-feature
           (append features (delq nil (mapcar (lambda (feat)
                                                (when (plist-get (cdr feat) :eager)
                                                  (car feat)))
                                              org-latex-feature-implementations))))
    (when-let ((prerequisites (plist-get (cdr (assoc potential-feature org-latex-feature-implementations)) :when)))
      (setf features (if (if (listp prerequisites)
                             (cl-every (lambda (preq) (memq preq features)) prerequisites)
                           (memq prerequisites features))
                         (append (list potential-feature) features)
                       (delq potential-feature features)))))
  (dolist (feature features)
    (when-let ((prevents (plist-get (cdr (assoc feature org-latex-feature-implementations)) :prevents)))
      (setf features (cl-set-difference features (if (listp prevents) prevents (list prevents))))))
  (sort (delete-dups features)
        (lambda (feat1 feat2)
          (if (< (or (plist-get (cdr (assoc feat1 org-latex-feature-implementations)) :order) 1)
                 (or (plist-get (cdr (assoc feat2 org-latex-feature-implementations)) :order) 1))
              t nil))))

现在我们有一个很好的最终要使用的特性列表,可以提取它们的片段并将结果连接在一起。

(defun org-latex-generate-features-preamble (features)
  "Generate the LaTeX preamble content required to provide FEATURES.
This is done according to `org-latex-feature-implementations'"
  (let ((expanded-features (org-latex-expand-features features)))
    (concat
     (format "\n%% features: %s\n" expanded-features)
     (mapconcat (lambda (feature)
                  (when-let ((snippet (plist-get (cdr (assoc feature org-latex-feature-implementations)) :snippet)))
                    (concat
                     (pcase snippet
                       ((pred stringp) snippet)
                       ((pred functionp) (funcall snippet features))
                       ((pred listp) (eval `(let ((features ',features)) (,@snippet))))
                       ((pred symbolp) (symbol-value snippet))
                       (_ (user-error "org-latex-feature-implementations :snippet value %s unable to be used" snippet)))
                     "\n")))
                expanded-features
                "")
     "% end features\n")))

然后需要建议 Org 实际使用这个生成的前导内容。

(defadvice! org-latex-save-info (info &optional t_ s_)
  :before #'org-latex-make-preamble
  (setq org-export-latex--preamble-info info))

(defadvice! org-splice-latex-header-and-generated-preamble-a (orig-fn tpl def-pkg pkg snippets-p &optional extra)
  "Dynamically insert preamble content based on `org-latex-conditional-preambles'."
  :around #'org-splice-latex-header
  (let ((header (funcall orig-fn tpl def-pkg pkg snippets-p extra)))
    (if snippets-p header
      (concat header
              (org-latex-generate-features-preamble (org-latex-detect-features nil org-export-latex--preamble-info))
              "\n"))))

我对 org-export-latex--preamble-info 的使用有点老套。 当我尝试将其上游化时,这应 该会变得更加清晰,因为我可以通过直接修改 org-latex-make-preamble 来传递信息。

数学声明

数学总是层出不穷。我想这既说明了我和这门学科本身。虽然 LaTeX 的命令集相当合理, 但我们可以让一些常用的符号更方便一些。

宏包

首先我们需要非常常用的包。

%% Maths-related packages
% More maths environments, commands, and symbols.
\\usepackage{amsmath, amssymb}
% Slanted fractions with \sfrac{a}{b}, in text and maths.
\\usepackage{xfrac}
% Visually cancel expressions with \cancel{value} and \cancelto{expression}{value}
\\usepackage[makeroom]{cancel}
% Improvements on amsmath and utilities for mathematical typesetting
\\usepackage{mathtools}
\\usepackage{upgreek,extarrows}
\\usepackage[math-style=ISO]{unicode-math}
自定义分隔符

接下来,我们要将各种类型的四舍五入和绝对值分隔符作为命令访问。

% Deliminators
\\DeclarePairedDelimiter{\\abs}{\\lvert}{\\rvert}
\\DeclarePairedDelimiter{\\norm}{\\lVert}{\\rVert}

\\DeclarePairedDelimiter{\\ceil}{\\lceil}{\\rceil}
\\DeclarePairedDelimiter{\\floor}{\\lfloor}{\\rfloor}
\\DeclarePairedDelimiter{\\round}{\\lfloor}{\\rceil}
数字集合

然后,我们有各种常见的数字集,如果能有一种方便的方法来键入它们并选择性地赋予它们 幂,那就更好了。要同时支持 \XX\XX[n] 是相当容易的。

\\newcommand{\\RR}[1][]{\\ensuremath{\\ifstrempty{#1}{\\mathbb{R}}{\\mathbb{R}^{#1}}}} % Real numbers
\\newcommand{\\NN}[1][]{\\ensuremath{\\ifstrempty{#1}{\\mathbb{N}}{\\mathbb{N}^{#1}}}} % Natural numbers
\\newcommand{\\ZZ}[1][]{\\ensuremath{\\ifstrempty{#1}{\\mathbb{Z}}{\\mathbb{Z}^{#1}}}} % Integer numbers
\\newcommand{\\QQ}[1][]{\\ensuremath{\\ifstrempty{#1}{\\mathbb{Q}}{\\mathbb{Q}^{#1}}}} % Rational numbers
\\newcommand{\\CC}[1][]{\\ensuremath{\\ifstrempty{#1}{\\mathbb{C}}{\\mathbb{C}^{#1}}}} % Complex numbers
导数

导数的排版其实很麻烦,如果能有一个支持 \dv 的命令就更好了:

  • \dv{x}: x 的导数
  • \dv{f}{x}: f 相对于 x 的导数
  • \dv[2]{f}{x}: f 关于 x 的二阶导数

同样,如果能有一个部分派生的对应 \pdv 就更好了,它的行为方式类似,但可以提供多 个逗号分隔的变量 – 例如 \pdv{f}{x,y,z}.

% Easy derivatives
\\ProvideDocumentCommand\\dv{o m g}{%
  \\IfNoValueTF{#3}{%
    \\dv[#1]{}{#2}}{%
    \\IfNoValueTF{#1}{%
      \\frac{\\dd #2}{\\dd #3}%
    }{\\frac{\\dd[#1] #2}{\\dd {#3}^{#1}}}}}
% Easy partial derivatives
\\ExplSyntaxOn
\\ProvideDocumentCommand\\pdv{o m g}{%
  \\IfNoValueTF{#3}{\\pdv[#1]{}{#2}}%
  {\\ifnum\\clist_count:n{#3}<2
    \\IfValueTF{#1}{\\frac{\\partial^{#1} #2}{\\partial {#3}^{#1}}}%
    {\\frac{\\partial #2}{\\partial #3}}
    \\else
    \\frac{\\IfValueTF{#1}{\\partial^{#1}}{\\partial^{\\clist_count:n{#3}}}#2}%
    {\\clist_map_inline:nn{#3}{\\partial ##1 \\,}\\!}
    \\fi}}
\\ExplSyntaxOff
公共运算符

公共运算符集可以进行一些扩展。

% Laplacian
\\DeclareMathOperator{\\Lap}{\\mathcal{L}}

% Statistics
\\DeclareMathOperator{\\Var}{Var} % varience
\\DeclareMathOperator{\\Cov}{Cov} % covarience
\\newcommand{\\EE}{\\ensuremath{\\mathbb{E}}} % expected value
\\DeclareMathOperator{\\E}{E} % expected value
矩阵列对齐

默认情况下,矩阵中的所有内容都是居中排列的,但我发现这往往并不可取。如果能将对齐 方式作为环境的一个可选参数,并默认为右对齐,那就更好了。

% Redefine the matrix environment to allow for alignment
% via an optional argument, and use r as the default.
\\makeatletter
\\renewcommand*\\env@matrix[1][r]{\\hskip -\\arraycolsep%
    \\let\\@ifnextchar\\new@ifnextchar
    \\array{*\\c@MaxMatrixCols #1}}
\\makeatother

封面

要制作漂亮的封面,想到的一个简单方法就是重新定义 \maketitle 。为了精确控制定位 ,我们将使用 tikz 包,然后添加 Tikz 库 calcshape.geometric 来为背 景做一些漂亮的装饰。

首先为序言设置必要的补充。 这将完成以下任务:

  • 加载所需的包
  • 重新定义 \maketitle
  • 用 Tikz 画一个 Org 图标,用在封面上 (这是一个小彩蛋)
  • 通过重新定义 \tableofcontents 在目录之后开始一个新页面
\\usepackage{tikz}
\\usetikzlibrary{shapes.geometric}
\\usetikzlibrary{calc}

\\newsavebox\\orgicon
\\begin{lrbox}{\\orgicon}
  \\begin{tikzpicture}[y=0.80pt, x=0.80pt, inner sep=0pt, outer sep=0pt]
    \\path[fill=black!6] (16.15,24.00) .. controls (15.58,24.00) and (13.99,20.69) .. (12.77,18.06)arc(215.55:180.20:2.19) .. controls (12.33,19.91) and (11.27,19.09) .. (11.43,18.05) .. controls (11.36,18.09) and (10.17,17.83) .. (10.17,17.82) .. controls (9.94,18.75) and (9.37,19.44) .. (9.02,18.39) .. controls (8.32,16.72) and (8.14,15.40) .. (9.13,13.80) .. controls (8.22,9.74) and (2.18,7.75) .. (2.81,4.47) .. controls (2.99,4.47) and (4.45,0.99) .. (9.15,2.41) .. controls (14.71,3.99) and (17.77,0.30) .. (18.13,0.04) .. controls (18.65,-0.49) and (16.78,4.61) .. (12.83,6.90) .. controls (10.49,8.18) and (11.96,10.38) .. (12.12,11.15) .. controls (12.12,11.15) and (14.00,9.84) .. (15.36,11.85) .. controls (16.58,11.53) and (17.40,12.07) .. (18.46,11.69) .. controls (19.10,11.41) and (21.79,11.58) .. (20.79,13.08) .. controls (20.79,13.08) and (21.71,13.90) .. (21.80,13.99) .. controls (21.97,14.75) and (21.59,14.91) .. (21.47,15.12) .. controls (21.44,15.60) and (21.04,15.79) .. (20.55,15.44) .. controls (19.45,15.64) and (18.36,15.55) .. (17.83,15.59) .. controls (16.65,15.76) and (15.67,16.38) .. (15.67,16.38) .. controls (15.40,17.19) and (14.82,17.01) .. (14.09,17.32) .. controls (14.70,18.69) and (14.76,19.32) .. (15.50,21.32) .. controls (15.76,22.37) and (16.54,24.00) .. (16.15,24.00) -- cycle(7.83,16.74) .. controls (6.83,15.71) and (5.72,15.70) .. (4.05,15.42) .. controls (2.75,15.19) and (0.39,12.97) .. (0.02,10.68) .. controls (-0.02,10.07) and (-0.06,8.50) .. (0.45,7.18) .. controls (0.94,6.05) and (1.27,5.45) .. (2.29,4.85) .. controls (1.41,8.02) and (7.59,10.18) .. (8.55,13.80) -- (8.55,13.80) .. controls (7.73,15.00) and (7.80,15.64) .. (7.83,16.74) -- cycle;
  \\end{tikzpicture}
\\end{lrbox}

\\makeatletter
\\g@addto@macro\\tableofcontents{\\clearpage}
\\renewcommand\\maketitle{
  \\thispagestyle{empty}
  \\hyphenpenalty=10000 % hyphens look bad in titles
  \\renewcommand{\\baselinestretch}{1.1}
  \\let\\oldtoday\\today
  \\renewcommand{\\today}{\\LARGE\\number\\year\\\\\\large%
    \\ifcase \\month \\or Jan\\or Feb\\or Mar\\or Apr\\or May \\or Jun\\or Jul\\or Aug\\or Sep\\or Oct\\or Nov\\or Dec\\fi
    ~\\number\\day}
  \\begin{tikzpicture}[remember picture,overlay]
    %% Background Polygons %%
    \\foreach \\i in {2.5,...,22} % bottom left
    {\\node[rounded corners,black!3.5,draw,regular polygon,regular polygon sides=6, minimum size=\\i cm,ultra thick] at ($(current page.west)+(2.5,-4.2)$) {} ;}
    \\foreach \\i in {0.5,...,22} % top left
    {\\node[rounded corners,black!5,draw,regular polygon,regular polygon sides=6, minimum size=\\i cm,ultra thick] at ($(current page.north west)+(2.5,2)$) {} ;}
    \\node[rounded corners,fill=black!4,regular polygon,regular polygon sides=6, minimum size=5.5 cm,ultra thick] at ($(current page.north west)+(2.5,2)$) {};
    \\foreach \\i in {0.5,...,24} % top right
    {\\node[rounded corners,black!2,draw,regular polygon,regular polygon sides=6, minimum size=\\i cm,ultra thick] at ($(current page.north east)+(0,-8.5)$) {} ;}
    \\node[fill=black!3,rounded corners,regular polygon,regular polygon sides=6, minimum size=2.5 cm,ultra thick] at ($(current page.north east)+(0,-8.5)$) {};
    \\foreach \\i in {21,...,3} % bottom right
    {\\node[black!3,rounded corners,draw,regular polygon,regular polygon sides=6, minimum size=\\i cm,ultra thick] at ($(current page.south east)+(-1.5,0.75)$) {} ;}
    \\node[fill=black!3,rounded corners,regular polygon,regular polygon sides=6, minimum size=2 cm,ultra thick] at ($(current page.south east)+(-1.5,0.75)$) {};
    \\node[align=center, scale=1.4] at ($(current page.south east)+(-1.5,0.75)$) {\\usebox\\orgicon};
    %% Text %%
    \\node[left, align=right, black, text width=0.8\\paperwidth, minimum height=3cm, rounded corners,font=\\Huge\\bfseries] at ($(current page.north east)+(-2,-8.5)$)
    {\\@title};
    \\node[left, align=right, black, text width=0.8\\paperwidth, minimum height=2cm, rounded corners, font=\\Large] at ($(current page.north east)+(-2,-11.8)$)
    {\\scshape \\@author};
    \\renewcommand{\\baselinestretch}{0.75}
    \\node[align=center,rounded corners,fill=black!3,text=black,regular polygon,regular polygon sides=6, minimum size=2.5 cm,inner sep=0, font=\\Large\\bfseries ] at ($(current page.west)+(2.5,-4.2)$)
    {\\@date};
  \\end{tikzpicture}
  \\let\\today\\oldtoday
  \\clearpage}
\\makeatother

现在我们有了一个不错的封面页,我们只需要不时使用它。将此添加到 #+options 感觉 最合适。让封面选项接受 auto 作为值,然后根据字数决定是否应使用封面。然后我们只想 插入一个 LaTeX 片段在封面中来调整标题格式。

(defvar org-latex-cover-page 'auto
  "When t, use a cover page by default.
When auto, use a cover page when the document's wordcount exceeds
`org-latex-cover-page-wordcount-threshold'.
Set with #+option: coverpage:{yes,auto,no} in org buffers.")
(defvar org-latex-cover-page-wordcount-threshold 5000
  "Document word count at which a cover page will be used automatically.
This condition is applied when cover page option is set to auto.")
(defvar org-latex-subtitle-coverpage-format "\\\\\\bigskip\n\\LARGE\\mdseries\\itshape\\color{black!80} %s\\par"
  "Variant of `org-latex-subtitle-format' to use with the cover page.")
(defvar org-latex-cover-page-maketitle "
<<latex-cover-page>>
"
  "LaTeX preamble snippet that sets \\maketitle to produce a cover page.")

(eval '(cl-pushnew '(:latex-cover-page nil "coverpage" org-latex-cover-page)
                   (org-export-backend-options (org-export-get-backend 'latex))))

(defun org-latex-cover-page-p ()
  "Whether a cover page should be used when exporting this Org file."
  (pcase (or (car
              (delq nil
                    (mapcar
                     (lambda (opt-line)
                       (plist-get (org-export--parse-option-keyword opt-line 'latex) :latex-cover-page))
                     (cdar (org-collect-keywords '("OPTIONS"))))))
             org-latex-cover-page)
    ((or 't 'yes) t)
    ('auto (when (> (count-words (point-min) (point-max)) org-latex-cover-page-wordcount-threshold) t))
    (_ nil)))

(defadvice! org-latex-set-coverpage-subtitle-format-a (contents info)
  "Set the subtitle format when a cover page is being used."
  :before #'org-latex-template
  (when (org-latex-cover-page-p)
    (setf info (plist-put info :latex-subtitle-format org-latex-subtitle-coverpage-format))))

(add-to-list 'org-latex-feature-implementations '(cover-page :snippet org-latex-cover-page-maketitle :order 9) t)
(add-to-list 'org-latex-conditional-features '((org-latex-cover-page-p) . cover-page) t)

或许之后可以再加一些不同的封面。

更好看的代码块

来个序言区模板,主要采用 tcblisting 生成主题 box

\\usepackage{accsupp}
\\usepackage[most,breakable,minted]{tcolorbox}
\\definecolor{solarized-light-background}{HTML}{FDF6E3}
\\definecolor{solarized-light-frame}{HTML}{EEE8D6}
\\definecolor{solarized-light-title}{HTML}{979797}
\\definecolor{solarized-light-lineno}{HTML}{237D99}
\\newcommand{\\SetFancyVerbLine}{
  \\renewcommand{\\theFancyVerbLine}{
    \\protect\\BeginAccSupp{ActualText={}}\\sffamily\\textcolor{solarized-light-lineno}{\\scriptsize\\oldstylenums{\\arabic{FancyVerbLine}}}\\protect\\EndAccSupp{}
  }
}
\\newenvironment{orglisting}[2][]{
  \\SetFancyVerbLine
  \\tcblisting{
    frame empty,
    enhanced jigsaw,
    drop fuzzy shadow,
    breakable,
    center,
    width=\\linewidth,
    bottom=1mm, top=1mm, left=6mm,
    fonttitle=\\bfseries,
    listing only,
    listing engine=minted,
    colback=solarized-light-background,
    colframe=solarized-light-frame,
    coltitle=solarized-light-title,
    minted style=solarized-light,
    minted language=#2,
    minted options={
      breaklines=t,
      breakbefore=.,
      samepage=nil,
      encoding=utf8,
      fontsize=\\small,
      mathescape=t,
      escapeinside=,
      autogobble=t,
      breakautoindent=t,
      tabsize=4,
      numbersep=2mm,
      % numbers=left,
      numberblanklines=t,
      firstline=1,
      firstnumber=1,
      % lastline=,
      showspaces=nil,
      space=\\textvisiblespace, %% only showspaces=true
      obeytabs=nil,
      showtabs=nil,
      #1
    },
  }
}{
  \\endtcblisting
}

设置一下自己的代码渲染方式

(setq! org-latex-listings 'tcblisting
       org-latex-tcblisting-code-preamble "
<<latex-tcblisting-code-preamble>>
")

修改一下导出代码块的行为

(defadvice! org-latex-src-block-tcblisting (orig-fn src-block contents info)
  "Like `org-latex-src-block', but supporting an tcblisting backend"
  :around #'org-latex-src-block
  (if (eq 'tcblisting org-latex-src-block-backend)
      (ginshio/org-latex-scr-block src-block contents info)
    (funcall orig-fn src-block contents info)))

(defun ginshio/org-latex-src-block-getlang (language minted-langs)
  (if (not (string-equal-ignore-case language "fundamental"))
      (or (cadr (assq (intern language) minted-langs))
          (downcase language))
    "text"))

(defun ginshio/org-latex-scr-block (src-block contents info)
  (let* ((lang (org-element-property :language src-block))
         (attributes (org-export-read-attribute :attr_latex src-block))
         (float (plist-get attributes :float))
         (num-start (org-export-get-loc src-block info))
         (retain-labels (org-element-property :retain-labels src-block))
         (caption (org-element-property :caption src-block))
         (caption-above-p (org-latex--caption-above-p src-block info))
         (caption-str (org-latex--caption/label-string src-block info))
         (placement (or (org-unbracket-string "[" "]" (plist-get attributes :placement))
                        (plist-get info :latex-default-figure-position)))
         (float-env
          (cond
           ((string= "multicolumn" float)
            (format "\\begin{listing*}[%s]\n%s%%s\n%s\\end{listing*}"
                    placement
                    (if caption-above-p caption-str "")
                    (if caption-above-p "" caption-str)))
           (caption
            (format "\\begin{listing}[%s]\n%s%%s\n%s\\end{listing}"
                    placement
                    (if caption-above-p caption-str "")
                    (if caption-above-p "" caption-str)))
           ((string= "t" float)
            (concat (format "\\begin{listing}[%s]\n" placement)
                    "%s\n\\end{listing}"))
           (t "%s")))
         (options (plist-get info :latex-minted-options))
         (body
          (format
           "\\begin{orglisting}[%s]{%s}\n%s\\end{orglisting}"
           ;; Options.
           (concat
            (org-latex--make-option-string
             (if (or (not num-start) (assoc "linenos" options))
                 options
               (append
                `(("linenos")
                  ("firstnumber" ,(number-to-string (1+ num-start))))
                options)))
            (let ((local-options (plist-get attributes :options)))
              (and local-options (concat "," local-options))))
           ;; Language.
           (ginshio/org-latex-src-block-getlang lang (plist-get info :latex-minted-langs))
           ;; Source code.
           (let* ((code-info (org-export-unravel-code src-block))
                  (max-width
                   (apply 'max
                          (mapcar 'length
                                  (org-split-string (car code-info)
                                                    "\n")))))
             (org-export-format-code
              (car code-info)
              (lambda (loc _num ref)
                (concat
                 loc
                 (when ref
                   ;; Ensure references are flushed to the right,
                   ;; separated with 6 spaces from the widest line
                   ;; of code.
                   (concat (make-string (+ (- max-width (length loc)) 6)
                                        ?\s)
                           (format "(%s)" ref)))))
              nil (and retain-labels (cdr code-info)))))))
    ;; Return value.
    (format float-env body)))

内联代码块的行为相对更好修改

(defadvice! org-latex-inline-src-block-tcblisting (orig-fn inline-src-block contents info)
  "Like `org-latex-inline-src-block', but supporting an tcblisting backend"
  :around #'org-latex-inline-src-block
  (if (eq 'tcblisting org-latex-src-block-backend)
      ;; (funcall orig-fn inline-src-block contents (plist-put info :latex-listings 'minted))
      (ginshio/org-latex-inline-src-block inline-src-block contents info)
    (funcall orig-fn inline-src-block contents info)))

(defun ginshio/org-latex-inline-src-block (inline-src-block _contents info)
  (let* ((code (org-element-property :value inline-src-block))
         (separator (org-latex--find-verb-separator code))
         (org-lang (org-element-property :language inline-src-block))
         (mint-lang (ginshio/org-latex-src-block-getlang lang (plist-get info :latex-minted-langs)))
         (options (org-latex--make-option-string
                   (plist-get info :latex-minted-options))))
    (format "\\mintinline%s{%s}{%s}"
            (if (string= options "") "" (format "[%s]" options))
            mint-lang code)))

最终将其添加到智能序言中

(add-to-list 'org-latex-conditional-features '("^[ \t]*#\\+begin_src\\|^[ \t]*#\\+BEGIN_SRC\\|src_[A-Za-z]" . tcblisting-code-preamble) t)
(add-to-list 'org-latex-feature-implementations '(tcblisting-code-preamble :snippet org-latex-tcblisting-code-preamble :order 99) t)

有一个问题,就是代码中的所有引号有问题。

使 verbatim 与 code 不同

区分 verbatimverb

(setq! org-latex-text-markup-alist
       '((bold . "\\textbf{%s}")
         (code . protectedtexttt)
         (italic . "\\emph{%s}")
         (strike-through . "\\sout{%s}")
         (underline . "\\uline{%s}")
         (verbatim . verb)))

Pandoc 导出

顾名思义,这是一个语言的巴别塔,我们可以在 org-mode 中运行所有编程语言!当然,我 们需要定制一些东西。

(after! ox-pandoc
  <<ox-pandoc-conf>>
  )

GitHub Flavored Markdown

在使用 Pandoc 进行导出 gfm 时,链接 headline 时链接会被导出为数字。具体参见 Issue #58

(advice-add 'org-pandoc-link
            :around #'(lambda (fun link contents info)
                        (let* ((dest (when (string= (org-element-property :type link) "fuzzy")
                                       (org-export-resolve-fuzzy-link link info)))
                               (dest-type (when dest (org-element-type dest)))
                               (path (org-element-property :path link)))
                          (if (eq dest-type 'headline)
                              (format "[[#%s][%s]]" path path)
                            (funcall fun link contents info)))))

LaTeX

(setq! LaTeX-biblatex-use-Biber t)
(custom-set-variables '(LaTeX-section-label '(("part" . "part:")
                                              ("chapter" . "chap:")
                                              ("section" . "sec:")
                                              ("subsection" . "subsec:")
                                              ("subsubsection" . "subsubsec:")))
                      '(TeX-auto-local "auto")
                      '(TeX-command-extra-options "--shell-escape --interaction=nonstopmode"))
(after! latex
  <<tex-conf>>
  )

编译

(setq! TeX-save-query nil
       TeX-show-compilation t
       LaTeX-clean-intermediate-suffixes (append TeX-clean-default-intermediate-suffixes
                                                 '("\\.acn" "\\.acr" "\\.alg" "\\.glg"
                                                   "\\.ist" "\\.listing" "\\.fdb_latexmk")))
(add-to-list 'TeX-command-list '("LatexMk (LuaLaTeX)" "LC_ALL=en_US.UTF-8 latexmk -pdf -lualatex -8bit %S%(mode) %(file-line-error) %(extraopts) %t" TeX-run-latexmk nil
                                 (plain-tex-mode latex-mode doctex-mode)
                                 :help "Run LatexMk (LuaLaTeX)"))

引用

如果 bib 中有你不想要的某些字段,可以通过一下方法去除 (导言区)

\AtEveryBibitem{
  \clearfield{note}
  \ifentrytype{book}{
    \clearfield{url}
    \clearfield{isbn}
  }{}
  \ifentrytype{article}{
    \clearfield{url}
  }{}
  \ifentrytype{thesis}{
    \clearfield{url}
  }{}
}

视觉

增强一点点视觉效果

(setq TeX-fold-math-spec-list
      `(;; missing/better symbols
        ("" ("le"))
        ("" ("ge"))
        ("" ("ne"))
        ;; convenience shorts -- these don't work nicely ATM
        ;; ("‹" ("left"))
        ;; ("›" ("right"))
        ;; private macros
        ("" ("RR"))
        ("" ("NN"))
        ("" ("ZZ"))
        ("" ("QQ"))
        ("" ("CC"))
        ("" ("PP"))
        ("" ("HH"))
        ("𝔼" ("EE"))
        ("𝑑" ("dd"))
        ;; known commands
        ("" ("phantom"))
        (,(lambda (num den) (if (and (TeX-string-single-token-p num) (TeX-string-single-token-p den))
                                (concat num "" den)
                              (concat "" num "" den ""))) ("frac"))
        (,(lambda (arg) (concat "" (TeX-fold-parenthesize-as-necessary arg))) ("sqrt"))
        (,(lambda (arg) (concat "" (TeX-fold-parenthesize-as-necessary arg))) ("vec"))
        ("‘{1}’" ("text"))
        ;; private commands
        ("|{1}|" ("abs"))
        ("‖{1}‖" ("norm"))
        ("⌊{1}⌋" ("floor"))
        ("⌈{1}⌉" ("ceil"))
        ("⌊{1}⌉" ("round"))
        ("𝑑{1}/𝑑{2}" ("dv"))
        ("∂{1}/∂{2}" ("pdv"))
        ;; fancification
        ("{1}" ("mathrm"))
        (,(lambda (word) (string-offset-roman-chars 119743 word)) ("mathbf"))
        (,(lambda (word) (string-offset-roman-chars 119951 word)) ("mathcal"))
        (,(lambda (word) (string-offset-roman-chars 120003 word)) ("mathfrak"))
        (,(lambda (word) (string-offset-roman-chars 120055 word)) ("mathbb"))
        (,(lambda (word) (string-offset-roman-chars 120159 word)) ("mathsf"))
        (,(lambda (word) (string-offset-roman-chars 120367 word)) ("mathtt"))
        )
      TeX-fold-macro-spec-list
      '(
        ;; as the defaults
        ("[f]" ("footnote" "marginpar"))
        ("[c]" ("cite"))
        ("[l]" ("label"))
        ("[r]" ("ref" "pageref" "eqref"))
        ("[i]" ("index" "glossary"))
        ("..." ("dots"))
        ("{1}" ("emph" "textit" "textsl" "textmd" "textrm" "textsf" "texttt"
                "textbf" "textsc" "textup"))
        ;; tweaked defaults
        ("©" ("copyright"))
        ("®" ("textregistered"))
        (""  ("texttrademark"))
        ("[1]:||►" ("item"))
        ("❡❡ {1}" ("part" "part*"))
        ("❡ {1}" ("chapter" "chapter*"))
        ("§ {1}" ("section" "section*"))
        ("§§ {1}" ("subsection" "subsection*"))
        ("§§§ {1}" ("subsubsection" "subsubsection*"))
        ("¶ {1}" ("paragraph" "paragraph*"))
        ("¶¶ {1}" ("subparagraph" "subparagraph*"))
        ;; extra
        ("⬖ {1}" ("begin"))
        ("⬗ {1}" ("end"))
        ))

(defun string-offset-roman-chars (offset word)
  "Shift the codepoint of each character in WORD by OFFSET with an extra -6 shift if the letter is lowercase"
  (apply 'string
         (mapcar (lambda (c)
                   (string-offset-apply-roman-char-exceptions
                    (+ (if (>= c 97) (- c 6) c) offset)))
                 word)))

(defvar string-offset-roman-char-exceptions
  '(;; lowercase serif
    (119892 .  8462) ;
    ;; lowercase caligraphic
    (119994 . 8495) ;
    (119996 . 8458) ;
    (120004 . 8500) ;
    ;; caligraphic
    (119965 . 8492) ;
    (119968 . 8496) ;
    (119969 . 8497) ;
    (119971 . 8459) ;
    (119972 . 8464) ;
    (119975 . 8466) ;
    (119976 . 8499) ;
    (119981 . 8475) ;
    ;; fraktur
    (120070 . 8493) ;
    (120075 . 8460) ;
    (120076 . 8465) ;
    (120085 . 8476) ;
    (120092 . 8488) ;
    ;; blackboard
    (120122 . 8450) ;
    (120127 . 8461) ;
    (120133 . 8469) ;
    (120135 . 8473) ;
    (120136 . 8474) ;
    (120137 . 8477) ;
    (120145 . 8484) ;
    )
  "An alist of deceptive codepoints, and then where the glyph actually resides.")

(defun string-offset-apply-roman-char-exceptions (char)
  "Sometimes the codepoint doesn't contain the char you expect.
Such special cases should be remapped to another value, as given in `string-offset-roman-char-exceptions'."
  (if (assoc char string-offset-roman-char-exceptions)
      (cdr (assoc char string-offset-roman-char-exceptions))
    char))

(defun TeX-fold-parenthesize-as-necessary (tokens &optional suppress-left suppress-right)
  "Add ❪ ❫ parenthesis as if multiple LaTeX tokens appear to be present"
  (if (TeX-string-single-token-p tokens) tokens
    (concat (if suppress-left "" "")
            tokens
            (if suppress-right "" ""))))

(defun TeX-string-single-token-p (teststring)
  "Return t if TESTSTRING appears to be a single token, nil otherwise"
  (if (string-match-p "^\\\\?\\w+$" teststring) t nil))

一些 local 快捷键使生活更轻松

(map!
 :map LaTeX-mode-map
 :ei [C-return] #'LaTeX-insert-item)
(setq TeX-electric-math '("\\(" . ""))

当然数学界定符不需要强调

;; Making \( \) less visible
(defface unimportant-latex-face
  '((t :inherit font-lock-comment-face :weight extra-light))
  "Face used to make \\(\\), \\[\\] less visible."
  :group 'LaTeX-math)

(font-lock-add-keywords
 'latex-mode
 `(("\\\\[]()[]" 0 'unimportant-latex-face prepend))
 'end)

;; (font-lock-add-keywords
;;  'latex-mode
;;  '(("\\\\[[:word:]]+" 0 'font-lock-keyword-face prepend))
;;  'end)

CDLaTeX

默认情况下,符号修改非常好用,但可以充实更多符号。让我们将前缀更改为同样很少使用但更方便的键,例如 =;=​。

(after! cdlatex
  (setq cdlatex-env-alist
        '(("bmatrix" "\\begin{bmatrix}\n?\n\\end{bmatrix}" nil)
          ("equation*" "\\begin{equation*}\n?\n\\end{equation*}" nil)))
  (setq ;; cdlatex-math-symbol-prefix ?\; ;; doesn't work at the moment :(
   cdlatex-math-symbol-alist
   '( ;; adding missing functions to 3rd level symbols
     (?_    ("\\downarrow"  ""           "\\inf"))
     (?2    ("^2"           "\\sqrt{?}"     ""     ))
     (?3    ("^3"           "\\sqrt[3]{?}"  ""     ))
     (?^    ("\\uparrow"    ""           "\\sup"))
     (?k    ("\\kappa"      ""           "\\ker"))
     (?m    ("\\mu"         ""           "\\lim"))
     (?c    (""             "\\circ"     "\\cos"))
     (?d    ("\\delta"      "\\partial"  "\\dim"))
     (?D    ("\\Delta"      "\\nabla"    "\\deg"))
     ;; no idea why \Phi isnt on 'F' in first place, \phi is on 'f'.
     (?F    ("\\Phi"))
     ;; now just convenience
     (?.    ("\\cdot" "\\dots"))
     (?:    ("\\vdots" "\\ddots"))
     (?*    ("\\times" "\\star" "\\ast")))
   cdlatex-math-modify-alist
   '( ;; my own stuff
     (?B    "\\mathbb"        nil          t    nil  nil)
     (?a    "\\abs"           nil          t    nil  nil))))

SyncTeX

(setq!
 ;;; %o: TeX-output-extension; %n: TeX-current-line; %b: TeX-current-file-name-master-relative
 TeX-view-program-list '(("Okular" "okular --unique %o#src:%n%(dir)./%b"))
 TeX-view-program-selection '((output-pdf "Okular") (output-dvi "Okular")))

Fixes

(add-hook! latex-mode #'TeX-latex-mode)

Markdown

Doom 默认的 Markdown 是 GFM (GitHub Flavored Markdown),不过有了 EmacsOrg Mode 谁还用 Markdown 。但是 toc-org-mode 依然可以使用。

可以采用如下方式支持 Markdown。

# TOC         <!-- :TOC: -->

C/C++

tcc 也是 C++

(add-to-list 'auto-mode-alist '("\\.tcc\\'" . c++-mode))

如果开启了 format 那可以使用 clang-format 自动格式化代码

(set-formatter! 'clang-format
  '("clang-format" "--Wno-error=unknown" ("-assume-filename=%S" (or buffer-file-name mode-result "")))
  :modes '(c-mode c++-mode))

设置 gud-gdb 显示当前行

(after! gud-gdb
  ;;; Keep the current line in sync with the point and in the center of the
  ;;; buffer. Otherwise the current line may disappear from the buffer as you step
  ;;; into the code. I don't know why this is not the default.
  (defadvice gud-display-line (after gud-display-line-centered activate)
    "Center the current line in the source code window"
    (when (and gud-overlay-arrow-position gdb-source-window)
      (with-selected-window gdb-source-window
        ; (marker-buffer gud-overlay-arrow-position)
        (save-restriction
          ;; Compiler-happy equivalent to (goto-line (ad-get-arg 1))
          (goto-char (point-min))
          (forward-line (1- (ad-get-arg 1)))
          (recenter))))))

参考配置