让company-yasnippet更好的工作

测试了一下更新后的company。 发现一个问题: doc-buffer preview 报错:

Error running timer: (error "Company: backend (company-capf :separate company-yasnippet :separate company-tempo :separate company-keywords :separate company-abbrev) error \"Wrong type argument: stringp, (stringp nil)\" with args (doc-buffer template)")

我有一个 template snippet,内容如下:

# -*- mode: snippet -*-
# name: template
# key: template
# --
;;; ${1:$$(file-name-base)}.el --- $2 -*- lexical-binding: t; -*-

;;; Time-stamp: <2020-02-02 17:37:34 stardiviner>

;;; Commentary:

$3

;;; Code:

$0





(provide '$1)

;;; $1.el ends here

是因为snippet里面的 $$(file-name-base) 函数的缘故。删掉这个函数后就可以预览了。不知道有什么解决办法没有?可能是因为yasnippet内部的expand机制缘故。

是会报错,问题是出在yasnipet-expand-snippet。测试

(yas-expand-snippet "${1:$$(file-name-base)}.el")

*** Eval error ***  Wrong type argument: stringp, (stringp nil)

这样测试是能过的

(yas-expand-snippet "${1:$$(upcase yas-text)}")

问题是这个file-name-base应该。

这种情况应该比较少见,类似函数 file-name-base 这种依赖于当前环境的这种情况。 (如果没有解决办法,我觉得可以加一个条件判断来跳过报错,或者可以用 ignore-errors这样的粗暴办法)

这样忽略错误不是根本办法。。可以让yasnippet author 瞅瞅其实。

肯定的,所以我说是“如果没有解决办法”。毕竟我也不熟悉yasnippet的展开机制。

比较奇怪的是,我第一次选择到 template的时候,会上面那个错误,但是当我delete一个字符,然后重新补全的时候,company-yasnippet又能显示 template 的 doc-buffer 了。不过里面并没有显示 $$(file-name-base) 的文件名,难道是cache缓存的结果?

file-name-base 接受一个参数 filename 。。docbuffer没有所以挂掉了。。我在瞅瞅yasnippet

还真的是,那就是我snippet写错了。

另外,我看 company-yasnippet--doc 的代码逻辑,是不是把 (funcall mode) 的那个逻辑放在启动yasnippet-minor-mode 和expand之前更好点?

@@ -101,16 +101,16 @@ (defun company-yasnippet--doc (arg)
   (let ((template (get-text-property 0 'yas-template arg))
         (mode major-mode))
     (with-current-buffer (company-doc-buffer)
-      (yas-minor-mode 1)
-      (yas-expand-snippet (yas--template-content template))
       (delay-mode-hooks
         (let ((inhibit-message t))
           (if (eq mode 'web-mode)
-                (progn
-                  (setq mode 'html-mode)
-                  (funcall mode))
+              (progn
+                (setq mode 'html-mode)
+                (funcall mode))
             (funcall mode)))
-         (ignore-errors (font-lock-ensure)))
+        (ignore-errors (font-lock-ensure))
+        (yas-minor-mode 1)
+        (yas-expand-snippet (yas--template-content template)))
       (current-buffer))))

没啥影响,你这个还是要报错的。这个filename-base会接收当前文件名为参数,docbuffer并没有。

doc-buffer 应该有buffer名字的 (with-current-buffer (company-doc-buffer) ... (company-doc-buffer) 返回的是 #<buffer *company-documentation*>.

你看下源码。。filename或者buffer-file-name…

(defun file-name-base (&optional filename)
  "Return the base name of the FILENAME: no directory, no extension."
  (declare (advertised-calling-convention (filename) "27.1"))
  (file-name-sans-extension
   (file-name-nondirectory (or filename (buffer-file-name)))))

这不就解决了吗。。给一个buffer-file-name不就好了

我试过了,还是报错。奇怪。

。。在company里修的。提了pr,等合并

我想了下,觉得还是 再加上 ignore-errors 比较稳定,因为yasnippet expand可能不止这种缺少 buffer-file-name 的情况,还有其他的edge case。所以ignore-errors会防止报错。个人观点。虽然这个导致了忽略报错情况。

我也赞成用 ignore-error 或者直接给出错误提示。如果 snippet 写错了或者由于 bug 无法展开不至于在 company 里报错。

错误是出在使用了这种特殊函数的情况,其他的snippet展开应该是没问题。加上ignore-error是可以的,但是没有doc出来也没有错误。用户不会觉得很奇怪吗。给出错误会打扰用户输入的思维。感觉也不是很好。

可以在 doc buffer 中显示原始字符串,并给出错误提醒。

还有个问题:为什么要用 delay-mode-hooks?有什么考量呢?

我简化了下,这样就能工作:

(defun my-company-yasnippet--doc (arg)
      (let ((template (get-text-property 0 'yas-template arg))
            (mode major-mode)
            (file-name (buffer-file-name)))
        (with-current-buffer (company-doc-buffer)
          (let ((inhibit-message t)
                (buffer-file-name file-name))
            (ignore-errors
              (yas-minor-mode 1)
              (yas-expand-snippet (yas--template-content template))
              (funcall mode)
              (font-lock-ensure)))
          (current-buffer))))

company有没有办法做到 0 delay 自动弹出且不影响输入的流畅性呢?

隔壁有帖子说native-comp branch 很流畅。