【分享】在 vertico 中标记 candidates 并应用 embark

在 Embark 中,标记 candidates 并应用 embark-act-all 是通过 embark-collect 来完成的,具体的步骤如下:

  1. run embark-act
  2. embark-collect
  3. 在 collect buffer 中,按 m 标记
  4. A (embark-act-all)
  5. 选择命令

这个方案有一个明显的问题:操作太繁琐了。前三步的目的是标记 candidates ,那为什么不直接在 minibuffer 中标记呢?然后对标记的对象应用 embark ,这样即节省了操作步数,也免去了 buffer 切换等容易打乱工作流的操作。

基于以上想法,下面是我的解决方案:

(defvar eli/vertico-marked-list nil
  "List of marked candidates in minibuffer.")

(defun eli/vertico-mark ()
  "Mark candidates in minibuffer"
  (interactive)
  (let*
	  ((selected (embark--vertico-selected))
	   (target (cdr selected)))
	(if (member target eli/vertico-marked-list)
		(setq eli/vertico-marked-list (delete target eli/vertico-marked-list))
	  (push target eli/vertico-marked-list))))

(defun eli/vertico-marked-p (candidate)
  "Return t if CANDIDATE is in `eli/vertico-marked-list'."
  (member (concat vertico--base candidate) eli/vertico-marked-list))

(defun eli/vertico--format-candidate-hl-marked (args)
  "Highlight marked vertico items."
  (let* ((cand (car args)))
	(if (eli/vertico-marked-p cand)
		(add-face-text-property 0 (length cand) 'embark-collect-marked nil cand)
	  (vertico--remove-face 0 (length cand) 'embark-collect-marked cand))
	args))

(advice-add #'vertico--format-candidate :filter-args #'eli/vertico--format-candidate-hl-marked)

(defun eli/vertico-marked-list-clean ()
  "Initialize `eli/vertico-marked-list' and `eli/vertico-mark-type'."
  (setq eli/vertico-marked-list nil))

(add-hook 'minibuffer-setup-hook #'eli/vertico-marked-list-clean)

(defun eli/embark-vertico-marked-list ()
  (when eli/vertico-marked-list
    (cons (car (embark--vertico-selected)) (reverse eli/vertico-marked-list))))

(add-hook 'embark-candidate-collectors #'eli/embark-vertico-marked-list -100)
(setq embark-confirm-act-all nil)
(keymap-set vertico-map "C-SPC" #'eli/vertico-mark)
(keymap-set vertico-map "C-," #'embark-act-all)

效果如下

Peek 2023-04-08 11-37

Peek 2023-04-08 11-39

Edit :修复一些 Bug 使其更通用。

7 个赞

抄走。 :grin: :grin:

重新修改了下逻辑,使用 embark-act-all 而不是 embark-act

草,原来标记 candidates 的默认方法这么复杂 :joy:,楼主的这个hack太好用了

快推到上游 :grin:

1 个赞

几个建议哈

  1. vertico--format-candidate 可以不必 override, 可以改成 filter-args , 通过 advice 来更改第一个 参数,这样可以更多复用 vertico 原来的代码。

  2. embark--maybe-transform-candidates 也可以不用 override 来覆盖,可以简单地向 embark-candidate-collectors 添加个函数即可:

  (defun eli/embark-vertico-marked-list ()
    (when eli/vertico-marked-list
      (cons (car (embark--vertico-selected)) eli/vertico-marked-list)))
  (pushnew! embark-candidate-collectors #'eli/embark-vertico-marked-list)
1 个赞

多谢建议,已经改过来了

@zqso @yangyingchao 现在这个方案已被上游采纳,并且在 regular buffers 中也可以进行标记。

目标功能已经基本实现:

具体讨论见:Generalize embark-mark and embark-unmark · Issue #253 · oantolin/embark · GitHub

7 个赞

优秀!感谢大佬~

好长的讨论。简单来说,就是在做,还没有进主干 :)

已经进master了,不过目前embark的实现方式是需要embark-act再embark-select,不能直接一步embark-select

这和作者讨论过,一是不这么做实现起来比较困难,二是用户可以自己定义键盘宏,比如

(global-set-key (kbd "C-;") (kbd "C-. SPC"))
3 个赞

已经更新到 master,但是不知道怎么操作。

楼主能简单说明下操作步骤吗?标记是用的哪个命令?

@aqua0210 默认 C-. SPCembark-select

这儿有个changelog (作者把时间写成2020年了,内容没有错):

@VagrantJoker 前后选择了好几个,但怎么一键取消呢?

1 个赞

默认是 C-. A SPC ,也可以自己绑定个宏,不过这种做法在我这有时会有 bug ,就先自己写了个函数来取消选择

(defun eli/embark-deselect ()
  "Deselect all selected candidates."
  (interactive)
  (when embark--selection
	(dolist (target embark--selection)
	  (when-let ((overlay (cdr target)))
		(delete-overlay overlay)))
	(setq embark--selection nil)))
1 个赞

大佬总有办法。抄走了。 :star_struck:

作者已经在 README 中更新使用方法。默认是通过 embark-act 来调用 embark-select ,选择完成后调用 embark-act-all 就好了

1 个赞