helm/ivy 都有提供列表选单形式的 completing-read。
helm/ivy 的好处是可以善用强大的过滤功能,但弹窗对整个窗口布局都有影响。于是想到用 company 来实现 completing-read:
(defconst company-completing-read-candidates nil)
(defconst company-completing-read-grab-start nil)
(defun company-completing-read-backend (command &optional arg &rest ignored)
(interactive (list 'interactive))
(cl-case command
(interactive (company-begin-backend 'company-completing-read-backend))
(prefix (save-excursion
(save-restriction
(narrow-to-region company-completing-read-grab-start (point-max))
(company-grab-symbol))))
(candidates
(cl-remove-if-not
(lambda (c) (string-prefix-p arg c))
company-completing-read-candidates))))
(defun company-completing-read-begin-backend (backend &optional callback)
"Start a completion at point using BACKEND.
Different from `company-begin-backend':
``` diff
-- (or (company-manual-begin)
-- (user-error \"Cannot complete at point\"))
++ (company-manual-begin)
```"
(interactive (let ((val (completing-read "Company backend: "
obarray
'functionp nil "company-")))
(when val
(list (intern val)))))
(when (setq company-callback callback)
(add-hook 'company-completion-finished-hook company-callback nil t))
(add-hook 'company-completion-cancelled-hook 'company-remove-callback nil t)
(add-hook 'company-completion-finished-hook 'company-remove-callback nil t)
(setq company-backend backend)
;; Return non-nil if active.
(company-manual-begin))
(defun company-completing-read (collection &optional initial-input)
(let ((company-completing-read-candidates collection))
(setq company-completing-read-grab-start (point))
(company-completing-read-begin-backend 'company-completing-read-backend)
(when initial-input
(insert initial-input))))
感觉用于 sniappet 展开的时候比 yas-choose-value + helm/ivy 要直观——其补全窗口距离编辑点太远,造成短暂的失焦:
虽然每天都在用 company,但从来没认真研究过。不知如何更好地处理 prefix,目前是用一个全局变量,加上 narrow-to-region,以避免光标之前已经存在的字符影响补全,例如:
(progn
(insert "a")
(company-completing-read '("foo" "bar")))
如果不屏蔽掉字符 a
以及它前面的内容,会导致 prefix 误判。
WARNING
这只是一项实验,尚不完善以及不确定对 company 原有功能会产生什么影响。