- 1 课程回顾
- 2 Emacs自带的补全系统
- 3 Emacs的第三方补全体系
- 3.1 vertico
- 3.2 orderless
- 3.3 marginalia
- 3.4 consult
- 3.5 corfu
- 4 yasnippet模板补全
- 5 结语
1 课程回顾 🔗
在上一节课中,我们通过设置,改变了Emacs的一些默认行为如自动备份、选择文本后输入进行替换等,让我们的Emacs更加符合日常使用习惯。我们也通过一些插件,如 org-auto-tangle
等插件实现了自动 tangle 等功能,极大的方便了我们通过 Org 文件对配置进行模块化的管理。
今天,我们将对Emacs自带的补全系统做一个增强,让Emacs在效率上更进一步。
2 Emacs自带的补全系统 🔗
在我们尝试用 C-x C-f
打开某个文件时,可以按 TAB
键触发Emacs自带的补全系统,这样它会把当前目录下可能的文件名列出来,你可以按某个字母后再按 TAB
键进行进一步的补全。如下面的动图:
自带的补全系统当然不够好用,不够直观,也不支持模糊匹配,筛选结果只能按照它提示的字母,按顺序进行筛选。
我们希望达到的效果是,凭着我们记忆中的一些关键词,顺序也不重要,通过这些关键词的组合来筛选匹配出候选的结果。例如,我希望筛选出的是所有包含 emacs
和 org
的文件名,它应该自动帮我过滤筛选出所有文件名包含 emacs
和 org
的文件名,从而达到快速选择快速定位的目的。
3 Emacs的第三方补全体系 🔗
Emacs有很多很好用的补全体系,如 ivy 体系,vertico 体系等等,这里我们推荐新手朋友使用的是 vertico
,当然,将来你完全可以按照自己的意愿和喜好去选择喜欢的补全体系,毕竟Emacs是自由的!
vertico
体系包含以下几个插件:
- vertico
- consult
- corfu
- marginalia
- orderless
所谓的补全体系包含这几个插件,也只是我个人的看法,并不代表必须这么搭配,只是在社区,这几个插件的组合更加适合而已。
下面我们一个一个来安装配置这些生产力中的生产力!
3.1 vertico 🔗
vertico 插件提供了一个垂直样式的补全系统。
(use-package vertico
:ensure t
:hook (after-init . vertico-mode)
:bind (:map minibuffer-local-map
("M-<DEL>" . my/minibuffer-backward-kill)
:map vertico-map
("M-q" . vertico-quick-insert)) ; use C-g to exit
:config
(defun my/minibuffer-backward-kill (arg)
"When minibuffer is completing a file name delete up to parent
folder, otherwise delete a word"
(interactive "p")
(if minibuffer-completing-file-name
;; Borrowed from https://github.com/raxod502/selectrum/issues/498#issuecomment-803283608
(if (string-match-p "/." (minibuffer-contents))
(zap-up-to-char (- arg) ?/)
(delete-minibuffer-contents))
(backward-kill-word arg)))
;; Do not allow the cursor in the minibuffer prompt
(setq minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
(setq vertico-cycle t) ; cycle from last to first
:custom
(vertico-count 15) ; number of candidates to display, default is 10
)
安装完 vertico 后,我们可以看到我们打开文件时,不用按 TAB
触发,所有的候选默认展现出来,变成一列,这样比自带的平铺的候选更加直观方便。
3.2 orderless 🔗
orderless 插件提供一种无序的补全新姿势,将一个搜索的范式变成数个以空格分隔的部分,各部分之间没有顺序,你要做的就是根据记忆输入关键词、空格、关键词。
orderless 在我看来,是一个杀手级应用,它改变了我们使用和思考的习惯,我们不再需要关心信息的顺序,我们只需要在脑海中搜索关键信息片段,然后把这些片段组合起来即可,剩下的都交给Emacs。
在这里,还有一个很重要的插件是 pinyinlib ,他能够让 orderless 支持中文的首字母匹配,对效率的提升又大了一截,我们对于中文的文件名的搜索和过滤将更加方便!
;; support Pinyin first character match for orderless, avy etc.
(use-package pinyinlib
:ensure t)
;; orderless 是一种哲学思想
(use-package orderless
:ensure t
:init
(setq completion-styles '(orderless partial-completion basic))
(setq orderless-component-separator "[ &]") ; & is for company because space will break completion
(setq completion-category-defaults nil)
(setq completion-category-overrides nil)
:config
;; make completion support pinyin, refer to
;; https://emacs-china.org/t/vertico/17913/2
(defun completion--regex-pinyin (str)
(orderless-regexp (pinyinlib-build-regexp-string str)))
(add-to-list 'orderless-matching-styles 'completion--regex-pinyin)
)
安装配置完 orderless ,Emacs开始彰显威力,此时我们想找出当前文件夹下包含 emacs
和 org
关键词的时候,只需要输入 emacs org
即可:
3.3 marginalia 🔗
marginalia 插件给迷你缓冲区的补全候选条目添加一些提示信息。
(use-package marginalia
:ensure t
:hook (after-init . marginalia-mode)
:custom
(marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)))
安装了这个插件后,可以看到,我们在选择文件时,迷你缓冲区里加上了文件的权限、大小、修改时间等信息,极大的便利了我们对文件进行选择和判断。
3.4 consult 🔗
consult 插件提供了一系列的查找和补全的命令,非常方便。
(use-package consult
:ensure t
:after org
:bind (([remap goto-line] . consult-goto-line)
([remap isearch-forward] . consult-line-symbol-at-point) ; my-consult-ripgrep-or-line
([remap switch-to-buffer] . consult-buffer)
([remap switch-to-buffer-other-window] . consult-buffer-other-window)
([remap switch-to-buffer-other-frame] . consult-buffer-other-frame)
([remap yank-pop] . consult-yank-pop)
([remap apropos] . consult-apropos)
([remap bookmark-jump] . consult-bookmark)
([remap goto-line] . consult-goto-line)
([remap imenu] . consult-imenu)
([remap multi-occur] . consult-multi-occur)
([remap recentf-open-files] . consult-recent-file)
("C-x j" . consult-mark)
("C-c g" . consult-ripgrep)
("C-c f" . consult-find)
("\e\ef" . consult-locate) ; need to enable locate first
("C-c n h" . my/consult-find-org-headings)
:map org-mode-map
("C-c C-j" . consult-org-heading)
:map minibuffer-local-map
("C-r" . consult-history)
:map isearch-mode-map
("C-;" . consult-line)
:map prog-mode-map
("C-c C-j" . consult-outline)
)
:hook (completion-list-mode . consult-preview-at-point-mode)
:init
;; Optionally configure the register formatting. This improves the register
;; preview for `consult-register', `consult-register-load',
;; `consult-register-store' and the Emacs built-ins.
(setq register-preview-delay 0
register-preview-function #'consult-register-format)
;; Optionally tweak the register preview window.
;; This adds thin lines, sorting and hides the mode line of the window.
(advice-add #'register-preview :override #'consult-register-window)
;; Use Consult to select xref locations with preview
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
;; MacOS locate doesn't support `--ignore-case --existing' args.
(setq consult-locate-args (pcase system-type
('gnu/linux "locate --ignore-case --existing --regex")
('darwin "mdfind -name")))
:config
(consult-customize
consult-theme
:preview-key '(:debounce 0.2 any)
consult-ripgrep consult-git-grep consult-grep
consult-bookmark consult-recent-file consult-xref
consult--source-recent-file consult--source-project-recent-file consult--source-bookmark
:preview-key (kbd "M-."))
;; Optionally configure the narrowing key.
;; Both < and C-+ work reasonably well.
(setq consult-narrow-key "<") ;; (kbd "C-+")
(autoload 'projectile-project-root "projectile")
(setq consult-project-root-function #'projectile-project-root)
;; search all org file headings under a directory, see:
;; https://emacs-china.org/t/org-files-heading-entry/20830/4
(defun my/consult-find-org-headings (&optional match)
"find headngs in all org files."
(interactive)
(consult-org-heading match (directory-files org-directory t "^[0-9]\\{8\\}.+\\.org$")))
;; Use `consult-ripgrep' instead of `consult-line' in large buffers
(defun consult-line-symbol-at-point ()
"Consult line the synbol where the point is"
(interactive)
(consult-line (thing-at-point 'symbol)))
)
安装了 consult 后,我们可以将 C-s
绑定到 consult-line
这个命令,使用 vertico+orderless,很容易能够搜索并跳转到我们想要去的行,在切换不同的候选时,还能预览候选行的内容,非常方便!
之前提到过一个问题,我们要怎么跳转到org文件的某一个标题行呢?当我们安装完 consult
插件后,这个问题可以得到解答了,我们可以使用 consult-org-heading
命令(绑定到了 C-c C-j
按键)来跳转到我们想要去的标题,结合 orderless
我们能快速定位标题行:
3.5 corfu 🔗
corfu 插件可以让我们通过弹窗进行补全。
(use-package corfu
:ensure t
:hook (after-init . global-corfu-mode)
:bind
(:map corfu-map
("SPC" . corfu-insert-separator) ; configure space for separator insertion
("M-q" . corfu-quick-complete) ; use C-g to exit
("TAB" . corfu-next)
([tab] . corfu-next)
("S-TAB" . corfu-previous)
([backtab] . corfu-previous))
:config
;; TAB cycle if there are only few candidates
(setq completion-cycle-threshold 0)
(setq tab-always-indent 'complete)
(defun corfu-enable-always-in-minibuffer ()
"Enable Corfu in the minibuffer if Vertico/Mct are not active."
(unless (or (bound-and-true-p mct--active)
(bound-and-true-p vertico--input))
;; (setq-local corfu-auto nil) Enable/disable auto completion
(corfu-mode 1)))
(add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)
;; enable corfu in eshell
(add-hook 'eshell-mode-hook
(lambda ()
(setq-local corfu-auto nil)
(corfu-mode)))
;; For Eshell
;; ===========
;; avoid press RET twice in Eshell
(defun corfu-send-shell (&rest _)
"Send completion candidate when inside comint/eshell."
(cond
((and (derived-mode-p 'eshell-mode) (fboundp 'eshell-send-input))
(eshell-send-input))
((and (derived-mode-p 'comint-mode) (fboundp 'comint-send-input))
(comint-send-input))))
(advice-add #'corfu-insert :after #'corfu-send-shell)
:custom
(corfu-cycle t) ;; Enable cycling for `corfu-next/previous'
)
安装配置了 corfu 后,我们在写配置时,就可以通过一个弹窗来进行补全,配合 orderless,我们可以很方便的通过输入多个关键词来筛选我们需要的函数了。
3.5.1 cape 🔗
Cape 插件提供了一系列开箱即用的补全后端,跟corfu联合使用。
(use-package cape
:ensure t
:init
;; Add `completion-at-point-functions', used by `completion-at-point'.
(add-to-list 'completion-at-point-functions #'cape-file)
(add-to-list 'completion-at-point-functions #'cape-dabbrev)
(add-to-list 'completion-at-point-functions #'cape-keyword) ; programming language keyword
(add-to-list 'completion-at-point-functions #'cape-ispell)
(add-to-list 'completion-at-point-functions #'cape-dict)
(add-to-list 'completion-at-point-functions #'cape-symbol) ; elisp symbol
(add-to-list 'completion-at-point-functions #'cape-line)
:config
(setq cape-dict-file (expand-file-name "etc/hunspell_dict.txt" user-emacs-directory))
;; for Eshell:
;; ===========
;; Silence the pcomplete capf, no errors or messages!
(advice-add 'pcomplete-completions-at-point :around #'cape-wrap-silent)
;; Ensure that pcomplete does not write to the buffer
;; and behaves as a pure `completion-at-point-function'.
(advice-add 'pcomplete-completions-at-point :around #'cape-wrap-purify)
)
4 yasnippet模板补全 🔗
当我们配置完 vertico, orderless, marginalia, consult, corfu 后,我们会发现Emacs的便利之处。除了 vertico 补全体系完,我们还需要一个模板体系,如当我们想在 org 文件里添加一个代码块时,需要手动输入 #+begin_src emacs-lisp
和 #+end_src
代码块的头行和尾行,然后在里面编写相关的配置,非常麻烦。
yasnippet 插件是恰恰是为了解决这个问题的一个模板补全系统。yasnippet的安装和配置非常简单:
(use-package yasnippet
:ensure t
:diminish yas-minor-mode
:hook ((after-init . yas-reload-all)
((prog-mode LaTeX-mode org-mode) . yas-minor-mode))
:config
;; Suppress warning for yasnippet code.
(require 'warnings)
(add-to-list 'warning-suppress-types '(yasnippet backquote-change))
(setq yas-prompt-functions '(yas-x-prompt yas-dropdown-prompt))
(defun smarter-yas-expand-next-field ()
"Try to `yas-expand' then `yas-next-field' at current cursor position."
(interactive)
(let ((old-point (point))
(old-tick (buffer-chars-modified-tick)))
(yas-expand)
(when (and (eq old-point (point))
(eq old-tick (buffer-chars-modified-tick)))
(ignore-errors (yas-next-field))))))
所有的模板文件,需要放在 ~/.emacs.d/snippets
文件夹里,针对不同的模式,需要放到不同的文件夹。如在 org-mode
里,我们创建了两个模板文件,在 emacs-lisp-mode
里,我们也创建了两个模板文件:
/Users/randolph/.emacs.d/snippets
├── emacs-lisp-mode
│ ├── defun
│ └── usepackage
├── org-mode
│ ├── emacslisp
│ ├── orgsrc
...
我们以其中 org-mode/emacslisp
的模板为例:
# -*- mode: snippet -*-
# name: emacslisp
# key: <el
# --
#+BEGIN_SRC emacs-lisp
$0
#+END_SRC
创建好这个文本文件后,我们只需要在 org 文件里,输入 <el
后,按 TAB
键后,yasnippet会自动帮我们补全成下面这3行,然后光标会自动放在第二行,非常方便:
#+BEGIN_SRC emacs-lisp
#+END_SRC
至于 yansippet 的模板应该怎么写,我想通过上面那个例子,你应该可以照葫芦画瓢写一些简单的模板,如果需要更复杂的功能,可以参考:yasnippet manual
5 结语 🔗
经过今天的课程,我们给Emacs添加上了非常强大的补全体系,进一步提升了我们使用Emacs的效率。
配置文件的快照见:emacs-config-l7.org
你也可以在 这里 查看最新的配置文件。