如何把用户输入添加到 helm/ivy 候选列表中?

就像 helm-find-file 会把用户输入添加到候选列表中:

[?] file <----
file1
file2
file3

当输入到 file4 没有任何匹配到任何文件名的时候,仍然有一个可选项:

[?] file4 <----

看了下 wiki 上的这个例子:

(defun helm/test-default-action (candidate)
  (browse-url (format
               "http://www.google.com/search?q=%s"
               (url-hexify-string candidate))))

(helm :sources (helm-build-dummy-source "test"
                 :action '(("Google" . helm/test-default-action)))
      :buffer "*helm test*")

是可以处理用户输入了,但是固定选项如何设置?

虽然字我都认识,但是没看明白要做什么 :joy:

helm-find-files 应该是自己在 candidates 函数里实现的

helm-find-files -> helm-find-files-1 -> helm-source-find-files -> helm-find-files-get-candidates -> search /(list path)/

不了解 Ivy,只就 Helm 来说。

helm-build-dummy-source 是添加一个新的 Source,一般用来处理其它的 Source 没匹配时的情况,比如在 helm-filtered-bookmarks 中,它用来创建一个新的 Bookmark。又比如这一例(允许选择和创建一个名字):

(helm :sources
      (list (helm-build-sync-source "Names"
              :candidates '("Alice" "Ekko" "Jess"))
            (helm-build-dummy-source "Create Name")))

不清楚你的需求,你可以把输入加入候选列表(注意要把 volatile 设为 t):

(helm :sources
      (helm-build-sync-source "Test"
        :volatile t
        :candidates
        (lambda () (list "foo" "bar" "baz" helm-pattern))))

但这么做好像没什么意义。

1 个赞

给定一组 candidates,让用户选择,但是如果都不符合期望,就以用户输入为准,helm-find-files 就是这么做的。但是 helm-find-files 太复杂了,一时找不到关键所在,就来论坛求助了😄

:volatile t 是有效的,接下来我看看怎么给用户输入的那一行做个标记,以示区别。

让我想到了 Gentoo 的 eselect。

感谢 @xuchunyang 提供思路。不过,其实不需要 :volatile t,也无需往 :candidates 列表添加 helm-pattern

(let ((default-candidates '("file1" "file12" "file3"))
      (user-input-indicator "[?] "))
  (helm :sources
        (helm-build-sync-source "Test"
          :candidates default-candidates

          :action
          (lambda (candidate)
            (message "Result: %s"
                     (if (string-prefix-p user-input-indicator candidate)
                         helm-pattern
                       candidate)))

          :filtered-candidate-transformer
          (lambda (candidates _source)
            (if (not (string-empty-p helm-pattern))
                (if (member helm-pattern default-candidates)
                    candidates
                  (cons (concat user-input-indicator helm-pattern) candidates))
              candidates))
          )))

emacs-helm-source

存在有两个小问题:

  1. 高亮始终落在第一个选项上。当用户输入不完全匹配时,高亮应该落在第二项;
  2. 初始化之后列表没有行号,直到用户输入才显示行号,不过 heml-find-files 也是这样(或是我哪里设置不对?)。

Ivy 可以实现这种功能,我在 exwmx-dmenu 里面用了好久了。。。。

今天github被墙了?

               (ivy-read
                “test”
                #'(lambda (input)
                    (cons (if (< (length input) 1)
                              "**NULL**"
                            input)
                          (cl-remove-if-not
                           #'(lambda (cmd)
                               (string-match-p (funcall ivy--regex-function input) cmd))
                           commands)))
                :dynamic-collection t
                :keymap exwmx-dmenu-ivy-minibuffer-map)))))))
1 个赞

ivy 这个写法挺直观,不过为什么 “test” 最后一个字符显示不出来?

emacs -Q 测试一下,我这边似乎没有这个问题

对ivy来说,虽然加不到里面,但是默认的有(setq ivy-use-selectable-prompt t)。设定后就可以选择用户输入了

测了,新建一份配置,就加载 ivy 一个包,问题仍然存在。

  • macOS 10.11.6
  • emacs 25.2 / 26.0.50
  • ivy-20170817.1000 / ivy-20170911.1034

“test” 后面加一个空格就好像可以了。。。。

(ivy-read
 "test "
 #'(lambda (input)
     (cons (if (< (length input) 1)
               "**NULL**"
             input)
           '("this" "that" "hello")))
 :dynamic-collection t)

看了 ivy-read 文档,这是 feature :sweat_smile:

PROMPT is a format string, normally ending in a colon and a
space; %d anywhere in the string is replaced by the current
number of matching candidates.  For the literal % character,
escape it with %%. See also ‘ivy-count-format’.

正确应该这样写:

"Test: "

先前差点就去提 issue 了,还好没找到 ivy 项目地址,而是跑去 avy 了,搜了一下发现没有 ivy-read 相关的结果,才意识到不对。ivy 的项目地址竟然是 https://github.com/abo-abo/swiper :sweat_smile:

1 个赞

ivy, counsel, swiper 三位一体,最初我也很困惑。