在 NEWS.29 中可以看到 emacs 新增了 dabbrev-capf
函数,我们可以将它添加到 completion-at-point-functions
中来使用它提供的补全功能。dabbrev 提供了根据 buffer 内容获取补全项的能力,即使没有 LSP 也能进行基础补全。
** Dabbrev
*** New function ‘dabbrev-capf’ for use on ‘completion-at-point-functions’.
*** New user option ‘dabbrev-ignored-buffer-modes’.
Buffers with major modes in this list will be ignored. By default,
this includes “binary” buffers like ‘archive-mode’ and ‘image-mode’.
在 corfu 的 cape README 中作者也提到了这个函数:
但是,如果你直接将它添加到 completion-at-point-functions
中会出现以下错误(以下测试在 emacs -Q 中进行,需要打开 toggle-debug-on-error
,通过 M-/
可以触发 capf 补全):
顺着调用链找下去可以发现是 dabbrev-capf
→ dabbrev--abbrev-at-point
→ dabbrev--goto-start-of-abbrev
→ looking-at
。问题出在 dabbrev--goto-start-of-abbrev
给 looking-at
的参数上:
此处的 dabbrev--abbrev-char-regexp
为空值并未初始化,它的初始化由 dabbrev.el 中的 dabbrev--reset-global-variables
负责,但是该函数只会在 dabbrev-completion
, dabbrev-expand
内被调用。而 dabbrev-capf
没有负责该变量的初始化,因此会出错。
解决方法就是在配置文件或其他地方 (require 'dabbrev)
并调用 dabbrev--reset-global-variables
完成初始化。
我观察了最新的 emacs 代码,似乎并未就这个问题做出什么改变,但是也没有文档说明 dabbrev-capf
的具体用法。
2 个赞
不应该呀,dabbrev--reset-global-variables
是内部函数,在dabbrev-expand
和 dabbrev-completion
中都会调用。使用这两个autoload 函数就没有问题。
确实,这个 reset 函数在每次 dabbrev-completion
都会被调用来刷新状态,我的理解有点问题。
而且我上面给出的方法有问题,这个 reset 不是什么初始化函数
仔细看了下 dabbrev-capf
的由来,根据这个 commit ,它只是被从 dabbrev-completion
中拆分出来了而已,要直接使用 dabbrev-capf
得学下 dabbrev-completion
的用法。
此贴终结
(defun yy/dabbrev-capf ()
"对 `dabbrev-capf' 的简单包装"
(dabbrev--reset-global-variables)
(setq dabbrev--check-other-buffers nil)
(setq dabbrev--check-all-buffers nil)
(let* ((abbrev (dabbrev--abbrev-at-point))
(beg (progn (search-backward abbrev) (point)))
(end (progn (search-forward abbrev) (point)))
(ignore-case-p (dabbrev--ignore-case-p abbrev))
(list 'uninitialized)
(table
(lambda (s p a)
(if (eq a 'metadata)
`(metadata (cycle-sort-function . ,#'identity)
(category . dabbrev))
(when (eq list 'uninitialized)
(save-excursion
;;--------------------------------
;; New abbreviation to expand.
;;--------------------------------
(setq dabbrev--last-abbreviation abbrev)
;; Find all expansion
(with-temp-message (current-message)
(let* ((inhibit-message t)
(completion-list
(dabbrev--find-all-expansions abbrev ignore-case-p))
(completion-ignore-case ignore-case-p))
(setq list
(cond
((not (and ignore-case-p dabbrev-case-replace))
completion-list)
((string= abbrev (upcase abbrev))
(mapcar #'upcase completion-list))
((string= (substring abbrev 0 1)
(upcase (substring abbrev 0 1)))
(mapcar #'capitalize completion-list))
(t
(mapcar #'downcase completion-list))))))))
(complete-with-action a list s p)))))
(list beg end table)))
参考 dabbrev-completion
搓了个 capf,也许能用
.emacs.d/settings/package_extra.el
(use-package dabbrev
:commands (dabbrev--reset-global-variables)
:init
;; from https://eshelyaron.com/esy.html
;; 直接用dabbrev-capf有问题,cape的dabbrev也有问题(如它忽略了dabbrev-abbrev-char-regexp导致中文设置不生效,另外补全项好像没有dabbrev-completion多?)
(defvar has-dabbrev-capf nil)
(defun my-dabbrev-capf ()
"Workaround for issue with `dabbrev-capf'."
(let ((inhibit-message t)
(disable-cursor-chg t)) ;; 屏蔽dabbrev和corfu的消息
(dabbrev--reset-global-variables)
(setq dabbrev-case-fold-search nil)
(if has-dabbrev-capf
(ignore-errors
(dabbrev-capf))
(cl-letf (((symbol-function #'completion-in-region)
(lambda (beg end table &rest args)
(list beg end table))))
(dabbrev-completion)) ;; hack dabbrev-completion to return list
)))
(add-to-list 'completion-at-point-functions 'my-dabbrev-capf)
:config (setq has-dabbrev-capf (functionp 'dabbrev-capf)))
不理解为啥要这个变量 has-dabbrev-capf
,直接判断不就行了么?
PS:如果认为cape有问题,可以尝试提交issue或者PR,造福大众
这是楼上的配置,我只是贴过来而已
我猜楼上在 29还没发布的时候就开始用了,而且可能在混用多个 emacs 版本。
至于cape-dabbrev ,我用了一下似乎行为和我的预期有些不一样,但也不知道具体什么问题,这个描述有点模糊。有时间看看它是怎么实现的
lynnux
2023 年12 月 26 日 08:39
9
延迟加载还有就是兼容低版本呗。cape那种bug也不好复现,英文又不太好,自己用着舒服就行了呗。主要我的好多配置hack度都很高