Doom Emacs 配置
这份配置基于 Emacs 模式的按键组合,没有启用 Doom Emacs 缺省的
evil
模式,如果希望使用类 Vim 的按键方式,可以参考其他一些大牛,比如的 Tec Osaur 的非常有教益的版本。
前言
Doom Emacs 的配置都在变量 doom-user-dir
指向的目录下(根据安装时的选择可能是 ~/.doom.d/
或 ~/.config/doom/
),主要是三个文件:
init.el
:启用/停用 Doom 预先配置好的模块( modules );package.el
:自己另行下载安装的软件包( packages ),或对已启用的软件包版本进行配置,Doom Emacs 使用straight.el
包管理器 →;config.el
:各种配置,大部分预制模块不需要或者只需要少许配置,因为 Doom 已经做了相对好的缺省安排,自行安装的包则需要做相应的配置;当然如果对 Doom 提供的缺省安排不满意,也可以在此动手覆盖。
要更改配置就编辑这三个文件即可,但我们有更好的选择,那就是 literate config ,这是个很酷的功能 1,我们把所有配置代码都用 org-babel
的源码块( source block )写在这个 config.org
文件中(对,你现在看到的就是这个文件本身),然后用 org-babel
提供的 org-babel-tangle-file
函数来自动生成上述三个文件。
emacs --batch --eval "(progn (require 'org) (setq org-confirm-babel-evaluate nil) (org-babel-tangle-file \"~/.config/doom/config.org\"))"
这个 bootstrap 过程只需要做一次,之后 Doom 提供的 literate
模块会自动维护。
无论通过什么方式,配置文件变化后要使用 doom sync
命令来更新环境(这个命令首先会自动从本文件出发重新构造新的配置文件),还可以用 doom doctor
来检查配置中可能存在的问题(最常见的问题是依赖的外部程序找不到或者没配好);Doom Emacs 更新比较频繁,而且鼓励用户滚动更新紧跟开发分支,所以可以定期用 doom upgrade
来更新 Doom Emacs 本身及所有启用的包。
本着不折腾的原则,我对功能实现方案总的选择标准是:
- 能用预制模块的尽量用;
- 能用缺省配置的尽量用;
- 重视持续维护和移植的代价;
- 还是不喜欢
evil
。
预制模块
Doom 提供的大量预制模块大多是当前优选,难以确定的地方也提供了主流方案多选一,并进行了比较恰当的配置,是宝藏,我的配置主要以这些预制模块为主。
如果不清楚某个模块效果到底如何,可以启用试试;官方文档虽然还不完整,但已经挺有用了,可以了解每个模块的基本配置项、依赖和一些常见问题,千万不要错过。
;;; init.el -*- lexical-binding: t; -*-
(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 的 :config
缺省配置大多很好,少数需要定制的后面再说;另外很重要的是启用 literate
模块。
literate
(default +bindings +smartparens)
用户界面
这部分包含了 Doom 的 :completion
:ui
:editor
:emacs
:term
:checkers
:tools
:os
几大类别。
无处不在的自动补全功能,选择最时髦的 vertico
。
(company +childframe) ; the ultimate code completion backend
;;helm ; the *other* search engine for love and life
;;ido ; the other *other* search engine...
;;ivy ; a search engine for love and life
(vertico +icons) ; the search engine of the future
这里面有偏视觉和娱乐的(前者如 hl-todo
ligatures
后者如 doom-quit
),但大多数是很实用的,比如 window-select
大幅强化了多窗口时切换窗口 C-x o
的体验(实际使用的是 ace-window
)。另外 unicode
这个非常重要,如果不打开会有很多中文显示方面的问题。
;;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 +all +defaults) ; tame sudden yet inevitable temporary windows
;;tabs ; a tab bar for Emacs
(treemacs +lsp) ; 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-select +number) ; visually switch windows
workspaces ; tab emulation, persistence & separate workspaces
;;zen ; distraction-free coding or writing
编辑相关的模块这里,我做的最大选择是不用 evil
,当然很多 Spacemacs 和 Doom 用户都会选择这个模式。这里要特别提一下的是 lipsy
,这是针对 Lisp 类语言的一个编辑模式,非常巧妙的实现了单按键的光标快速移动和编辑操作,但不需要切 Vim 模式,我认为比传统的 paredit
和 smartparens
都更高明。
;;(evil +everywhere) ; come to the dark side, we have cookies
file-templates ; auto-snippets for empty files
fold ; (nigh) universal code folding
;;(format +onsave) ; 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 +dirvish +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
Doom 支持几种内置的终端模拟器,效果最好的应该是 vterm
。
;;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
关于拼写检查,我试了各种组合,目前最好的应该还是 flyspell
和 aspell
。
(syntax +childframe) ; tasing you for every semicolon you forget
(spell +aspell +flyspell) ; tasing you for misspelling mispelling
(grammar +lsp) ; tasing grammar mistake every you make
这一组工具我基本都是缺省选择,值得一提的是 magit
真棒,用起来简直上瘾,连写 commit message 都变得香甜了。注意 lsp
要打开,后面配置很多编程语言时会用到。
;;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)
lookup ; navigate your code and its documentation
lsp ; M-x vscode
(magit +forge) ; a git porcelain for Emacs
make ; run make tasks from Emacs
;;pass ; password manager for nerds
pdf ; pdf enhancements
;;prodigy ; FIXME managing external services & code builders
;;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
这两个都是与操作系统有关的补丁,前一个 Mac 用户启用,后一个则都应该启用。
(:if IS-MAC macos) ; improve compatibility with macOS
(tty +osc) ; improve the terminal Emacs experience
编程语言
Doom 提供了几乎所有主流语言的预制模块,大部分都提供了对 LSP 的支持,有些 language server 可以直接在 Emacs 中下载配置,但有些需要另外安装配置。有不少 language server 实现不太好,所以需要自己判断,下面是我启用的一些用得到的语言,大部分都启用了 LSP,可以用 doom doctor
来确认需要另外配置的外部支持软件。另一个值得尝试的选项是 +tree-sitter
,一个强大的静态代码分析插件,官方文档中没有,但实际上存在。另外,无论你主要用什么编程语言, emacs-lisp
和 org
这俩都应该启用,否则还用什么 Emacs 啊!
;;agda ; types of types of types of types...
;;beancount ; mind the GAAP
;;(cc +lsp) ; C > C++ == 1
(clojure +lsp) ; 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 +tree-sitter) ; a language that's lazier than I am
;;hy ; readability of scheme w/ speed of python
;;idris ; a language you can depend on
(json +lsp +tree-sitter) ; 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
;;lean ; for folks with too much to prove
;;ledger ; be audit you can be
;;lua ; one-based indices? one-based indices
(markdown +grip) ; 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 +dragdrop +gnuplot ; organize your plain life in plain text
+hugo +journal
+jupyter +noter
+pandoc +present
+pretty +roam2)
;;php ; perl's insecure younger brother
plantuml ; diagrams for confusing people more
;;purescript ; javascript, but functional
(python +lsp +pyright) ; beautiful is better than ugly
;;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 +rails) ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
(rust +lsp +tree-sitter) ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
;;scala ; java, but good
;;(scheme +guile) ; a fully conniving family of lisps
(sh +tree-sitter) ; 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 +lsp) ; C, but simpler
输入法
增加一些输入法,在 macOS 系统下基本不需要。
;;bidi ; (tfel ot) thgir etirw uoy gnipleh
;;chinese
;;japanese
;;layout ; auie,ctsrnm is the superior home row
万能的 Emacs
有人会用 Emacs 来做任何事,包括电子邮件、IRC、RSS 等等,甚至听歌看视频,目前我只用邮件。
(:if (executable-find "mu") (mu4e +org +gmail))
;;notmuch
;;(wanderlust +gmail)
;;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
基本设置
这里是一些只要 Emacs 启动就需要尽快设置的内容,不需要考虑延迟加载的一些设置,包括一些 Emacs 本身的全局设置,也包括一些需要尽快加载的模块和包的基本设置。
;;; config.el -*- lexical-binding: t; -*-
个人信息
(setq user-full-name "Neo Lee"
user-mail-address "neo@soulhacker.me")
更好的缺省值
Doom 大部分的缺省配置都是优化且实用的,但还是有少量的需要修改。其中 word-wrap-by-category
这个选项是 Emacs 28 新加的,修复了 CJK 语言文本自动换行的老 bug。
(setq-default
window-combination-resize t
x-stretch-cursor t
yas-triggers-in-field t
)
(setq
undo-limit 80000000
auto-save-default t
scroll-preserve-screen-position 'always
scroll-margin 2
word-wrap-by-category t
all-the-icons-scale-factor 1.0
)
(global-subword-mode t)
因为我不用 evil
所以需要给 local leader 一个方便的组合键。
(map! "C-z" nil)
(setq doom-localleader-alt-key "C-z")
如果想显示相对行号,将这个变量值改为 'relative
。
(setq display-line-numbers-type t)
Org 的根目录需要尽早设置。
(setq org-directory "~/Code/Org/")
缺省配置下 treemacs
只会显示文件的 git
状态,改为 'extended
可以显示文件和目录的状态,另一个选项 'deferred
似乎有问题;这个选项要在 treemacs
加载前设置。
(setq +treemacs-git-mode 'extended)
这个开关可以让 treemacs
将空目录尽可能合并为一项来显示,数值代表同时能合并显示的目录数上限。
(after! treemacs
:config
(setq treemacs-collapse-dirs 200))
使用 PlantUML 用本地模式比较靠谱。
(setq plantuml-jar-path (concat (expand-file-name user-emacs-directory) "plantuml/plantuml.jar")
plantuml-default-exec-mode 'jar)
非图形界面下运行
有时我们无法在图形界面下运行 Emacs,这时就需要下面这些 tweak :
(unless (display-graphic-p)
(map!
"C-c b" 'treemacs-select-window
[mouse-4] 'scroll-down-line
[mouse-5] 'scroll-up-line))
字体和主题
(setq +main-font "Iosevka Fixed")
(setq +unicode-font "Sarasa Fixed SC")
(setq doom-font (font-spec :family +main-font :size 16 :weight 'light)
doom-big-font (font-spec :family +main-font :size 20 :weight 'light)
doom-variable-pitch-font (font-spec :family +main-font) ;; inherits :size from doom-font
doom-serif-font (font-spec :family +main-font :weight 'light)
doom-unicode-font (font-spec :family +unicode-font)
)
(setq doom-themes-enable-bold t
doom-themes-enable-italic t)
随系统切换 light 和 dark mode 主题,利用 macOS 下 emacs-plus
增加的 ns-system-appearance-change-functions
钩子来实现。
;; (setq +light-theme 'doom-one-light
;; +dark-theme 'doom-vibrant)
;; (setq +light-theme 'modus-operandi
;; +dark-theme 'modus-vivendi)
;; (setq +light-theme 'doom-acario-light
;; +dark-theme 'doom-acario-dark)
(setq +light-theme 'doom-flatwhite
+dark-theme 'doom-city-lights)
;; (setq +light-theme 'doom-opera-light
;; +dark-theme 'doom-opera)
;; (setq +light-theme 'doom-nord-light
;; +dark-theme 'doom-nord)
(defun +apply-theme (appearance)
"Load theme, taking current system APPEARANCE into consideration."
(mapc #'disable-theme custom-enabled-themes)
(pcase appearance
('light (load-theme +light-theme t))
('dark (load-theme +dark-theme t))))
(add-hook! 'ns-system-appearance-change-functions '+apply-theme)
其他关联主题和字体设置, treemacs
适配 nerd-icons
似乎已经合并进 Doom ,不再需要手动设置。
(doom-themes-org-config)
;;(after! treemacs
;; :config
;; (doom-themes-treemacs-config)
;; (treemacs-load-theme "nerd-icons")
;; )
状态条
Doom 缺省的状态条是 doom-modeline
,可能是几个状态条方案里最漂亮的一个,不过这问题见仁见智,选自己喜欢的就好。目前版本缺省配置就挺好,以前的 hacking 都被我删了。
切换窗口
预制模块 ace-window
加强了 C-x o
的功能,当多于2个窗口时会高亮显示每个窗口左上角的一个字母,按下这个字母就切换到对应窗口,但这个字母的显示非常小,很不醒目,解决方案很简单,定制这个字母显示的样式,把它变大再加上红色底,就醒目了。 ace-window
还有不少别的功能,比如激活之后可以按 x
来删除当前窗口,按 m
来与选择的窗口交换内容等,具体可参考官网说明。
(custom-set-faces!
'(aw-leading-char-face
:foreground "white" :background "red"
:weight bold :height 1.5 :box (:line-width 10 :color "red")))
花式符号
预制模块 ligature
及其 +extra
选项会用一些漂亮的文字符号来替代一些固定文本组合,比如用 λ
代替 Lisp 类语言里的 lambda
和 fn
关键字,用 𝒇
代替 function
关键字,用 ⟻
代替 import
关键字等,没啥用就图个乐,不过在某些语言里这个玩意儿会影响理解和编码,所以对这些语言禁用掉是个明智选择。
(defun +appened-to-negation-list (head tail)
(if (sequencep head)
(delete-dups
(if (eq (car tail) 'not)
(append head tail)
(append tail head)))
tail))
(when (modulep! :ui ligatures)
(setq +ligatures-extras-in-modes
(+appened-to-negation-list
+ligatures-extras-in-modes
'(not c-mode c++-mode emacs-lisp-mode python-mode scheme-mode racket-mode rust-mode)))
(setq +ligatures-in-modes
(+appened-to-negation-list
+ligatures-in-modes
'(not emacs-lisp-mode scheme-mode racket-mode))))
语言服务器
Emacs 支持微软开源的 LSP 接口以及各种各样的语言服务器时间还不长,问题也有一些,不过未来可期。 lsp-ui
提供了大量的定制选项。
(after! lsp
:custom
(setq lsp-log-io nil
lsp-print-io nil
lsp-document-sync-method 'incremental ; none, full, incremental, or nil
lsp-response-timeout 10
)
:hook
(lsp-mode . lsp-ui-mode))
(after! lsp-ui
:custom
(setq
; lsp-enable-symbol-highlighting nil
; lsp-lens-enable nil
lsp-headerline-breadcrumb-enable nil
; lsp-diagnostics-provider :none
; lsp-completion-provider :none
lsp-ui-doc-enable nil
lsp-ui-doc-show-with-mouse nil
lsp-ui-doc-show-with-cursor nil
lsp-ui-doc-header nil
lsp-ui-doc-include-signature nil
lsp-ui-doc-position 'at-point ;; top, bottom, or at-point
lsp-ui-doc-max-width 120
lsp-ui-doc-max-height 30
lsp-ui-doc-use-childframe t
lsp-ui-doc-use-webkit t
lsp-ui-sideline-enable nil
lsp-ui-sideline-show-code-actions t
lsp-ui-sideline-show-hover nil
lsp-ui-sideline-ignore-duplicate t
lsp-ui-sideline-show-symbol t
lsp-ui-sideline-show-diagnostics t
lsp-ui-peek-enable t
lsp-ui-peek-fontify 'on-demand ;; never, on-demand, or always
lsp-ui-imenu-enable t
lsp-ui-imenu-kind-position 'top
)
:preface
(defun +toggle-lsp-ui-doc ()
(interactive)
(if lsp-ui-doc-mode
(progn
(lsp-ui-doc-mode -1)
(lsp-ui-doc-hide))
(progn
(lsp-ui-doc-mode 1)
(lsp-ui-doc-show))
))
:bind
(map! :map lsp-mode-map
("C-c l r" #'lsp-ui-peek-find-references)
("C-c l j" #'lsp-ui-peek-find-definitions)
("C-c l i" #'lsp-ui-peek-find-implementation)
("C-c l m" #'lsp-ui-imenu)
("C-c l s" #'lsp-ui-sideline-mode)
("C-c l d" #'+toggle-lsp-ui-doc))
)
其他一些语言服务器的配置。
(add-hook! 'js-mode-hook (add-to-list 'flycheck-disabled-checkers 'lsp))
(setq lsp-javascript-validate-enable nil
lsp-javascript-suggest-enabled nil
lsp-javascript-suggestion-actions-enabled nil
lsp-zig-zls-executable "~/Code/Repo/zls/zig-out/bin/zls")
拼写检查
无论使用 spell-fu
还是 flyspell
都需要配置个人词汇表的保存位置,我都放在 doom-user-dir
目录下了。另外 flyspell
激活错词修改菜单的缺省操作是鼠标中键(滚轮),MacBook 上没这玩意儿啊,所以我给改成 Command+鼠标主键
了。
(after! spell-fu
(defun +spell-fu-register-dictionary (lang)
"Add `LANG` to spell-fu multi-dict, with a personal dictionary."
;; Add the dictionary
(spell-fu-dictionary-add (spell-fu-get-ispell-dictionary lang))
(let ((personal-dict-file (expand-file-name (format "aspell.%s.pws" lang) doom-user-dir)))
;; Create an empty personal dictionary if it doesn't exists
(unless (file-exists-p personal-dict-file) (write-region "" nil personal-dict-file))
;; Add the personal dictionary
(spell-fu-dictionary-add (spell-fu-get-personal-dictionary (format "%s-personal" lang) personal-dict-file))))
(add-hook! 'spell-fu-mode-hook
(+spell-fu-register-dictionary "en")
))
(after! flyspell
:config
(setq ispell-personal-dictionary (expand-file-name "aspell.pws" doom-user-dir))
:bind
(map!
[s-down-mouse-1] 'flyspell-correct-word)
)
电子邮件
在配置好 isync
和 mu
之后(比较 tricky 的部分是使用 GnuPG 来加密密码,可以参考这篇以及这篇),在 Emacs 中使用 mu4e
就很简单了。
(set-email-account! "neo"
'((mu4e-sent-folder . "/neo/Sent")
(mu4e-drafts-folder . "/neo/Drafts")
(mu4e-trash-folder . "/neo/Trash")
(mu4e-refile-folder . "/neo/All Mail")
(smtpmail-smtp-user . "neo@soulhacker.me")
(user-mail-address . "neo@soulhacker.me")
(mu4e-compose-signature . "--\nNeo Lee a.k.a. soulhacker"))
t)
(setq send-mail-function 'smtpmail-send-it)
(setq smtpmail-smtp-server "localhost")
(setq smtpmail-smtp-service 1025)
(setq smtpmail-stream-type 'starttls)
(setq org-msg-signature "\n#+begin_signature\n--\nNeo Lee a.k.a. soulhacker\n#+end_signature")
(after! mu4e
(setq mu4e-headers-auto-update t
mu4e-update-interval 120
mu4e-view-show-images t
mu4e-use-fancy-chars t)
)
补充软件包
Doom 的缺省配置和预制模块能覆盖我绝大部分需求,所以需要我另外添加的软件包很少,这些软件包需要写在 packages.el
里。
;;; packages.el -*- no-byte-compile: t; -*-
窗口移动
用快捷键快速在几个窗口间交换 buffer,比如同时打开 .c
和 .h
文件,先在左边窗口编辑 .h
文件,然后把右边窗口的 .c
文件换过来进行编辑,使用这个包用一个组合键就能做到。其实预制模块里的 ace-window
也可以做类似的事情,但 buffer-mover
一个按键搞定更快。这里我用 :recipe
子句来禁用这个包的编译,因为这个包有个 bug,编译后会报错。
(package! buffer-move :recipe (:build (:not compile)))
安装和启用之后,在 config.el
里配置自己习惯的按键组合(我用 Control+Command+方向键
):
(map!
[C-s-up] #'buf-move-up
[C-s-down] #'buf-move-down
[C-s-left] #'buf-move-left
[C-s-right] #'buf-move-right
)
锁定可靠版本
缺省的 Doom Emacs 有着过于激进的版本跟进策略,有些包的 master 分支并不可信,一旦出现问题我们需要锁定在可靠的版本上。
org-mode
最新的 org-mode
9.7 开发分支有不少问题,而 Doom Emacs 缺省会跟着 org-mode
的 master 分支,经常会破坏一些本来正常工作的软件包,所以我们目前需要锁在 9.6 最新稳定版本。
(package! org :pin "071c6e986c424d2e496be7d0815d6e9cd83ae4e6")
添加另外一些与 org-mode
相关的软件包。
(package! jupyter)
(package! ox-hugo)
知识网络
org-roam-ui
→ 提供强大的知识网络笔记系统 org-roam
的网络可视化,并可与 org-roam
紧密整合。注意 org-roam-ui
只支持 org-roam v2
,如果使用 org-roam v1
的话请用 org-roam-server
→。
(package! org-roam-ui)
公式预览
xenops
和 org-fragtog
都是用来在 org-mode
里实时渲染显示 \(LaTeX\) 公式的,它们都会实时将公式渲染为图片(前者用 SVG 格式,后者用 PNG 格式)并缓存,然后显示出来,区别在于 org-fragtog
是同步模式,渲染时会卡住 Emacs,等它好了你才能继续操作,而 xenops
使用 emacs-aio
实现异步渲染,它干活的时候你也可以继续干活,它渲染好就会自动显示出来。
(package! xenops)
编程语言 - Clojure
clj-refactor
这个包需要和后端 Clojure 的 refactor-nrepl
包版本同步,且它们更新都挺频繁,所以最好解放版本限制。
(unpin! clj-refactor)
编程语言 - Python
Doom 其实有个大一统的源代码格式美化模块( :editor format
),但我还没时间深入研究,而且这个模块和 LSP 是彼此独立的,不知道有什么坑,所以没开。针对比较有这个需求的 Python 语言单独加了 black
这个懒汉的选择,作为 LSP 方案的保底。
(package! python-black)
小玩具
keycast
可以跟踪并显示所有按键操作,在录制与 Emacs 有关的 screencast 时有用,其实我用的很少,ScreenFlow 提供更漂亮的解决方案,但在 Linux 下是个不错的方案。
(package! keycast)
这个包允许你在 Emacs 里查询、下载和查看 xkcd 漫画,嘿嘿嘿!
(package! xkcd)
Org Mode
org-mode
是 Emacs 的招牌应用,一个全副武装的 org-mode
可以在一致的环境下完成日程计划、笔记、博客、论文等各种写作任务,不仅可以写出文本、结构化列表、表格,还可以插入图片、 \(LaTeX\) 公式、可运行的程序代码、可追踪的任务,还可以借助 org-roam
实现笔记的知识网络化。这些功能是以 org-mode
为中心的一组软件包配合实现的,所以配置也相对多一点。
基本配置
设置 org-agenda
使用的 Org 文档的位置,可以指定目录和文件名 pattern 把所有 .org
文件放进去:
(setq org-agenda-files (directory-files-recursively org-directory "\\.org$"))
但通常还是指定几个分类任务文件就好了。
(after! org
(setq org-agenda-files
(list (expand-file-name "Inbox.org" org-directory)
(expand-file-name "Private.org" org-directory)
(expand-file-name "Work.org" org-directory)
(expand-file-name "Projects.org" org-directory)
(expand-file-name "Notes.org" org-directory)
)))
另一个和 org-agenda
有关的场景,就是用 org-roam
写东西很快就会有非常多的文件,有些笔记一时写不完可以加上 TODO
标签,然后就需要一个又快又方便的手段来整理这些 TODO
列表,对这个问题 Emacs 社区有位 Boris Buliga(a.k.a d12frosted,这位还是 homebrew-emacs-plus
和 flyspell-correct
的维护者)给出了一个漂亮的解决方案,简单说就是检测到 TODO
时自动给文件打一个 project
标签(如果检测到当前文件所有 TODO
都没有了就自动移除标签),然后直接通过 org-roam 的数据库查询动态将这些文件添进 org-agenda
文件列表。从他分享的 gist 克隆代码到本地,然后加载就可以了。
(after! org-agenda
(load! "lisp/vulpea-agenda"))
原来的版本有个问题,就是我们上面给变量 org-agenda-files
指定的文件会被移除,所以我做了一点 patch 之后才合用 →。
Org 文档导出 \(LaTeX\) 文档时需要做一些配置,一是引入 ctex
包,否则中文处理会有问题;二是配置一下带链接文本的格式,不然会是带红色框的文本,非常恐怖;另外是一些 \(LaTeX\) 的常规设置。
(after! org
(setq org-latex-default-packages-alist
(remove '("AUTO" "inputenc" t) org-latex-default-packages-alist))
(setq org-latex-pdf-process '("xelatex -interaction nonstopmode %f"
"xelatex -interaction nonstopmode %f"))
(setq org-latex-packages-alist
'(("" "ctex" t)))
(setq org-latex-hyperref-template
"\\hypersetup{linktoc=all,colorlinks=true,urlcolor=blue,linkcolor=blue}")
)
缺省的 \(LaTeX\) 模板的目录之后直接会接上正文,但一般的排版习惯会希望正文另起一页,这个简单定制一下就可以了:
(setq org-latex-toc-command "\\tableofcontents \\clearpage\n\n")
在编辑 Org 文档时,经常点击链接跳转到另一篇笔记,这时候快速返回可以设置为快捷键,我用的是 Control-Command-b
。
(after! org
(map! :map org-mode-map
"C-s-b" #'org-mark-ring-goto
))
维护很多 Org 笔记时,记下笔记最后编辑时间会很有用,这个事情很适合交给 Emacs 自己做,可以利用 Emacs 的 Time Stamp 功能实现。
(after! org
(add-hook 'org-mode-hook
(lambda ()
(setq-local time-stamp-active t
time-stamp-line-limit 18
time-stamp-start "^#\\+last_modified: [ \t]*"
time-stamp-end "$"
time-stamp-format "\[%Y-%m-%d %a %H:%M:%S\]")
(add-hook 'before-save-hook 'time-stamp nil 'local))))
需要这个功能的 Org 笔记在 header 里加入下面一行即可(在笔记的前18行都可以)。
#+last_modified: [ ]
运行代码块
借助 org-babel
来实现可运行的代码块,可以在代码块中应用对应的 major mode ,提供良好的语法高亮及编辑功能。
(after! org-src
(add-to-list 'org-src-lang-modes '("plantuml" . plantuml)))
另外我还会用 Jupyter 来运行不同语言引擎。注意第一段里 :kernel
参数,需要设为系统里存在的某个 Jupyter kernel 的名字,且系统环境里应该装有 Jupyter(即系统 PATH
中能找到 jupyter
程序);使用中如果需要 pip
安装 Python 软件包,也是安装在这个 kernel 环境下。
(setq org-babel-default-header-args:jupyter-python '((:async . "no")
(:session . "jp")
(:kernel . "wop")))
(after! org
(require 'org-tempo)
(org-babel-do-load-languages 'org-babel-load-languages
'((emacs-lisp . t)
(python . t)
(shell . t)
(latex . t)
(sql . t)
(sqlite . t)
(calc . t)
(jupyter . t)))
)
(after! org-src
(dolist (lang '(python))
(cl-pushnew (cons (format "jupyter-%s" lang) lang)
org-src-lang-modes :key #'car))
)
知识网络
org-roam
其实我刚开始用 org-roam
,所以只有最简单的配置,一些复杂的模板之类的都还没开始用。这里最重要的配置是 Roam 笔记的根目录,另外我稍微修改了下缺省的 capture template ,在元数据里加了 tag 。
(use-package! org-roam
:custom
(org-roam-directory (file-truename "~/Code/Org/roam/"))
(org-roam-completion-everywhere t)
:bind (("C-c n l" . org-roam-buffer-toggle)
("C-c n f" . org-roam-node-find)
("C-c n g" . org-roam-graph)
("C-c n i" . org-roam-node-insert)
("C-c n c" . org-roam-capture)
("C-c n j" . org-roam-dailies-capture-today))
:config
(setq org-roam-capture-templates
`(("d" "default" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
"#+title: ${title}\n#+filetags:\n\n")
:unnarrowed t)))
(org-roam-db-autosync-mode)
(require 'org-roam-protocol))
org-roam-ui
这个知识网络的可视化会显示在浏览器中,通过 websocket 与 Emacs 通信。
(after! org-roam
(use-package! websocket)
(use-package! org-roam-ui
:config
(setq org-roam-ui-sync-theme t
org-roam-ui-follow t
org-roam-ui-update-on-save t
org-roam-ui-open-on-start t))
)
实时公式预览
xenops
基本配置,注意公式显示的大小可用变量 xenops-math-image-scale-factor
微调。
(when (display-graphic-p)
(use-package! xenops
:hook (latex-mode . xenops-mode)
:hook (LaTeX-mode . xenops-mode)
:hook (org-mode . xenops-mode)
:defer t
:config
(map! :map xenops-mode-map
:n "RET" #'xenops-dwim)
(setq xenops-cache-directory (concat doom-cache-dir "xenops/")
xenops-reveal-on-entry t
xenops-math-latex-process 'dvisvgm
xenops-math-image-scale-factor 1.5
)))
其他配置
Smartparens
因为我用 lipsy
所以其实 smaertparens
用的不多,不过作为保底,还是配了一下常用按键组合。
(map!
(:after smartparens
(:map smartparens-mode-map
"C-M-f" #'sp-forward-sexp
"C-M-b" #'sp-backward-sexp
"C-M-u" #'sp-backward-up-sexp
"C-M-d" #'sp-down-sexp
"C-M-p" #'sp-backward-down-sexp
"C-M-n" #'sp-up-sexp
"C-)" #'sp-forward-slurp-sexp
"C-}" #'sp-forward-barf-sexp
"C-(" #'sp-backward-slurp-sexp
"C-{" #'sp-backward-barf-sexp
"C-M-s" #'sp-splice-sexp
))
)
Python
几个小工具方便一些常用操作,包括用 black
格式化源代码。
(after! python
:preface
(defun +python-make-fstring ()
"Change string to fstring"
(interactive)
(when (nth 3 (syntax-ppss))
(let ((p (point)))
(goto-char (nth 8 (syntax-ppss)))
(insert "f")
(goto-char p)
(forward-char))))
(defun +python-format-buffer ()
"Format python buffer with black"
(interactive)
(python-black-buffer))
:bind
(map! :map python-mode-map
"C-c x s" #'+python-make-fstring
"C-c x f" #'+python-format-buffer))
Clojure
CIDER 是 Clojure 的 REPL ,缺省会作为一个 popup 显示在窗口下方,但一般来说习惯于左右分屏,所以需要修改;后面试 CIDER 的几个设置,一个可以在切换 namespace 的时候自动加载对应代码,另一个指定 =cider-jack-in
时使用 deps.edn
中哪个配置 alias 。
(after! cider
(set-popup-rules!
'(("^\\*cider-repl"
:side right
:width 100
:quit nil
:ttl nil)))
(setq cider-repl-require-ns-on-set t)
(setq cider-clojure-cli-aliases ":cider")
)
Prolog
这个语言比较小众,所以需要设置一下缺省的文件后缀,自动对应 major-mode ,一般来说后缀用 pl
和 pro
都可以,前者 Perl
语言会用,所以我们用后一个:
(setq auto-mode-alist
(append '(("\\.pro" . prolog-mode))
auto-mode-alist))
Web
web-mode
的 auto indentation 经常有奇怪的行为,还是关了好。
(setq web-mode-enable-auto-indentation nil)
keycast
对 doom-modeline
需要做一些 tweak 才能用。
(use-package! keycast
:commands keycast-mode
:config
(define-minor-mode keycast-mode
"Show current command and its key binding in the mode line."
:global t
(if keycast-mode
(progn
(add-hook 'pre-command-hook 'keycast--update t)
(add-to-list 'global-mode-string '("" keycast-mode-line " ")))
(remove-hook 'pre-command-hook 'keycast--update)
(setq global-mode-string (remove '("" keycast-mode-line " ") global-mode-string))))
(custom-set-faces!
'(keycast-command :inherit doom-modeline-debug :height 1.0)
'(keycast-key :inherit custom-modified :height 1.1 :weight bold)))
xkcd
干活累了看看漫画,不亦乐乎!
(use-package! xkcd
:commands (xkcd-get xkcd)
:config
(setq xkcd-cache-dir (expand-file-name "xkcd/" doom-cache-dir)
xkcd-cache-latest (expand-file-name "xkcd/latest" doom-cache-dir)))