关于对 ivy-read 默认 action 的覆盖

刚刚写了个一辅助输入 nerd fonts 的扩展 nerd-fonts.el,顺便写了个 ivy-nerd-fonts 函数,提供以下功能:

  • M-x ivy-nerd-fonts <pattern> RET 插入选中 icon 到光标位置

    48_PM

  • M-x ivy-nerd-fonts <pattern> M-o 进入菜单,可选择 i: insertw: copy

    03_PM

由于 ivy 默认就有 i: insertw: copy 两个 actio,这样菜单里就出现多余的选项。作为一名 helm 用户,我想当然就用自己的 action 覆盖了默认值。然而等我补完测试之后,发现 ivy action 操作异常:一旦通过 M-x ... M-o w 选择了 copy 操作,之后所有的 M-x ... RET 都变成了 copy。

粗略翻了一下 ivy 源代码和 issues 列表,似乎作者有意不让覆盖/重载默认 action :sweat_smile:

我现在采用的方案是:

(defun ivy-nerd-fonts ()
  ...
  (ivy-save-action
    ...override-default-actions...
    ))

把默认 action 备份起来,函数执行完之后还原。这样所有测试用例通过了,简单试用也未发现异常。

不知有没有其它更好的做法,或者是我调查不周,ivy 其实是支持覆盖默认 action 的?

repo 的描述包含的 md 链接无法渲染,效果为: Emacs [nerd-fonts](https://github.com/ryanoasis/nerd-fonts) utilities.

1 个赞

我觉得从行为统一的角度看,作者的要求合情合理,i w 是内置键,默认行为应该保持 一致,都是简单地插入或拷贝当前选中的候选项,这样对于 ivy 的用户遇到一个新插件时 用起来才不会 surprise 。

你这个插件实现默认按 RET 插入 nerd font 字符就够了,其它的交给 Emacs 本身就 好了,按 M-o w 并不比剪切刚插入的 nerd font 字符方便多少。

1 个赞

菜单的作用其实并不大,默认的也能用。我只是想把默认的操作在当下做一点微调(只复制/插入 icon 符号,忽略名称),并没有破坏一致性,i w 的结果依然是可以预期的。

老铁写个 nerd fonts lib 来替换 all-the-icons 吧,最好 terminal 下也能统一使用,哈哈

2 个赞

好主意。

终端是必须的,我就是终端用户。

1 个赞

之前有个用户给我提过issue,希望doom-modeline集成 nerd fonts,无奈工作量太大,确实没时间搞。老兄能做最好了,我有时间也可以帮帮忙。

研究过all-the-icons和·icons-in-terminal,都有一点缺陷。all-the-icons需要安装多个字体文件,在模拟终端下就无法设置,除非把几种 fonts 合并起来。我尝试过,发现有冲突,得手工处理,要集成到不同字体中还不通用。icons-in-terminal用的方法挺巧妙,把多个字体映射到private 空间。Windows 下好像有些问题,API也不完善,而且作者死活不愿意上 melpa,安装略显麻烦,影响了体验。目前好像也没有更新了。

个人觉得 nerd fonts 可能是目前最好的方案,只需要一种字体,集成了几乎所有 icons 和 symbol,GUI 和 Terminal 通杀,如果能成绝对大赞!到时我会第一时间集成进 Centuar Emacs 和 doom-modeline 中,啊哈哈~~~

Update: 浏览了下 nerd-fonts 的代码,似乎 font data 已经很全了,只需要参考all-the-icons增加一些接口就可以了。老兄真是前进了一大步!!!只有一个疑问,不知道file type 的 icon 是否齐全。nerd fonts 中没有包含 fileicon 和 alltheicon 两个字体。不过从vim-devicons的效果看应该是有的,但我不确认。

工作量主要在于从这些字体中筛选出一套合适的 icons。我想可以优先处理高频使用的 icon,比如文件、分支等等。

这里的terminal是X下的terminal emulator吧,真正的terminal也能用?

传统意义上的终端已经没有人用了吧

真正的终端支持字体就行,不过我看很难呀。 现在远程终端很普遍。

是的,能优先处理常用的 icon 就好,可以参考all-the-icons的图标,还有 sidebar.el/sidebar.el at master · sebastiencs/sidebar.el · GitHub

我照着manual这样设感觉没遇到问题?

(defun ivy-yank-action (x)
  (kill-new x))

(defun ivy-copy-to-buffer-action (x)
  (with-ivy-window
    (insert x)))

(defun myfun (x)
  (insert (concat "hello->" x)))

(ivy-set-actions
 t
 '(
   ("i" ivy-copy-to-buffer-action "inserts")
   ("w" ivy-yank-action "yank")
   ("h" myfun "myfun")
   ))

(ivy-read "test: " (list 'a 'b))

有两个问题需要考虑:

  1. (ivy-set-actions t ...) 是全局有效的,如果想做到单次有效,就要像我前面那样 (save-ivy-action ...)

  2. ivy 如何组织 action 的,我看了一下 (ivy-read) 的实现:

    1839   (let ((extra-actions (cl-delete-duplicates
    1840                         (append (plist-get ivy--actions-list t)
    1841                                 (plist-get ivy--actions-list this-command)
    1842                                 (plist-get ivy--actions-list caller))
    1843                         :key #'car :test #'equal)))
    1844     (when extra-actions
    1845       (setq action
    1846             (cond ((functionp action)
    1847                    `(1
    1848                      ("o" ,action "default")
    1849                      ,@extra-actions))
    1850                   ((null action)
    1851                    `(1
    1852                      ("o" identity "default")
    1853                      ,@extra-actions))
    1854                   (t
    1855                    (delete-dups (append action extra-actions)))))))
    

    通过 (ivy-set-actions ...) 添加的 action 都存放在 ivy--actions-list,最后都汇总到上边代码l里的局部变量 extra-actions,所以它不影响最后展示的 o: default 选项。

    通过 (ivy-read ... :action ...) 添加的 action(就是上边代码里的 action 变量)。如果是单个函数,就直接作为 default,如果是空则用 identity 作为 default。

所以,如果 (ivy-read ...) 不设置 action,就会产生一个啥也不干的 o: default 选项(因为是 identity)。

嗯。看起来应该是这样的,有前面的append。总是会去拿 (plist-get ivy--actions-list t)。所以默认的全局设置还真是对每个生效。

这会看起来的话,如果真是要覆盖的话,你的那种暂存的方法是一种解法 :slight_smile:

let-boundivy--action-list, quick and dirty hack :stuck_out_tongue:

我一开始就是企图用 let 应付的,但是会有问题(具体已经不记得了),所以才专门写一个 save,并且在里边加了一些保护措施,防止 C-g 退出。不过 let 仍然在里边起作用:

先 save 再 let。