创建了一个helm source, 如何在action里面强制刷新candidates, 而不退出helm?

helm有这方面的开发文档吗?

helm-force-update?

不行, 照样退出

我画错重点了。

不退出的例子 helm 本身就提供了,试试 helm-mini,找到 Actions > Grep buffers ...,按 C-u RET 在所有(命中率大一些) buffer 中查找关键字。

helm 本质上就是创建一个 buffer,然后生成 candidates 并写入。

所以,用新的 candidate 原地重写一遍就行:

(defun test-helm (&optional candidates)
  (interactive)
  (helm :sources
        (helm-build-sync-source "Test"
          :candidates (or candidates '("file1" "file12" "file3"))
          :action
          (list
           (cons "Print selection (default)"
                 (lambda (candidate)
                   (message "Result: %s" candidate)))
           (cons "Update candidates (+1)"
                 (lambda (candidate)
                   (let* ((cands (helm-get-candidates
                                  (helm-get-current-source)))
                          (new-cand (format "file%s" (1+ (length cands)))))
                     (test-helm `(,@cands ,new-cand)))))))))

看上去是在action里重新启动了helm, 这个方法可以理解. 能不能不退出helm, 就是不用重启helm? 只更新候选列表?

helm 启动之后阻塞在 read 函数:

(helm-read-from-minibuffer prompt ...)

一旦 read 函数读到了回车(不管是在 candidate 列表还是 action 列表),就继续往下走,直到退出 helm。

如果想要阻止 helm 退出,就得想办法:

  1. 拦截某个 action 的回车事件,让 read 继续等待输入。

  2. 除了拦截回车键,你还得想办法在 action 之后,重建 candidate 列表。可以参考 helm-select-action -> helm-show-action-buffer 的实现,这两个函数刚好完成了相反的过程:从 candidate 列表到 action 列表。

如果不转到 action 执行,而是直接在 candidate 列表按某个快捷键,应该就不会遇到退出问题,只需考虑如何刷新当前列表了。

我在helm项目咨询了一下, helm-find-files已经实现了该功能, 不过看代码相当复杂. 想搞个简化版本.

不要被 helm-ff 复杂的表象迷惑,有 :update 一切就很简单了:

(defvar test-cands '("file1" "file12" "file3"))

(defun get-cands () test-cands)

(defun test-helm ()
  (interactive)
  (helm :sources
        (helm-build-sync-source "Test"
          :candidates 'get-cands
          :update (lambda ()
                    (let* ((cands (helm-get-candidates
                                   (helm-get-current-source)))
                           (new-cand (format "file%s" (1+ (length cands)))))
                      (setq test-cands `(,@cands ,new-cand)))))))

在 candidate 列表按 C-c C-u

操作方式不太一样, helm-find-file里面, 选中一个目录回车, 会直接进入该目录(依然是helm界面), 这个操作比较自然, C-c C-u操作不太自然, 跟回车是俩不同的操作, 很容易误操作.

跟回车进入下个目录不一样,C-c C-u 更新的是匹配当前输入的 candidate 列表,这对于 helm ff 很有用。由于 helm 用了缓存,所以常常会出现 find file 看不到新文件的情况,这时就需要 C-c C-u 了。

我的思路是, 进入某个目录后, 删除缓存

(defun test-get-cands ()
  (if (string-prefix-p "dir2 >" helm-input)
      (list "file21" "file22")
    (list "file1" "dir2 >" "file3")))

(defun test-persistent-action (cand)
  (with-selected-window (or (active-minibuffer-window) (minibuffer-window))
    (unless (or (helm-follow-mode-p) helm--temp-follow-flag)
      (delete-minibuffer-contents)
      (set-text-properties 0 (length cand) nil cand)
      (insert cand)))
  (helm-check-minibuffer-input))

(defun test-action-RET ()
  (interactive)
  (with-helm-alive-p
    (let ((cand (helm-get-selection)))
      (if (equal cand "dir2 >")
          (progn
            (helm-attrset 'test-action '(test-persistent-action . never-split))
            (helm-execute-persistent-action 'test-action))
        (prog1 (print cand)
          (helm-exit-minibuffer))))))

(defun test-helm ()
  (interactive)
  (helm :sources
        (helm-build-sync-source "Test"
          :candidates 'test-get-cands
          :match (lambda (cand)
                   (let ((patt (replace-regexp-in-string "^dir2 >" "" helm-pattern)))
                     (when (string-match-p patt cand)
                       cand)))
          :volatile t
          :keymap (let ((map (make-sparse-keymap)))
                    (set-keymap-parent map helm-map)
                    (define-key map (kbd "RET") 'test-action-RET)
                    map))))

其中, test-persistent-action里面这一段是什么逻辑? 看上去这么复杂?

  (with-selected-window (or (active-minibuffer-window) (minibuffer-window))
    (unless (or (helm-follow-mode-p) helm--temp-follow-flag)
      (delete-minibuffer-contents)
      (set-text-properties 0 (length cand) nil cand)
      (insert cand)))

跟helm作者沟通有点困难, 不知道什么原因…

这段代码的作用就是把当前 candidate 作为输入(即过滤条件),写到 minibuffer。这样才能跟后续新的列表匹配。也可以删除输入回到先前的列表。行为逻辑上保持自洽。

作者一开始没明白你的意图吧,几轮回复之后大概有点倦怠了,维护这样一个项目压力不小,之前不是还闹情绪罢工过一阵子嘛。

另,我把 issue 链接贴一下:[help] I want to write a helm source. In the action, I want to update candidates but don't quit helm. Is there documents for this? · Issue #2401 · emacs-helm/helm · GitHub 以便其他人阅览此帖。

功能了解, 就是里面那么多布尔条件, 好像都是helm内部的东西, 感觉挺复杂.

对他来说很简单的事情, 对我们来说很复杂. 而且helm的文档好像不够详细, 有几个问题还没找到答案:

  1. 如何定义一个helm的会话里的变量, 仅在该会话里生效, 所有的action, candidate函数都可以访问的
  2. action想修改default-directory, 但是好像不生效 (测试发现, action和candidate函数运行在不同的buffer里)

好像只能用全局变量了.

想到办法了.

(let ((foo "abc"))
    (helm ...)))