Emacs 中的补全有很多种形式,写代码的时候弹出来的补全框,AI 使用 inline text 实现的续写,使用 M-x 调起来的 minibuffer 补全,某些交互式命令调用时弹出的补全框,都是补全。
但是不管那种补全,都由以下模块组成:
补全后端框架 #
补全后端负责收集和整理数据,为前端提供统一的补全接口。
minibuffer 中的补全 #
补全项的来源都是命令自己产生的数据。
普通 Buffer 中的补全 #
capf #
这是一个 Emacs 自己构建的系统,前端调用当前 buffer 的 completion-at-point-functions 这个变量里面的函数,来获取补全,可以使用 cape 来增强 capf 的能力。
第三方 #
-
acm
lsp-bridge 自己构建的补全系统,只能给
acm前端使用,但是能调用capf的接口来提供数据。
补全前端框架 #
有了数据后,需要考虑如何呈现给用户。Emacs 提供了多种前端界面可供选择。
minibuffer 中的补全 #
使用 Emacs 原生补全接口 #
-
Ido 包允许您以最少的按键次数切换缓冲区并访问文件和目录。它是
Stephen Eglen开发的交互式缓冲区切换包 Iswitchb 的超集,给默认切换 buffer 和打开文件提供了更加强大的功能,显示补全项的方式是直接在输入的后面使用 { a | b } 来显示。 但是默认不支持M-x的补全显示,需要安装 smex 来使用ido风格的补全显示方式。
-
是 Emacs 原生的 minibuffer 补全方式。
-
Fido (Fake ido)
Fido 模式是 Icomplete 模式的替代方案。它与 Icomplete 模式非常相似,但保留了来自名为 Ido 模式的流行扩展的一些功能。(默认使用 flex 风格 来匹配)
-
icomplete-vertico-mode和 icomplete 是一样的,但是垂直展示补全项。
-
fido-vertical-mode和 fido 是一样的,但是垂直展示补全项。
-
使用原生
*Completions*buffer 来弹出补全项,但是能自动更新*Completions*buffer, 为原生补全提供更多的智能化。
-
目前最新的框架,可以支持前端样式,能根据补全的内容自定义补全样式。 能支持同时多种类别补全项目展示,根据补全项的分类来分隔展示。
第三方 #
-
主要不使用 minibuffer 来进行补全,而是使用单独的 buffer 来进行补全项的展示。
普通 Buffer 中的补全 #
使用 Emacs capf 作为后端 #
-
使用
*Completion*使用一个单独的 buffer 来展示补全项。
-
设置变量
icomplete-in-buffer为 t 启用(可能需要开关icomplete-mode),默认使用icomplete-mode的补全风格,在输入的后面显示补全项。
-
completion-preview-mode使用
inline Text的方式展示预览项,默认只展示一个。
第三方 #
-
使用
overlay来弹出补全菜单。
匹配,过滤和排序 #
补全样式 #
在补全过程中,补全项会根据输入内容匹配相应条目。Emacs 默认配置了多种补全样式。
补全样式的确定流程如下:
- 检查
completion-category-overrides中是否有当前补全项的分类,若有则使用; - 若无,检查
completion-category-defaults; - 若两者均无或补全项无分类,则使用默认的
completion-styles。
确定样式列表后,Emacs 按列表顺序尝试补全:若首个样式返回 nil ,则依次使用后续样式,形成 降级回退 机制。
匹配和过滤 #
orderless #
它提供了一个强大的匹配和过滤机制,一下是来自 orderless 官方的介绍。
它将模式分割成以空格分隔的组件,并匹配所有组件(顺序不限)的候选词。每个组件可以通过多种方式匹配:字面匹配、正则表达式匹配、首字母缩写匹配、灵活匹配或多个单词前缀匹配。默认情况下,正则表达式匹配和字面匹配已启用。
并且它还提供了一个很强大的功能, Style dispatchers, 可以支持将带有前缀的输入内容,单独使用其他匹配规则。
基与这个功能和 pyim 提供的词库,就可以简单实现一个输入对应前缀,然后输入拼音,就可以直接匹配和过滤中文。1
(require 'pyim)
(defun chinese-orderless-regexp (component)
"Match COMPONENT as a chinese regexp."
(condition-case nil
(pyim-cregexp-build
(progn (string-match-p component "")
component))
(invalid-regexp nil)))
(with-eval-after-load 'orderless
(add-to-list 'orderless-affix-dispatch-alist
`(?= . ,#'chinese-orderless-regexp)))
orderless-affix-dispatch-alist 这个变量里面保存的就是 orderless 用来检测输入的前缀对应的匹配函数。
fussy #
这个包提供了一个给匹配项排序的功能,通过评分算法计算出匹配项的评分,然后根据评分来进行排序。并且可以自己定义评分和匹配过滤函数。
这是一个为 Emacs 提供 completion-style 包,能够利用 flx 以及各种其他库,例如 fzf-native 提供智能评分和排序。
匹配函数可利用 orderless 的过滤功能,但默认评分函数不支持其前缀特性,导致匹配项得分均为零或负数。
为此,我自定义了支持前缀评分的函数,完整实现详见配置中的 fussy-orderless 文件。
主要核心内容就是给每一个 orderless 的前缀添加评分,最后计算总共的评分,将各个评分相加起来。
例如,以下代码用于计算中文内容的评分: 通过评估匹配内容在整体中的占比及其位置信息来计算得分2。
(defun fussy-orderless-chinese-regexp-score (string query)
"Use QUERY and STRING calc chinese regexp score."
(require 'pyim)
(when-let* (((string-match-p "\\cc" string))
(regexp (when (fboundp 'pyim-cregexp-build)
(pyim-cregexp-build query))))
(string-match regexp string)
(pcase-let* ((`(,start ,end) (match-data))
(len (length string)))
(when (< end len)
(list (+ (* 20 (/ (float (- end start))
len))
(* 80 (/ (float (- len start)) len)))
start
end)))))
之后只要修改以下内容:
首先需要将想要使用的计分函数添加到 fussy-whitespace-ok-fns 变量中,以确保传递给评分函数的查询内容中的空格不被去除,避免 orderless 无法识别前缀。
(with-eval-after-load 'fussy
(add-to-list 'fussy-whitespace-ok-fns
#'fussy-orderless-score-with-flx-rs))
然后就是将过滤函数设置 orderless, 评分函数设置成我上面写的函数,目前只支持 flx-rs 和 flx 。
(setopt fussy-score-fn 'fussy-orderless-score-with-flx-rs
fussy-filter-fn 'fussy-filter-orderless-flex)