「分享」使用 TAB 进行 yas-expand,缩进(indentation)和补全

一直以来都是使用 CorfuTAB-and-Go 模式自动补全,但有时发现自动补全有点干扰输入,所以最近打算尝试手动按需启动补全。

但是直接使用 TAB 键触发 completion-at-point 的话,就没法使用 TAB 触发 Yasnippet 的 snippet 展开,也无法使用 Emacs 默认的 TAB 缩进 (indent-for-tab-command)。

参考了帖子 解决Yasnippet和Company的TAB冲突问题 中大佬们分享的方案, 还有 Emacs TG 中文群中群友的帮助,目前整合为以下方案, yas-expand,缩进(indentation)和补全都可以用 TAB来触发。

欢迎大家指点和帮忙改进。

(defun smarter-yas-expand-indent-or-complete ()
    "Try to `yas-expand' and `yas-next-field' at current cursor position.
If failed, try indent command, then try `completion-at-point'"
    (interactive)
    (cond
     ((and yas-minor-mode (yas-active-snippets))
      (yas-next-field))
     (t
      (let ((old-point (point))
            (old-tick (buffer-chars-modified-tick)))
        (yas-expand)
        (when (and (eq old-point (point))
                   (eq old-tick (buffer-chars-modified-tick)))
          (if (derived-mode-p 'c-mode 'c++-mode)
              (call-interactively 'c-indent-line-or-region)
            (unless (derived-mode-p 'haskell-mode)
              (indent-for-tab-command)))
          (when (and (eq old-point (point))
                     (eq old-tick (buffer-chars-modified-tick))
                     (not (region-active-p)))
            (if (check-expansion)
                (completion-at-point))))))))

  (defun check-expansion ()
    (save-excursion
      (if (looking-at "\\_>") t
        (backward-char 1)
        (if (looking-at "\\.") t
          (backward-char 1)
          (if (or (looking-at "->")
                  (looking-at "::"))
              t nil)))))
 
  (define-key prog-mode-map (kbd "<tab>") 'smarter-yas-expand-indent-or-complete)

indent-for-tab-command 在 haskell-mode中无法正常使用,目前还没找到方案。C/C++ 中保留了使用 c-indent-line-or-region 进行缩进。

1 个赞

问下,corfu-autocorfu-auto-delay 不能解决问题吗?

之前也考虑过设置 corfu-auto-delay 长点时间。但后面觉得难受,需要补全时还要等,不如手动 TAB 出来的快。

其实尝试手动弹出补全,主要是有时想完全不用补全,体验那种自己手刻代码的感觉(当然也可以临时关闭 corfu实现)。因为使用补全多了后,在没补全时反而不会写了。

(setq corfu-auto nil) 就可以关闭自动补全呀,然后tab手动开启。

目前我就是设置了 (setq corfu-auto nil) ,通过 TAB 手动开启。但Emacs中好几个功能都用 TAB,包括缩进,Yasnippet 的yas-expand,展开后的跳转。所以才需要做上面的函数的判断这些不同的场景。

实际上在大部分 mode 下(比如 Elisp),直接设置 (setq corfu-auto nil),并设置 (setopt tab-always-indent 'complete),就可以用 TAB触发 corfu 补全了,但 C/C+±mode 默认是用的 c-indent-line-or-region,在缩进正确时并不会触发 complete。不知道在 cc-mode 下,c-indent-line-or-region 和 Emacs 默认的 Indent 有什么区别?

还有就是 haskell-mode,因为 indent-for-tab-command 在有的情况下缩进是 cycle 的,完全没法用。最后会造成 Corfu没法触发。参考:Why TAB cycle indentation for Haskell is a hard problem · haskell/haskell-mode Wiki · GitHub

比如在下面的代码第2行中按了 TAB 后,就会破坏 Haskell的缩进