目录
版本
Emacs 是我最常用的编码工具。我使用的是 Emacs 27 版本,自己编译,编译选项为
./configure --with-dbus --with-gif --with-jpeg --with-png --with-rsvg --with-tiff --with-xft --with-xpm --with-xml2 --with-xwidgets --with-modules --with-native-compilation --with-pgtk --with-json --with-mailutils CFLAGS="-O3 -mtune=native -march=native -fomit-frame-pointer"
配置文件主体
对于新手而言,不建议自己从头开始配置。因为很有可能会迷失在配置文件之中,建议先使
用其他人的配置,同时通过学习其他人的代码来进行学习,在一定时间之后再逐渐积累形成
自己的配置。就我个人而言最开始使用的是purcell的配置文件。
https://github.com/purcell/emacs.d 又尝试了Spacemacs,
https://github.com/syl20bnr/spacemacs, 但是由于Spacemacs启动速度过慢,同时有过多
的冗余设置,加入自己的配置也不够方便,因此,开始了自己的配置。从各个repo中抄了很
多的代码,途中也学习了不少关于Emacs和Lisp的知识。最近发现了
Doom-emacs,https://github.com/hlissner/doom-emacs/。 相比于Spacemacs,Doom-emacs
启动较快,同时利用Doom-emacs提供的各种宏,能够更好地在不改变原始仓库代码的同时进
行配置。 目前我的配置就建立在Doom-emacs的基础上 。目前我已经重新使用完全由自己
编码的配置来换取精确的控制。就我现在来看,我更加推荐 Purcell 的配置,并从他的配
置中逐步学习。这是因为他的配置额外的结构较少,不容易像学习 spacemacs 一样陷入配
置的重重引用中。
好的字体是成功的一半
推荐字体 更纱黑体 ,中英文等宽等高避免Org-mode的各种错位。虽然个人感觉英文字体 没有那么好看,但还是可以接受的。(中文还是非常漂亮的)附链接:Sarasa Gothic
可以这样配置(中文使用更纱黑体,英文使用 Iosevka 与 Source Code Pro 的混合):
(defvar zenith-font (font-spec :family "Iosevka Term SS09" :size 16 :weight 'semi-bold))
(defvar zenith-unicode-font (font-spec :family "Sarasa Term SC" :weight 'bold))
(setq inhibit-x-resources t)
(defun zenith/init-font ()
(add-to-list 'default-frame-alist `(font . ,(font-xlfd-name zenith-font)))
(set-fontset-font t 'unicode zenith-unicode-font nil 'prepend))
(zenith/init-font)
在GTK环境中,EMACS在打开后会根据GConf重新设置字体格式,因此要有效的设置字体必须在.emacs或者init.el中加入
(define-key special-event-map [config-changed-event] 'ignore)
Writeroom-mode and others
至少在Ubuntu 18.04中,这些使文字居中的模式(Writeroom )不能够很好地与ibus协作, ibus的选单会发生偏移。原因和解决方案有待进一步观察。(目前我已经使用 darkroom-mode 但是会遇到同样的问题)
事实上,这个问题可以使用 Emacs 的内置输入法来解决。但是内置输入法太弱,因此我们 使用 https://github.com/tumashu/pyim 。事实上,pyim 除了输入法外还提供了很多非常 有用的功能。比如根据环境切换中英文状态,比如将英文字符串转变为输入法的输入等。配 置可以参考我的 config/init-pyim.el 。其中我使用的 pyim 后端是 rime, rime 是一个 非常好用的输入法。
在Emacs中使用使用Microsoft Python Language Server
Why
目前来看 Microsoft 的 Python Language Server 由于使用 C# 写成,因此速度最快,卡顿较少,补全效果较好。
How
主要的过程都在这篇文章之中:https://vxlabs.com/2018/11/19/configuring-emacs-lsp-mode-and-microsofts-visual-studio-code-python-language-server/ ,但是有必要对于一些步骤进行改进。
首先在代码中对于 lsp-ui-sideline–format-info 和 lsp-ui-doc–extract 这两个函数添加了 advice,而这advice在输入的 doc 为 nil 时会报错,因此在ccls时这两个函数不能够正常工作,因此需要添加 (when doc ……) 这样的判定条件才能够保证其正确性。 另一方面,在我的电脑上,python language server 在补全时会加入一些莫名其妙的东西。在字符较短时,会列出所有的关键字(不知道为什么)。因此需要修改 company-transformers 为了保证模糊匹配的效果,加入了 company-flx-transformer 来对所获得的序列进行排序。效果尚可。
补充
由于目前emacs的lsp-mode速度较慢,因此我在使用Doom-emacs并没有开启lsp选项 。 目前
我使用的是 nox 是 eglot 的阉割版,自动支持 mspyls。
Org-mode
一些有用的功能
首先就是 org-bullets 还有就是为了配合 ivy 等补全工具设置
(setq org-outline-path-complete-in-steps nil
org-goto-interface 'outline-path-completion)
放大 latex preview 的公式,并让公式背景与主体适应:
(setq org-format-latex-options (plist-put org-format-latex-options :scale 1.5))
(setq-default
org-format-latex-options
(plist-put org-format-latex-options
:background
(face-attribute (or (cadr (assq 'default face-remapping-alist))
'default)
:background nil t)))
在字体使用粗体时,emphasis 的效果加粗无法很好的显示出来。因此我通过配置 org-emphasis-alist改变了原来应该加粗的字体的颜色,并使得能够这一效果可以换行。代码如下:
(add-to-list 'org-emphasis-alist
'("*" (:foreground "pink")))
(setcar (nthcdr 4 org-emphasis-regexp-components) 4)
(org-set-emph-re 'org-emphasis-regexp-components org-emphasis-regexp-components)
还有很多细碎的小配置就不赘述了。
Org mode Easy Templates
在 Org-mode 9.2 中,easytemplate的使用方式发生了改变。从 <s <TAB>
;变成了 ~C-c C-,~ 。
不得不说 Org-babel 是个很好用的功能,可以探索一下。
org-refile
由于我个人倾向于将笔记放在一起,因此使用了一个笔记本文件夹,为了使得 refile 能够 与此配合需要一些配置。
(defun zenith/refile-targets-notes ()
(directory-files zenith/note-directory t ".*\\.org\\'"))
(setq-default org-refile-targets
'((nil :maxlevel 3)
(org-agenda-files :maxlevel 3)
(zenith/refile-targets-notes :maxlevel 3)))
GTD
Emacs Org-mode 的一个卖点就是 GTD。其中需要 org-agenda, org-capture, org-tags 等 部分互相配合。关于关键词可以参考,在 Tag 中创建了一个 Group Must 来管理不得不做 的一些事的 Tag:
(setq org-todo-keywords
'((sequence "TODO(t)" "WAITING(w@/!)" "PAUSE(p)" "SOMEDAY(s)" "NEXT(n)" "|" "DONE(d!)" "CANCELLED(c@)")
(sequence "[ ](T)" "[-](P)" "[?](m)" "|" "[X](D)"))
org-todo-keyword-faces
'(("[-]" :inherit (font-lock-constant-face bold))
("[?]" :inherit (warning bold))
("WAITING" :inherit bold)
("LATER" :inherit (warning bold))))
(setq org-capture-templates
'(
("h" "Homework" entry (file+headline "~/Dropbox/task.org" "Homework")
"* TODO %? :Homework:\n")
("s" "Schedule" entry (file+headline "~/Dropbox/task.org" "Schedule")
"* %?\n")
("r" "Project" entry (file+headline "~/Dropbox/task.org" "Project")
"* TODO %?\n")
("q" "Question" entry (file+headline "~/Dropbox/task.org" "Question")
"* TODO %? :Question:\n")
("d" "Idea" entry (file+headline "~/Dropbox/task.org" "Idea")
"* TODO %? :Idea:\n")))
;; Org tag
(setq org-tag-alist
'(("Improvement" . ?i)
(:startgrouptag)
("Must")
(:grouptags)
("Homework" . ?h)
("Job" . ?j)
(:endgrouptag)
("Personal" . ?p)
("Question" . ?q)
("Idea" . ?d)))
关于 Agenda 可以参考,在代码中创建了一个 “b” 视图,并且将紧急的事情,最近的 Deadline 较遥远的 Deadline 等整合到了一起。
;; Org agenda settings
(setq org-agenda-start-on-weekday nil
org-agenda-skip-scheduled-if-deadline-is-shown t
org-agenda-skip-deadline-prewarning-if-scheduled (quote pre-scheduled)
org-agenda-skip-scheduled-if-done t
org-agenda-skip-deadline-if-done t
org-agenda-span 7
org-agenda-compact-blocks t
org-agenda-show-all-dates nil
org-deadline-warning-days 365
org-agenda-show-future-repeats t
org-agenda-window-setup 'only-window)
(setq org-agenda-custom-commands
'(("b" "Agenda View" ((tags "AGENDAHEADER"
((org-agenda-overriding-header "Today's Schedule:")))
(agenda ""
((org-agenda-show-all-dates t)
(org-agenda-span 'day)
(org-deadline-warning-days 0)
(org-agenda-start-day "+0d")))
(todo "NEXT"
((org-agenda-overriding-header "========================================\nNext Tasks:")))
(tags-todo "Must/!-NEXT"
((org-agenda-overriding-header "========================================\nMust Do:")))
(tags "BEFOREWEEKGLANCE"
((org-agenda-overriding-header "========================================\nNext Week Glance:")))
(agenda ""
((org-agenda-show-all-dates t)
(org-agenda-span 6)
(org-agenda-start-day "+1d")))
(tags "BEFOREDEADLINE"
((org-agenda-overriding-header "========================================\nFar Away Tasks:")))
(agenda ""
((org-agenda-span 180)
(org-agenda-time-grid nil)
(org-agenda-show-all-dates nil)
(org-agenda-entry-types '(:deadline :scheduled))
(org-agenda-start-day "+7d")))))
("i" "Improvement" ((tags-todo "Question"
((org-agenda-overriding-header "Unsolved Questions:")))
(tags-todo "Improvement" ((org-agenda-overriding-header "\n\nImprovment:")))
(tags-todo "Idea+TODO<>\"NEXT\"|Personal+TODO<>\"NEXT\""
((org-agenda-overriding-header "\n\nPersonal Project:")))))))
GTD 补充
由于 Org-mode 本身并不带有更加可视化的视图以及提醒系统,我个人通过 org-icalendar 和 KOrganizer 来做到这一点。确切来说,在使用 Org-agenda 时会将目前的配置导出到 icalendar 文件中,然后再使用 KOrganizer 读取来达到目的。
;; ox-icalendar
(with-eval-after-load 'ox-icalendar
(setq org-icalendar-combined-agenda-file (expand-file-name "~/Dropbox/agenda.ics")
org-icalendar-include-todo t
org-icalendar-use-deadline '(event-if-not-todo todo-due)
org-icalendar-use-scheduled '(event-if-not-todo todo-start)
org-icalendar-alarm-time 15
org-icalendar-store-UID t
org-agenda-default-appointment-duration 90))
(add-hook 'org-agenda-finalize-hook 'org-icalendar-combine-agenda-files)
Org-attach
attach 是将文件附与某个 headline 的方法,这样可以在 Org-mode 中将标题和文件关联 起来,可以看成是另一种 Link. 我定义了一个方法,这个方法可以在 org-agenda 中更快 地打开 attach 的文件,这也是它相对于一般的 Link 的优势所在。
;; Org attach
(setq org-attach-method 'lns)
(zenith/autoload '(org-attach org-attach-open) "org-attach")
(defun org-agenda-attach-open ()
"Open attachment with one-key stroke."
(interactive)
(unless (eq major-mode 'org-agenda-mode)
(let ((debug-on-quit nil))
(signal 'quit '("This was written expressly for `*Org Agenda*`."))))
(let ((marker (or (get-text-property (point) 'org-hd-marker)
(get-text-property (point) 'org-marker))))
(if marker
(save-excursion
(set-buffer (marker-buffer marker))
(goto-char marker)
(org-back-to-heading t)
(call-interactively 'org-attach-open))
(error "No task in current line"))))
Org-id
一般的 Org-mode 之间的 Link 是依赖于文件结构的。这样当文件结构变化时其链接有可能 会失效,因此 Org-Mode 提供了 Org-id 这一模块来实现更加健壮的链接。
(setq org-id-track-globally t
org-id-link-to-org-use-id t
org-id-locations-file (expand-file-name ".org-id-locations" zenith-emacs-local-dir))
(with-eval-after-load 'org-id
(setq org-id-extra-files (directory-files-recursively zenith/note-directory ".*\\.org"))
(org-id-update-id-locations)
(defun org-id-complete-link (&optional arg)
"Create an id: link using completion"
(concat "id:"
(org-id-get-with-outline-path-completion org-refile-targets)))
(org-link-set-parameters "id"
:complete 'org-id-complete-link)
(defun zenith/search-id-reverse-link ()
"Search the id in the directory"
(interactive)
(let ((query
(cdr (first (org-entry-properties nil "ID")))))
(rg-project query "*.org")))
(defun zenith/org-insert-link-by-id ()
"Insert the link by id"
(interactive)
(let ((link (org-link--try-special-completion "id")))
(org-insert-link nil link))))
Org 与 LaTeX
Org 有着非常丰富的导出选项,比如导出到 PDF,这一功能通过 LaTeX 实现。因此需要对 于 ox-latex 进行一定的配置。
(with-eval-after-load 'ox-latex
(add-to-list 'org-latex-classes
'("myart"
"\\documentclass{article}
[DEFAULT-PACKAGES]
[PACKAGES]
\\usepackage[backend=biber,style=alphabetic]{biblatex}
\\addbibresource[location=local]{~/Dropbox/Library.bib}
\\setCJKmainfont{Source Han Sans CN}
\\setmonofont{Source Code Pro}
\\gappto{\\UrlBreaks}{\\UrlOrds}
"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
(setq org-latex-compiler "xelatex"
org-latex-default-class "myart"
org-export-with-sub-superscripts nil
org-latex-listings 'minted
org-latex-minted-options '(("breaklines" "true")
("frame" "single")
("breakanywhere" "true"))
org-latex-pdf-process
'("latexmk -g -pdf -pdflatex=\"%latex\" -shell-escape -outdir=%o %f"))
(setq org-latex-packages-alist '(("" "minted")
("" "xcolor")
("" "xeCJK")
("" "fontspec")
("" "etoolbox"))))
org-download in WSL
由于一些 Windows 下所必须的软件,我有很多时候是在 WSL 中使用 Emacs,为了更好地在 WSL 中使用 org-download 我进行了一些必要的 Trick。首先要在 Windows 中安装 IrfanView。然后需要使用我的 init-wsl.el 文件,其中包含了对文件路径进行一定的处理的函数。最后,再修改一下 org-download-yank 以及 org-download-screenshot 函数即可。当然,最好用的 drag-and-drop 还是无法使用的。
Emacs换行问题
关闭 auto-fill-mode,使用 visual-line-mode,因为visual-line-mode在操作hjkl时更加直观。
(remove-hook! org-mode
#'auto-fill-mode)
(global-visual-line-mode)
现在我同时使用 auto-fill 和 visual-fill-column。事实上 auto-fill-mode 有很多的优 点,在自己编辑时会感觉更加舒服,但是问题在于 auto-fill 后的文件给别人就会不舒服。 这是 visual-fill-column 就派上了用场。
(defun zenith/fill-and-indent-region ()
"Fill paragraph and indent region at once"
(interactive)
(when (or
(derived-mode-p 'text-mode)
(nth 4 (syntax-ppss))
(nth 8 (syntax-ppss)))
(call-interactively 'fill-paragraph))
(call-interactively 'indent-region))
;; visual fill column
(autoload 'visual-fill-column-mode "visual-fill-column" "" t)
(setq-default visual-fill-column-width (+ fill-column 20))
(add-hook 'visual-line-mode-hook 'visual-fill-column-mode)
(add-hook 'auto-fill-mode-hook 'visual-line-mode)
关于注释问题
Doom-emacs默认的注释插件是evil-commentary,但是我觉得evil-nerd-commenter https://github.com/redguardtoo/evil-nerd-commenter 更加便于使用,因为它能够toggle,注释和非注释状态。因此我使用evil-nerd-commenter取代了evil-commentary。
;; evil-nerd-commenter
;; dependencies: evil
(zenith/autoload
'(evilnc-comment-operator
evilnc-comment-or-uncomment-lines
evilnc-comment-or-uncomment-paragraphs
evilnc-comment-or-uncomment-to-the-line
evilnc-copy-and-comment-lines
evilnc-copy-and-comment-operator
evilnc-copy-to-line)
"evil-nerd-commenter")
其中 zenith/autoload 是我自己写的一个函数是 autoload 的一个封装,可以同时对多个 函数进行 autoload。
Evil-mode
由于我已经使用了个人的配置,就不得不对于 Evil-mode 来进行一番配置了,事实上使用 evil-collection 后基本上就按着默认来了。值得注意的是 https://github.com/emacs-evil/evil-surround 和 https://github.com/redguardtoo/evil-matchit 两个包,非常好用值得一试。
;; evil
;; dependencies: undo-tree goto-chg
(setq evil-want-integration t ; This is optional since it's already set to t by default.
evil-want-keybinding nil ; loading keybindings
evil-disable-insert-state-bindings t ; Use emacs's binding in insert state
evil-want-C-d-scroll nil ; Use emacs's C-d
evil-want-C-u-scroll nil ; Use emacs's C-u
evil-want-C-i-jump t ; Use vim's C-i
evil-want-fine-undo t ; Don not aggregate changes when exiting insert state
evil-want-C-w-delete t ; Use emacs's C-w
evil-toggle-key "" ; C-z not entering emacs state
)
(require 'evil)
(evil-mode 1)
自动保存
Emacs 的保存非常反人类,不如让他自己来保存吧。不过这个功能最好和版本控制系统一起 使用,否则后果自负。这里调用了 evil 的 api, 每 2 秒没有操作就保存一次。
;; The code is adjusted from https://github.com/manateelazycat/auto-save. The
;; problem of the original code is that it calls buffer-modified-p which makes
;; ws-butler unhappy.
(setq auto-save-idle 2)
(defun zenith/auto-save-buffers ()
(interactive)
(when (and
(not (minibufferp))
(or (not (boundp 'yas--active-snippets))
(not yas--active-snippets))
(or (not (boundp 'company-candidates))
(not company-candidates)))
(with-temp-message ""
(let ((inhibit-message t))
(evil-write-all nil)))))
(defun zenith/auto-save-enable ()
(interactive)
(run-with-idle-timer auto-save-idle t #'zenith/auto-save-buffers))
(zenith/auto-save-enable)
LaTeX
Ebib 是 bib 文件的查看器,但是我感觉用处并没有很大,很多时候我都是直接编辑的。
Auctex + reftex 是非常强大的 LaTeX 处理器,其中的功能非常强大而复杂。这里的代码 使得 Reftex 能够更好地在写数学论文时进行引用。
(add-hook 'LaTeX-mode-hook 'turn-on-reftex)
;; Get ReTeX working with biblatex
;; http://tex.stackexchange.com/questions/31966/setting-up-reftex-with-biblatex-citation-commands/31992#31992
(setq reftex-cite-format
'((?a . "\\autocite[]{%l}")
(?b . "\\blockcquote[]{%l}{}")
(?c . "\\cite[]{%l}")
(?f . "\\footcite[]{%l}")
(?n . "\\nocite{%l}")
(?p . "\\parencite[]{%l}")
(?s . "\\smartcite[]{%l}")
(?t . "\\textcite[]{%l}"))
reftex-plug-into-AUCTeX t
reftex-toc-split-windows-fraction 0.3
reftex-bibpath-environment-variables '("/home/zenith-john/Dropbox/")
reftex-bibliography-commands '("bibliography" "nobibiliography" "addbibresource")
reftex-label-alist
'(("theorem" ?m "thm:" "~\\ref{%s}" nil (regexp "[Tt]heorem" "[Tt]h\\.") -3)
("lemma" ?m "lem:" "~\\ref{%s}" nil (regexp "[Ll]emma" "[Ll]m\\.") -3)
("proposition" ?m "prop:" "~\\ref{%s}" nil (regexp "[Pp]roposition" "[Pp]rop\\.") -3)
("remark" ?m "rmk:" "~\\ref{%s}" nil (regexp "[Rr]emark" "[Rr]mk\\.") -3)
("definition" ?m "def:" "~\\ref{%s}" nil (regexp "[Dd]efinition" "[Dd]ef\\.") -3)
("corollary" ?m "cor:" "~\\ref{%s}" nil (regexp "[Cc]orollary" "[Cc]or\\.") -3))
reftex-ref-macro-prompt nil)
这里设置了自动补全的后端,注意顺序在这里是重要的。
(with-eval-after-load 'tex
;; the order of company-backend is important.
;; company-auctex
;; dependencies: yasnippet company auctex
(require 'company-auctex)
;; company-math
;; dependencies: company math-symbol-lists
(require 'company-math)
(add-to-list '+latex-company-backends 'company-auctex-labels)
(add-to-list '+latex-company-backends 'company-math-symbols-latex)
(add-to-list '+latex-company-backends '(company-auctex-macros company-auctex-environments)))
(defun zenith/latex-company-setup ()
"Setup company backends for latex editing."
(make-local-variable 'company-backends)
(setq zenith/local-company-backends nil)
(dolist (backend +latex-company-backends)
(add-to-list 'company-backends backend)))
(add-hook 'LaTeX-mode-hook 'zenith/latex-company-setup)
在 auctex 中比较重要的设置是 TeX-source-correlate-mode, LaTeX-fill-break-at-separators 以及 TeX-command-extra-options,详情可以参考我的 配置 config/init-latex.el 。值得注意的是其中我的几个函数,
(defun LaTeX-star-environment-dwim ()
"Convert between the starred and the not starred version of the current environment."
(interactive)
;; If the current environment is starred.
(if (string-match "\*$" (LaTeX-current-environment))
;; Remove the star from the current environment.
(LaTeX-modify-environment (substring (LaTeX-current-environment) 0 -1))
;; Else add a star to the current environment.
(LaTeX-modify-environment (concat (LaTeX-current-environment) "*"))))
(defun zenith/latex-toggle-section-with-star ()
(interactive)
(if (member '("section" 2) LaTeX-section-list) ;; TODO: Make it more robust.
(setq LaTeX-section-list
'(("part" 0)
("chapter" 1)
("section*" 2)
("subsection*" 3)
("subsubsection*" 4)
("paragraph" 5)
("subparagraph" 6)
("section" 2)
("subsection" 3)
("subsubsection" 4)))
(setq LaTeX-section-list
'(("part" 0)
("chapter" 1)
("section" 2)
("subsection" 3)
("subsubsection" 4)
("paragraph" 5)
("subparagraph" 6)))))
(defvar zenith/equation-env-list
'(("\\begin{equation}\n" . "\n\\end{equation}")
("\\[" . "\\]")
("\\(" . "\\)"))
"The pairs of equation environment")
(defun zenith/regex-or (l)
(let ((regex "\\(?:")
(first-one t))
(dolist (e l)
(if (not first-one)
(setq regex
(concat regex "\\\|"))
(setq first-one nil))
(setq regex
(concat regex (regexp-quote e))))
(concat regex "\\)")))
(defun zenith/equation-match (beg end)
"Check whether `beg' and `end' matches as equation"
(let ((beg-string (buffer-substring-no-properties beg (min (+ beg 20) (point-max))))
(end-string (buffer-substring-no-properties (max (point-min) (- end 20)) end))
ret)
(dolist (e zenith/equation-env-list)
(when (and
(string-prefix-p (car e) beg-string)
(string-suffix-p (cdr e) end-string))
(setq ret e)))
ret))
(defun zenith/cycle-equation ()
(interactive)
(if-let* ((regex (zenith/regex-or (append (mapcar 'car zenith/equation-env-list)
(mapcar 'cdr zenith/equation-env-list))))
(beg (save-excursion (re-search-backward regex nil t)))
(end (save-excursion (re-search-forward regex nil t)))
(kind (zenith/equation-match beg end))
(len (safe-length zenith/equation-env-list))
(pos (cl-position kind zenith/equation-env-list))
(next (nth (if (= pos (- len 1))
0
(+ pos 1)) zenith/equation-env-list)))
(progn
(save-excursion
(goto-char beg)
(delete-char (length (car kind)))
(insert (car next))
(re-search-forward (zenith/regex-or (mapcar
'cdr zenith/equation-env-list)))
(delete-backward-char (length (cdr kind)))
(insert (cdr next))))
(message "No match equation environment found.")))
其中 LaTeX-star-environment-dwim 可以为 environment 加上星号, zenith/latex-toggle-section-with-star 可以使得插入的 section 带上星号。而 zenith/cycle-equation 可以使得公式在三种状态下切换。
版本控制
Magit 是我见过的最好用的 git 前端,是我离不开 Emacs 的重要理由。同时 Emacs 还有 两个插件 https://github.com/syohex/emacs-git-gutter 和 https://github.com/emacsmirror/git-timemachine 来进一步强化 Emacs 和 Git 之间的 协作。
UI
我使用了 https://github.com/seagle0128/doom-modeline ,总体而言还是非常好用的, 但是必须进行一定的 tweak 否则和 Org-mode 之间会有一定的冲突
;; Redefine `doom-modeline-redisplay' to ignore `doom-modeline--size-hacked-p'
;; to fix the problem caused by reuse of some buffer, for example *Org Tags*
(defun zenith/doom-modeline-always-redisplay ()
"Check whether this buffer should always display"
(or (string-equal (buffer-name) " *Org tags*")
(string-equal (buffer-name) " *Org todo*")))
(defun doom-modeline-redisplay (&rest _)
"Call `redisplay' to trigger mode-line height calculations.
Certain functions, including e.g. `fit-window-to-buffer', base
their size calculations on values which are incorrect if the
mode-line has a height different from that of the `default' face
and certain other calculations have not yet taken place for the
window in question.
These calculations can be triggered by calling `redisplay'
explicitly at the appropriate time and this functions purpose
is to make it easier to do so.
This function is like `redisplay' with non-nil FORCE argument.
It accepts an arbitrary number of arguments making it suitable
as a `:before' advice for any function. If the current buffer
has no mode-line or this function has already been called in it,
then this function does nothing."
(when (and (bound-and-true-p doom-modeline-mode)
mode-line-format
(not doom-modeline--size-hacked-p))
(redisplay t)
(unless (zenith/doom-modeline-always-redisplay)
(setq doom-modeline--size-hacked-p t))))
除此之外,我对于 hl-line-mode 也进行了一定的定制。由于某些主体 highlight 的颜色 和 Mark 的颜色较为相似,造成分辨上的困难,我通过重定义使得在选择文本时关闭 hl-line-highlight。
;; Redefine `hl-line-highlight' to disable highlight line when selection is
;; active.
(defun hl-line-highlight ()
"Activate the Hl-Line overlay on the current line."
(if
(and hl-line-mode ; Might be changed outside the mode function.
(not (region-active-p)))
(progn
(unless hl-line-overlay
(setq hl-line-overlay (hl-line-make-overlay))) ; To be moved.
(overlay-put hl-line-overlay
'window (unless hl-line-sticky-flag (selected-window)))
(hl-line-move hl-line-overlay))
(hl-line-unhighlight)))
自动补全
自动补全使用的是 https://github.com/company-mode/company-mode 和 https://github.com/sebastiencs/company-box 。其中的配置可以参考 config/init-company.el 。事实上,在其中我自己实现了一个 company 的模糊匹配但是效 果不让人满意。
另一部分的自动补全是所谓的模板,模板使用的是 https://github.com/joaotavora/yasnippet。 同时我自己编写了一个插件实现模板的自动 展开,其要求是前一个单词以 , 开始然后按下空格键。函数会尝试展开,如果展开失败, 那么原封不动,如果展开成功,逗号就会消失。
;; Make yasnippet expandsion easy for me.
(defvar zenith/snippet-prefix ?,
"The first character of expanding yasnippet")
(defun zenith/may-expand ()
"Auto expand if the word before the point are started with
`zenith/snippet-prefix'. Return `t' if the expansion is successful
and `nil' otherwise."
(interactive)
(let* ((word-end (point))
(word-start (save-excursion
(save-restriction
(narrow-to-region (line-beginning-position 0) (line-end-position))
(search-backward-regexp "^\\|[[:blank:]]\\|(\\|)\\|\\[\\|]\\|{\\|}" nil t))))
(word)
(len))
(when word-start
(if (eq (char-after word-start) zenith/snippet-prefix)
(setq word (buffer-substring-no-properties word-start word-end))
(setq
word-start (+ word-start 1)
word (buffer-substring-no-properties word-start word-end)))
(when (eq zenith/snippet-prefix (string-to-char word))
(delete-region word-start (+ word-start 1))
(if (call-interactively 'yas-expand)
t
(setq len (- (length word) 1))
(backward-char len)
(insert-char zenith/snippet-prefix)
(forward-char len)
nil)))))
(defun zenith/post-command-hook ()
"Check whether or not to expand after insertion of ~SPC~."
(interactive)
(when
(and
yas-minor-mode
(eq last-command 'self-insert-command)
(eq (char-before) ?\s))
(delete-backward-char 1)
(unless (zenith/may-expand)
(insert-char ?\s)
(when (boundp company-mode)
(company-abort)))))
(define-minor-mode auto-expand-mode
"Minor mode for zenith/may-expand"
nil nil nil
(if auto-expand-mode
;; Priority of the function should be high enough to run before fill
;; column
(add-hook 'post-command-hook 'zenith/post-command-hook 0 t)
(remove-hook 'post-command-hook 'zenith/post-command-hook t)))
(define-globalized-minor-mode global-auto-expand-mode auto-expand-mode auto-expand-mode-on)
(defun auto-expand-mode-on ()
(auto-expand-mode 1))
(global-auto-expand-mode 1)
Ivy-mode
https://github.com/abo-abo/swiper ivy, swiper, counsel 是强大的工具是对于 ido 的 加强。https://github.com/DarwinAwardWinner/amx 进一步加强了 execute-command。值 得提到的有两点,一是
(defun zenith/open-by-external-program (path)
"Open file in external program"
(let ((display-buffer-alist '(("*Async Shell Command*" . (display-buffer-no-window)))))
(async-shell-command (format "nohup xdg-open \"%s\" >/dev/null 2>&1"
(file-relative-name path default-directory)))))
这里个函数在 Linux 中通过系统默认的程序来打开文件。二是
;; https://github.com/tumashu/emacs-helper/commit/1932a9e8a64f08bb9603cf244df41f6c0bbc3dac
;; Search chinese with pinyin
(defun zenith/ivy-cregexp-helper (str)
(cons (pyim-cregexp-build str) t))
(defun zenith/ivy-cregexp-ignore-order (str)
(let ((str-list (split-string str)))
(if str-list
(mapcar 'zenith/ivy-cregexp-helper (split-string str))
"")))
(setq ivy-re-builders-alist '((counsel-company . ivy--regex-fuzzy)
(t . zenith/ivy-cregexp-ignore-order)))
这样就可以在 ivy 的补全中直接使用拼音搜索,非常的好用。(事实上,在 ivy-posframe 中,pyim 莫名地不可用)
Programming
编码主要依赖于https://github.com/flycheck/flycheck, 用于进行代码的事实检查。 https://github.com/manateelazycat/nox 用于和 LSP Server 通信提供补全跳转等编码体 验,https://github.com/lassik/emacs-format-all-the-code 提供代码格式化。同时 https://github.com/bbatsov/projectile 提供了项目管理(事实上这一功能我用的比较少)。
琐碎
show-paren-mode 可以显示配对的括号。recentf 提供访问最近文件的方法。cua-mode 使 得 C-x, C-c, C-v 有和其他软件类似的行为。display-line-number-mode 是高效的显示行 号的方法。 winner-mode 提供对于 window 操作的撤销和重做。undo-tree 提供了非常好 的撤销, https://github.com/Fuco1/smartparens 提供括号和命令的补全。 https://github.com/lewang/ws-butler 以一种友好的方式去除行末空格。 https://github.com/abo-abo/ace-window 提供窗口切换的好方法。rg 提供快速的文本搜 索,和 wgrep 配合提供文本的替换。https://github.com/raxod502/ctrlf 增强了 Emacs 自带的搜索,使得行为更加可控。https://github.com/redguardtoo/wucuo 提供了快速的 拼写检查。 shackle 提供了对于弹出窗口的控制。 https://github.com/noctuid/general.el 提供了绑定快捷键的好方法。 https://github.com/justbur/emacs-which-key 提供了探索快捷键的方法。除此之外,我 还自己写了几个函数。
;; Delete word in a more user friendly way
(defun zenith/aggressive-delete-space ()
"Remove all the space until non-space character."
(interactive)
(let ((end (point))
(begin (save-excursion
(re-search-backward "[^ \t\n\r]" nil t))))
(delete-region (+ 1 begin) end)))
(defun zenith/delete-word-or-space ()
"Remove all the space until non-space character if the char at
point and before are all space characters and delete word
otherwise."
(interactive)
(if (and (zenith/is-space (char-before))
(zenith/is-space (char-before (- (point) 1))))
(zenith/aggressive-delete-space)
(backward-kill-word 1)))
zenith/aggressive-delete-space 使得删除词的行为更精细。
;; jump in my way
(defvar zenith/jump-function-alist
'((org-mode . org-goto)
(latex-mode . reftex-toc)
(org-agenda-mode . org-agenda-redo)
(t . counsel-imenu))
"The function to call when jump")
(defun zenith/jump ()
"Jump as `zenith/jump-function-alist' like."
(interactive)
(if-let ((func (alist-get major-mode zenith/jump-function-alist)))
(funcall func)
(funcall (alist-get t zenith/jump-function-alist))))
zenith/jump 提供了浏览文件的统一方法。