用 company 实现 completing-read

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 原有功能会产生什么影响。

2 个赞

真是善于发现问题啊 :+1:t2: