关于 ivy 中一种全选操作的用法问题

ivy

#1

系统:

emacs-mac 26.2(spacemacs)

主要相关的包:

ivy、lsp-java

案例描述:

用 emacs 写 java 程序,在写一个继承类时,想插入一些 override 的方法,我执行如下操作:

  1. 执行 lsp-execute-code-action;
  2. 选择 Override/Implement Methods…;
  3. 弹出的 ivy minibuffer 中出现了 lsp-java 找到的候选方法;
  4. 在这个例子中有 18 个方法;全部已被选中,也就是后面有个 ✓ 符号;
  5. 光标移动到不想插入的方法上,按 RET or C-m 取消它,光标会回到 line 1;
  6. 重复上一步操作直到仅剩想插入的方法;
  7. 把光标放到 line 0,按 RET,代码中已插入所选的所有方法,完成。

OK,现在我想问的是:

  • 移动光标到候选,按 C-m 后,光标可以不回 line 1 吗?我用 C-M-p 或 C-M-n 是没用的,它的行为就像普通的 C-p 或 C-n。

  • 在这个过程中,比如我只想插入 2 个方法,用上面所说的操作我就要重复 16 次,把 16 个不想要的方法取消选择,那如果继承链再长点、方法再多点,岂不要搞死人!ivy 有没有一种操作方法可以先全部取消选择,然后我再选上我要的?

BTW. 我试了试 ivy-occur,好像对这个问题无能为力。(也可能是我不会玩)

P.S. 我后来发现可以用鼠标点击各个候选项,或者按 C-’ 调用 ivy-avy 后按字母来选,方便不少,但还是不知如何全选或全不选。


ivy相对于Helm的优点?
#2

本来以为, 这只是 emacs 的小问题,这也没什么,毕竟 emacs 不是 IDE,本也不适合来搞 Java 这一类的。

没成想就这个需求再试了试 VS Code 和 IDEA(我虽然这两货都装好的,但基本不用),发觉大家居然都差不多,总结一下就是:

  • 用鼠标,三者都能用,IDEA 更好一点,emacs 如果不能全选、全不选,那就最麻烦;
  • 用键盘,emacs 能完成但麻烦,而另两个搞不定(这里说的搞不定是说我不能搞定)。

操作记录如下:

VS Code

和 emacs 中很像,也正常,两个都是用的 lsp。

  1. 按 command-shift-p 打开 command palette;
  2. 键入 source action,回车;
  3. 在弹出的关联菜单中选 Override/Implement Methods…(这里好像有 Bug,C-n 或 C-p 只能按一次就失去作用了,你只能用鼠标或是方向键。);
  4. 弹出候选列表,默认全部未选中,能全选、全不选以及过滤,这里 C-n、C-p 能移动光标,但不知怎样选中,只能用鼠标;
  5. 选完后点 OK,完成。

15%20AM

IDEA

  1. 按 shift x 2,打开 Search Everywhere;
  2. 键入 Override Methods, 回车;
  3. 弹出候选列表窗口,默认全部未选中,可排序,可按类分组,command-a 可全选 ,直接敲字符可高亮过滤,C-n、C-p 可移动光标,同样不知按什么键可选中,只能用鼠标 command + click 多选;
  4. 选完点 OK 或直接回车,完成。

其实,我只是想知道 emacs 怎样用键盘全选(或取消全选)候选项,我相信这能做到,只是不知 emacs/spacemacs 是否已经绑定好,还是要自己写一些 elisp 来实现?


#3

https://emacs-china.org/t/ivy-helm

这个帖子里有许多知道ivy的


#4

我没有试过LSP Java所以只是猜想一下。
进入ivy-occur之后,按 w 进入编辑模式,然后用多重光标在每一行加上光标,然后回车(如果是回车选定的话,如果是别的你就按对应的)


#5

我觉得这是一个 bug。。


#6

我觉得应该这功能应该是lsp反复调用completing-read实现的,和ivy helm ido无关


#7

多谢楼上各位的热心解答,我也觉得这个应该和 ivy 无关,进入 ivy-occur 后,任选一行按 RET 调用 default action 后,它什么也不做,只是当前光标跳回编辑文件中去而已。

我再去啃啃 lsp-java 的文档。


#8

看来这个问题有点难喔

因为这个涉及到了 lsp-mode 对他了解的人应该不多 了解lsp-java的人应该更少

在lsp-java中关键的代码是


(defun lsp-java--completing-read-multiple (message items initial-selection)
  (let ((deps initial-selection) dep)
    (while (setq dep (cl-rest (lsp--completing-read
                               (if deps
                                   (format "%s (selected %s): " message (length\
 deps))
                                 (concat message ": "))
                               items
                               (-lambda ((name . id))
                                 (if (-contains? deps id)
                                     (concat name " ✓")
                                   name)))))
      (if (-contains? deps dep)
          (setq deps (remove dep deps))
        (cl-pushnew dep deps)))
    deps))

这个代码中有 你图片上的 “✓”

他会传一个 initial-selection 后面又用了一个while

所以他和ivy没直接的关系 而是和 lsp–completing-read 有直接的关系

如果 lsp–completing-read 可以接受一个参数作为起始选择地址的话

你就不用看 lsp–completing-read 的代码了 如果他没有 你可以提 issue 的


#9

(defun lsp--completing-read (prompt collection transform-fn &optional predicate
                                    require-match initial-input
                                    hist def inherit-input-method)
  "Wrap `completing-read' to provide tranformation function.

TRANSFORM-FN will be used to transform each of the items before displaying.

PROMPT COLLECTION PREDICATE REQUIRE-MATCH INITIAL-INPUT HIST DEF
INHERIT-INPUT-METHOD will be proxied to `completing-read' without changes."
  (let* ((result (--map (cons (funcall transform-fn it) it) collection))
         (completion (completing-read prompt (-map 'cl-first result)
                                      predicate require-match initial-input hist
                                      def inherit-input-method)))
    (cdr (assoc completion result))))

他只是简单的封装了一下complete-read而已

而ivy好像是改变了complete-read的执行方式

所以 你只需要测试complete-read 让他按照你的方式执行就可以了

当然 你还得hack一下 lsp-java的代码

其实更简单的方式是提 issue 他们解决这个问题应该很简单


#10

非常感谢,issue 看了不少,还从未提过,不知自己的英文能否表达清楚这个问题,我试试吧。:sweat_smile:


#12

ivy-read里有一个参数叫 PRESELECT

他好像可以指定位置的

这样就不会每次都出现在第一个的位置了


#13

他还有个参数是MULTI-ACTION

好像是能够实现多选

这个方法也可以 就省去了while循环

但我没有找到ivy-mark的快捷键

原来它在ivy-hydra里


#14

看来lsp偷懒了,没对ivy和helm分别适配


#15

I am author of that code - the proper solution is to create a ivy/helm wrappers which support multiple selection. There is completing-read-multiple in emacs but IMO it is even more inconvenient to use so I decided to do that wrapper over completing-read which for that particular usecase is very hard to use. Feel free to file a lsp-java PR so we could work on improving that piece of code. I will implement the helm wrapper at lsp-helm project since I am primary a helm user. I guess someone could port the method to ivy as well.

Edit: it seems like helm/ivy do have implementation of completing-read-multiple, I will try it first.


#16

很高兴作者回复了,非常感谢,我就不折腾了,期待更新。:star_struck:

我现在只是改了下显示方式,加了一个默认完成(DONE)选项,每次光标都会跳到这里,并且把 ✓ 放到了前面,这下选项多也不眼花了。

(defun lsp-java--completing-read-multiple (message items initial-selection)
  (let ((deps initial-selection) dep)
    (while (setq dep (cl-rest (lsp--completing-read
                               (if deps
                                   (format "%s (selected %s): " message (length deps))
                                 (concat message ": "))
                               items
                               (-lambda ((name . id))
                                 (if (-contains? deps id)
                                     (concat "✓|" name)
                                   (concat " |" name)))
                               nil nil nil nil "+|-------[DONE]-------|+" nil)))
      (if (-contains? deps dep)
          (setq deps (remove dep deps))
        (cl-pushnew dep deps)))
    deps))


#17

I thought about that but it does not work on helm since when you have initial selection in it hides the rest of the items… You may try to delete this function and use completing-read-multiple which ivy actually overrides. But still it does not allow default selection…


#18

Is it possible to use ivy-read and helm directly? We can make lsp--completing-read a generics, and dispatch it depends on caller and backends


#19

I try this on helm, it seems like it’s work normally. Furthermore, helm has a bit of benefit than ivy, the order of the candidates doesn’t have change when you selected.


#20

That is strange, on my machine the following hides the rest of the options:

(completing-read "input: " '("a" "b" "c") nil nil "a")

@Cireu lsp--completing-read is already doing that under the hood(e. g. when helm mode is enabled it replaces the completing-read function). I guess you are talking about lsp-java--completing-read-multiple? I am not 100% sure whether I understand what you mean by generic but something simple like this:

(eval-after-load helm-mode
   (defun lsp-java--helm-completing-read-multiple  ...)
  ...)

and then:

(defun lsp-java--completing-read-multiple (...)
   (if helm-mode 
    ;; use helm function
    ....

Another approach is to use directly completing-read-multiple and delete lsp-java--completing-read-multiple.

Also, we could provide an extension point like

(defvar lsp-completing-read-multiple-function #'completing-read-multiple)

and let users customize it.

I would appreciate a feedback on each of the proposed approaches and eventually a PR. I want to keep the development as open as possible.


#21

我 很 欣 慰