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 退出,就得想办法:
-
拦截某个 action 的回车事件,让 read 继续等待输入。
-
除了拦截回车键,你还得想办法在 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的文档好像不够详细, 有几个问题还没找到答案:
- 如何定义一个helm的会话里的变量, 仅在该会话里生效, 所有的action, candidate函数都可以访问的
- action想修改default-directory, 但是好像不生效 (测试发现, action和candidate函数运行在不同的buffer里)
好像只能用全局变量了.
想到办法了.
(let ((foo "abc"))
(helm ...)))