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 +icons); 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 +childframe +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 ; 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)
幻灯片
dslide
可以直接把 org-mode
变成幻灯片。
(package! dslide)
编程语言 - Clojure
clj-refactor
这个包需要和后端 Clojure 的 refactor-nrepl
包版本同步,且它们更新都挺频繁,所以最好解放版本限制。
(unpin! clj-refactor)
编程语言 - Python
Doom 其实有个大一统的源代码格式美化模块( :editor format
),但我还没时间深入研究,而且这个模块和 LSP 是彼此独立的,不知道有什么坑,所以没开。针对比较有这个需求的 Python 语言单独加了 black
这个懒汉的选择,作为 LSP 方案的保底。
(package! python-black)
大语言模型
近期涌现了一批将大语言模型 Large Language Model 的能力整合进 Emacs 的工具,我们也可以来试试。
(package! gptel)
(package! ellama)
小玩具
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
)))
大语言模型
我的 ellama
配置,使用本地运行的一个与 OpenAI API 兼容的服务,它可以来自 Ollama
或者基于 llama.cpp
的各种工具(比如 LM Studio)。
(use-package! gptel
:init
(setq
gptel-model 'default
gptel-backend (gptel-make-openai "local"
:stream t
:protocol "http"
:host "localhost:1234"
:models '(default))))
(use-package! ellama
:bind ("C-c l" . ellama-transient-main-menu)
:init
(setq ellama-sessions-directory "~/.config/emacs/.local/cache/ellama-sessions")
; (setopt ellama-keymap-prefix "C-c l")
(setq llm-warn-on-nonfree nil)
(setq ellama-fill-paragraphs nil)
(setq ellama-long-lines-length 80)
(setq ellama-auto-scroll t)
(setopt ellama-language "Chinese") ; language to translate to
(require 'llm-openai)
(setopt ellama-provider
(make-llm-openai-compatible :url "http://localhost:1234/v1"
:chat-model "qwen2.5-coder-32b-instruct-mlx"
;; :chat-model "qwen2.5-coder-14b-instruct"
:embedding-model "text-embedding-nomic-embed-text-v1.5")))
幻灯片
dslide
基本配置。
(use-package! dslide
:hook
((dslide-start
.
(lambda ()
(org-fold-hide-block-all)
(setq-default x-stretch-cursor -1)
(redraw-display)
(blink-cursor-mode -1)
(setq cursor-type 'bar)
;;(org-display-inline-images)
;;(hl-line-mode -1)
(text-scale-increase 2)
(read-only-mode 1)))
(dslide-stop
.
(lambda ()
(blink-cursor-mode +1)
(setq-default x-stretch-cursor t)
(setq cursor-type t)
(text-scale-increase 0)
;;(hl-line-mode 1)
(read-only-mode -1))))
:config
(setq dslide-margin-content 0.5)
(setq dslide-animation-duration 0.5)
(setq dslide-margin-title-above 0.3)
(setq dslide-margin-title-below 0.3)
(setq dslide-header-email nil)
(setq dslide-header-date nil)
(define-key org-mode-map (kbd "<f8>") #'dslide-deck-start)
(define-key dslide-mode-map (kbd "<f9>") #'dslide-deck-stop)
)
其他配置
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)))