Emacs 中的 minibuffer 如何实现类似Google实时补全的效果?

我想要在Emacs输入候选项时,获得AJAX API提供的实时补全选项,从中选取。好像Helm有 个插件有类似的功能,忘记是啥了。不知道有没有人知道怎么怎么实现?

插件中如下代码

    (defun kiwix-ping-server ()
      "Ping Kiwix server to set `kiwix-server-available?' global state variable."
      (request kiwix-server-url
               :type "GET"
               :sync t
               :parser (lambda () (libxml-parse-html-region (point-min) (point-max)))
               :success (function* (lambda (&key data &allow-other-keys)
                                     (setq kiwix-server-available? t)))
               :error (function* (lambda (&rest args &key error-thrown &allow-other-keys)
                                   (setq kiwix-server-available? nil)))))

    (defun kiwix-ajax-search-hints (input)
      "Instantly AJAX request to get available Kiwix entry keywords
    list and return a list result."
      (let* ((ajax-api "http://127.0.0.1:8089/suggest?content=wikipedia_zh_all_2015-11&term=")
             (ajax-url (concat ajax-api input))
             (data (request-response-data
                    (request ajax-url
                             :type "GET"
                             :sync t
                             :headers '(("Content-Type" . "application/json"))
                             :parser #'json-read
                             :success (function*
                                       (lambda (&key data &allow-other-keys)
                                         (print data)))))))
        (if (vectorp data)
            (mapcar 'cdar data))))

    ;;;###autoload
    (defun kiwix-at-point (&optional interactively)
      "Search for the symbol at point with `kiwix-query'.

    Or When prefix argument `INTERACTIVELY' specified, then prompt
    for query string and library interactively."
      (interactive "P")
      (kiwix-ping-server)
      (if kiwix-server-available?
          (let* ((library (if (or kiwix-search-interactively interactively)
                              (kiwix-select-library)
                            (kiwix--get-library-name kiwix-default-library)))
                 (query (completing-read ; 如何实现实时搜索补全显示结果?
                         "Kiwix related entries: "
                         (kiwix-ajax-search-hints
                          (if interactively
                              (read-string "Kiwix Search: "
                                           (if mark-active
                                               (buffer-substring (region-beginning) (region-end))
                                             (thing-at-point 'symbol)))
                            (progn (if mark-active
                                       (buffer-substring (region-beginning) (region-end))
                                     (thing-at-point 'symbol))))))))
            (message (format "library: %s, query: %s" library query))
            (if (or (null library)
                    (string-empty-p library)
                    (null query)
                    (string-empty-p query))
                (error "Your query is invalid")
              (kiwix-query query library)))
        (warn "kiwix-serve is not available, please start it at first."))
      (setq kiwix-server-available? nil))

在 “; 如何实现实时搜索补全显示结果?” 位置,这个要怎么实现呢?需要用到 Ivy 和 Helm 么? completing-read 需要的第二个参数是 COLLECTION. 怎么样让他变成函数 呢?这样就可以使用 kiwix-ajax-search-hints 了。

其实就是拉取列表以后补全,可以参考yaoddmuse的代码

我用 kiwix-ajax-search-hints 拉取列表了。但是里面麻烦的是 read-string 在内部。不知道要怎么处理。补全在外部 completing-read.

— UPDATE

reading yaoddmue and helm-yaoddmuse code on GitHub.

现有的补全框架都没法很好的处理这种异步操作的补全候选项,ivy和helm的异步补全只支持异步子进程的补全, 没有通用的异步API(接受一个promise/async stream, 制造补全列表),而且他们实现都很hack

这样啊,那这异步补全是挺麻烦的。

你知道 helm 关于异步补全的部分是哪里么?我去看下。

helm默认只支持async process补全,在helm-get-candidates 里设置了子进程的process filter(子进程向父进程发送数据时调用的callback)具体是函数helm-output-filter-1。filter用helm-insert-match手动插入补全选项。

COLLECTION 可以是函数,印象中有机会每次用户按 TAB 的时候重新计算补全项目。


同步的方法或许是可行的,尤其在获取补全很快的情况下,用 Helm 或 Ivy 提供的工具,可以做到看起来是「实时」的效果。

helm的volatile和ivy的dynamic-collection。设置为t的话他们每次都会重新计算候选项

这个是个好问题

C-h f deferred:$

我觉得这个应该能行

json文件的内容

{“hello”:“hello”,“world”:“world”}

(defvar zxc nil "")

(deferred:$
   (deferred:url-get "http://192.168.1.101:8000/index.json")
   (deferred:nextc it
     (lambda (buf)
       (with-current-buffer buf
         (goto-char 1)
         (setq zxc (json-read))
         )))
   (deferred:nextc it
     (lambda (buf)
       (ivy-read "zxc: " zxc)
       )))

现在我把函数简化成这样。貌似还是不行,是不是我方式写错了?

(completing-read "Kiwix related entries: "
                 (kiwix-ajax-search-hints (read-string "Kiwix Search: ")))

这个deferred我也本来想到了,看了看,觉得还是有点问题。那就是Kiwix 的搜索请求API 是需要输入值的。也就是说当前的输入需要实时发送给这个API。不知道用你上面的形式怎 么嵌入。我尝试了一下:

(defvar kiwix-search-hints nil "")

(deferred:$
  (deferred:url-get (concat "http://127.0.0.1:8089/suggest?content=wikipedia_zh_all_2015-11&term=" (read-string "Query word: ")))
  (deferred:nextc it
    (lambda (buf)
      (with-current-buffer buf
        (goto-char 1)
        (setq zxc (json-read))
        )))
  (deferred:nextc it
    (lambda (buf)
      (ivy-read "Kiwix Search: " kiwix-search-hints))))

Helm 我还在看,对我来说有点难。:slight_smile:

zxc 你没有替换完欧

所以你得到的应该是个空表

json read出来的是什么

ivy里能用吗

你这个本地的ajax 有我能用的版本吗

昨晚在测试deferred函数的时候,弹出Ivy的错误stacktrace,发现 ivy-read 的调用参 数里包含 :dynamic-collection想到这是 @cireu 提到过的,哈哈,感谢。于是测试了一下用例。结果实现了。 就是这个实现目前还有点粗糙,但是至少实现了。睡觉都舒服了哈哈。实现后的diff如下:

https://github.com/stardiviner/kiwix.el/commit/cc6a8df3b956edbad9520a1a981c248354538b60#diff-f6c1581534ba40a62e1861f0c668b48bR223-R236

另外还有一点问题是: request 抓取的JSON结果会显示在 minibuffer也就是 echo area, 这个会破坏原有的Ivy补全项显示。怎么silent结果呢?我a可能了 request 函数 的参数,好像没有这个选项。Emacs下搜索也没找到含有silent的函数,但是之前我记得在 哪里看到过一个可以silent结果的。另外打算在Ivy的更新函数里尝试下 deferred 异步。 不知道会不会效果更好点。

你把 (print data) 变成 data 就行了

另外 有人尝试过读取