emacs 29 新增的 dabbrev-capf 出现 (wrong-type-argument stringp nil) 错误

在 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-capfdabbrev--abbrev-at-pointdabbrev--goto-start-of-abbrevlooking-at 。问题出在 dabbrev--goto-start-of-abbrevlooking-at 的参数上:

image

此处的 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-expanddabbrev-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,也许能用

看看我的配置很早就用这个函数了

1 个赞

.emacs.d/settings/package_extra.el :+1:

(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,造福大众 :grinning:

这是楼上的配置,我只是贴过来而已 :joy:

我猜楼上在 29还没发布的时候就开始用了,而且可能在混用多个 emacs 版本。

至于cape-dabbrev ,我用了一下似乎行为和我的预期有些不一样,但也不知道具体什么问题,这个描述有点模糊。有时间看看它是怎么实现的

延迟加载还有就是兼容低版本呗。cape那种bug也不好复现,英文又不太好,自己用着舒服就行了呗。主要我的好多配置hack度都很高