在你使用 auto-complete/company 的时候,有没有遇到和 yasnippet 一起使用时,TAB 键冲突的问题呢? 你是如何优雅的使用 auto complete, 同时还能享受随时展开自己定义的一大堆 snippet 红利呢?
如果你遇到类似的难题,那么这篇介绍的帖子就是你的菜,否则,你可能首先得了解一下稍后介绍的一些使用 auto complete 的习惯,以及该如何使用 Yet another snippet.
我会尽可能介绍的浅显易懂,希望也可以帮到从未用过 company, yasnippet 的初学者。
不废话,先贴配置。
company 配置
(require 'company)
(defun set-company-tab ()
(define-key company-active-map [tab] 'company-select-next-if-tooltip-visible-or-complete-selection)
(define-key company-active-map (kbd "TAB") 'company-select-next-if-tooltip-visible-or-complete-selection)
)
(set-company-tab)
(define-key company-active-map (kbd "<backtab>") 'company-select-previous)
(define-key company-active-map (kbd "S-TAB") 'company-select-previous)
(define-key company-active-map (kbd "C-s") 'company-filter-candidates)
(setq company-show-numbers t)
(setq company-frontends
'(company-pseudo-tooltip-unless-just-one-frontend
company-preview-frontend
company-echo-metadata-frontend))
(add-hook 'company-mode-hook '(lambda ()
(setq company-backends
(delete 'company-bbdb
(delete 'company-oddmuse
(delete 'company-cmake
(delete 'company-clang company-backends)))))
))
(defun advice-only-show-tooltip-when-invoked (orig-fun command)
"原始的 company-pseudo-tooltip-unless-just-one-frontend-with-delay, 它一直会显示
candidates tooltip, 除非只有一个候选结果时,此时,它会不显示, 这个 advice 则是让其
完全不显示, 但是同时仍旧保持 inline 提示, 类似于 auto-complete 当中, 设定
ac-auto-show-menu 为 nil 的情形, 这种模式比较适合在 yasnippet 正在 expanding 时使用。"
(when (company-explicit-action-p)
(apply orig-fun command)))
(defun advice-always-trigger-yas (orig-fun &rest command)
(interactive)
(unless (ignore-errors (yas-expand))
(apply orig-fun command)))
(with-eval-after-load 'yasnippet
(defun yas/disable-company-tooltip ()
(interactive)
(advice-add #'company-pseudo-tooltip-unless-just-one-frontend :around #'advice-only-show-tooltip-when-invoked)
(define-key company-active-map [tab] 'yas-next-field-or-maybe-expand)
(define-key company-active-map (kbd "TAB") 'yas-next-field-or-maybe-expand)
)
(defun yas/restore-company-tooltip ()
(interactive)
(advice-remove #'company-pseudo-tooltip-unless-just-one-frontend #'advice-only-show-tooltip-when-invoked)
(set-company-tab)
)
(add-hook 'yas-before-expand-snippet-hook 'yas/disable-company-tooltip)
(add-hook 'yas-after-exit-snippet-hook 'yas/restore-company-tooltip)
;; 这个可以确保,如果当前 key 是一个 snippet, 则一定展开 snippet,
;; 而忽略掉正常的 company 完成。
(advice-add #'company-select-next-if-tooltip-visible-or-complete-selection :around #'advice-always-trigger-yas)
(advice-add #'company-complete-common :around #'advice-always-trigger-yas)
(advice-add #'company-complete-common-or-cycle :around #'advice-always-trigger-yas)
)
(setq company-auto-commit t)
;; 32 空格, 41 右圆括号, 46 是 dot 字符
;; 这里我们移除空格,添加逗号(44), 分号(59)
;; 注意: C-x = 用来检测光标下字符的数字,(insert 数字) 用来测试数字对应的字符。
(setq company-auto-commit-chars '(41 46 44 59))
(add-hook 'after-init-hook 'global-company-mode)
(provide 'company_init)
;;; company_init.el ends here
你可以 (require 'company_init) 来加载这个文件。
company 配置简单介绍
首先在这个配置下,使用自动完成,是遵循一些早期在 AC 下面的一些惯例来着,下面列出来。
- 在只有唯一的一个 candidate 的时候, 可以使用 TAB/回车 来自动完成.
- 如果不止一个 candidates, 此时,TAB 的功能是选择下一个 candidate.
- 任何时候,回车键,总是完成当前选择的 candidate
当然还有其他玩法,但是本配置采用的是以上玩法,下面是一个示例:
在下面的介绍中,我会将上面截图中显示 候选列表菜单‘ 的那个弹窗叫做 tooltip. (company 官方叫法), 自动出现在光标后面的那一部分自动完成的字符(这里是
idate`),我称之为 overlay, 也有叫做 inline 的
开始介绍配置:
上面的 (set-company-tab)
这个 function, 就是在设定 TAB 在 tooltip 出来之后的行为,
我的选择就是前面介绍的行为,company-select-next-if-tooltip-visible-or-complete-selection
.
(define-key company-active-map (kbd "<backtab>") 'company-select-previous)
(define-key company-active-map (kbd "S-TAB") 'company-select-previous)
(define-key company-active-map (kbd "C-s") 'company-filter-candidates)
(setq company-show-numbers t)
上面几行代码,设定了Shift + TAB, 可以反着往回选。 也可以像 helm 那样, 通过 C-s, 直接根据关键字, narrow 匹配的 candidates. 同时,也可以 Alt + 1, Alt + 2, 这种直接选择 candidate.
(setq company-frontends
'(company-pseudo-tooltip-unless-just-one-frontend
company-preview-frontend
company-echo-metadata-frontend))
上面的代码,就是将 company 前端当中的 company-preview-if-just-one-frontend 替换为 company-preview-frontend, 这带来的效果就是,无论有几个 candidates, 总会显示 overlay, 控制类似功能的变量在 AC 里也有,叫做 ac-auto-show-menu
(add-hook 'company-mode-hook '(lambda ()
(setq company-backends
(delete 'company-bbdb
(delete 'company-oddmuse
(delete 'company-cmake
(delete 'company-clang company-backends)))))
))
然后,我删除了一些对我没啥用的 backends.
当遇到 yasnippet.
接下来, 描述下当引入 yasnippet 之后,我想解决的问题.
- yas 也使用同样的快捷键 TAB 来自动展开(expand)我们自己写的模板,
例如,你定义了一个 snippet, 他使用 pr 作为 key, 当你键入
pr
之后, 它会为你 expand 成println!("{}", |)
, 但同时,如果你开起了 company, 当你键入pr
时,company 可能会给你一大堆像下面一样的 candidates 建议。
那么, 此时按下 TAB 有两个选择:
- 选择下一个 candidate, 也就是那个 priv.
- 展开 pr 为 println!
此时,我希望总是优先展开我自己定义的 snippet。
- 当使用 yas 在 expanding 过程中时, yas 同样使用 TAB 切换到下一个 tab stop field. 详情见 Writing snippets
此时,我的选择是:
- 我不想受到 company 的 tooltip 干扰,这个时候出个 menu, 会很烦.
- 我也不想在此时使用 TAB 来自动完成 overlay, 如果我真想这样,我可以用回车键。
如果能看明白上面介绍的痛点,那么再来继续看代码,如果不明白,之有先自己试试了。
继续介绍代码
(add-hook 'yas-before-expand-snippet-hook 'yas/disable-company-tooltip)
(add-hook 'yas-after-exit-snippet-hook 'yas/restore-company-tooltip)
这两行代码解决的问题是, 当 yas 正在 expanding 的时候,关闭 tooltip, 并且使用 TAB 键时,总是切换到下一个 tab stop field, 如果希望 auto complete, 可以使用回车。 当 yas 展开完成后,会自动恢复成原来的配置。
(advice-add #'company-select-next-if-tooltip-visible-or-complete-selection :around #'advice-always-trigger-yas)
(advice-add #'company-complete-common :around #'advice-always-trigger-yas)
(advice-add #'company-complete-common-or-cycle :around #'advice-always-trigger-yas)
上面三行代码,解决的是,无论你使用 company 提供的无论哪种策略的来 complete, 使用 yas 展开 snippet 总是优先。
感谢
在编写本贴时,我注意到有坛子里已经有一些类似的帖子,但是在我编辑时, 并没有看过或参考过, 所以一并贴在这里,可以一起学习,阅读.
最后,感谢电报群里面 Youmu(@Youmu), 1ab(@stanley_110101011), 爱丽冥王星(@casouri) 以及等等早期帮过我,都可能记不得名字的大佬, 是你们不吝赐教,才有了上面的这个 company 配置。
有任何疑问欢迎评论,也欢迎指正。