radian-software / selectrum

🔔 Better solution for incremental narrowing in Emacs.
MIT License
737 stars 33 forks source link

ivy-occur equivalent #15

Closed noctuid closed 3 years ago

noctuid commented 4 years ago

One of my favorite features of ivy is ivy-occur. The integration with ibuffer for ivy-switch-buffer, with grep-mode for swiper, and with dired for counsel-find-file and similar commands is especially nice. I wouldn't want to try to do file management in a minibuffer with a bunch of keybindings (like helm), but it's nice to be able to populate a dired or grep buffer with results and then do whatever in it.

raxod502 commented 4 years ago

For the sake of other readers, the requested functionality is to take the candidates from the minibuffer and put them into a real buffer, with a keymap that allows you to select one of them. That seems like a reasonable feature because it will not require any particularly bad contortion of the abstractions. Thanks for making me aware of this feature; I had not seen it before.

dylanjm commented 4 years ago

+1 for this feature. Very useful for finding all of the faces a package provides.

mpenet commented 4 years ago

+1 That would be the big missing piece for me as well. It's the only reason I am still using ivy instead of selectrum.

b3n commented 4 years ago

+1 This is why I still use Ivy. My use case is counsel-rg, where I do a search for a term with ripgrep, turn it into an ivy-occur buffer, and then use wgrep to make it an editable buffer and make changes across files.

raxod502 commented 4 years ago

@b3n out of curiosity, is rg.el a suitable replacement for counsel-rg + ivy-occur?

b3n commented 4 years ago

@raxod502 I just tried out rg.el and it does indeed meet my needs, thank you for pointing me to it! The only downside is I can't get search results in the minibuffer as I type, but I can live with that.

mpenet commented 4 years ago

@raxod502 I don't think rg.el shows results as you type, counsel-rg does. rg is so fast that it is nearly instantaneous. It makes it usable for project navigation for instance.

raxod502 commented 4 years ago

I looked at this and I do not think it is a good idea to try to implement the whole thing like Ivy does it. That is because it breaks the completing-read abstraction badly. Ivy relies on knowing what action to take with the selected candidate, but Selectrum does not know this because it only implements completing-read.

That said, I'm not opposed to adding something more moderate to Selectrum which will allow commands like ivy-occur for specific use cases to be implemented. Here is what I am thinking. We add a new keyword argument to selectrum-read that allows you to get the list of currently displayed candidates when the user exits the minibuffer (whereas normally the return value is just the selected candidate). Then, to implement a replacement for (as an example) ivy-occur + Ibuffer, you simply make a command that uses selectrum-read to read a buffer, but with the new keyword argument -- and then feed the resulting list to Ibuffer. What do you all think? Would that be workable? I'm open to other ideas.

I think it might be desirable to make this feature controlled by a dynamically bound variable rather than a keyword argument, since that would allow you to use selectrum-read-buffer, selectrum-read-file-name, etc. in these ivy-occur replacements without modification or hacks.

The code is at https://github.com/raxod502/selectrum/pull/56.

clemera commented 4 years ago

The following might be funny given that I also added suggestions a while back to implement features which deter from the default API but I'm no more in favour of adding such features now. This will lead to a state where selectrum more and more can't just be used a a replacement for completing-read, people will implement all kinds of things which use such extra features and all of the sudden you have new selectrum-this, selectrum-that packages.

Not that there would be something wrong with that but I would wish we would have more packages which just use the default API and don't require a specific completion framework. For some things which need features not compatible with the default API it might make sense to use such a framework but for those we already have helm or counsel.

Personally I would prefer to focus on providing a better completing-read interface and afterwards think about how to implement extensions on top of the default API which are not framework dependent.

mpenet commented 4 years ago

I looked at this and I do not think it is a good idea to try to implement the whole thing like Ivy does it. That is because it breaks the completing-read abstraction badly. Ivy relies on knowing what action to take with the selected candidate, but Selectrum does not know this because it only implements completing-read.

That said, I'm not opposed to adding something more moderate to Selectrum which will allow commands like ivy-occur for specific use cases to be implemented. Here is what I am thinking. We add a new keyword argument to selectrum-read that allows you to get the list of currently displayed candidates when the user exits the minibuffer (whereas normally the return value is just the selected candidate). Then, to implement a replacement for (as an example) ivy-occur + Ibuffer, you simply make a command that uses selectrum-read to read a buffer, but with the new keyword argument -- and then feed the resulting list to Ibuffer. What do you all think? Would that be workable? I'm open to other ideas.

I think it might be desirable to make this feature controlled by a dynamically bound variable rather than a keyword argument, since that would allow you to use selectrum-read-buffer, selectrum-read-file-name, etc. in these ivy-occur replacements without modification or hacks.

The code is at #56.

That sounds like a decent solution, I am liking the direction of keeping selectrum lightweight as well and from what I understood a "selectrum-occur" can happen separately with this.

AmaiKinono commented 4 years ago

Here's an idea: I think a main advantage of ivy-occur is we can call the command first without thinking, and then choose to select a candidate or call ivy-occur based on the situation. As far as I can tell, #56 doesn't give us the mechanism to do this, so I would say it's not much useful. To me, pop up the Selectrum UI to only get a list of candidates from it seems weird.

If we seriously want a mechanism to implement similar feature, I think a new action must be added. When calling it, the list of candidates is returned like in #56. The command build on top of selectrum-read could do different things based on whether it receives a list or not from selectrum-read. If we do this, we need an argument in selectrum-read to explicitely enable this "occur" action, so it's blocked by default (and in selectrum-completing-read).

raxod502 commented 4 years ago

@AmaiKinono If, as you suggest, selectrum-read grows an argument to enable the "occur" action, then how is that different from the :return-candidates argument? In either case, the caller must explicitly declare that they want a list rather than just the selected candidate.

I agree that it is nice to be able to start the session first and then later decide whether to use the occur-like feature, but I am not sure it's possible to implement this without violating the design philosophy of completing-read rather badly.

AmaiKinono commented 4 years ago

how is that different from the :return-candidates argument? In either case, the caller must explicitly declare that they want a list rather than just the selected candidate.

With the :return-candidates argument, the caller declares it want a list. But with an enable-occur-action argument, the caller declares it can deal with a string (given by the selectrum-select-current-candidate or selectrum-submit-exact-input action), or a list (given by the suggested "occur" action).

raxod502 commented 4 years ago

With multiple selection (https://github.com/raxod502/selectrum/commit/5f59af278b3b7010c56e222c2196b24b1a56675d), there is a more elegant way: the caller invokes completing-read-multiple, and then we can have a keybinding ("the ivy-occur keybinding") which selects all the candidates before exiting the minibuffer. Then the caller decides what to do based on whether more than one candidate was returned in the list. What do you think of that? The only disadvantage I see is that you can't do an ivy-occur action on only one candidate -- but I have a hard time seeing many use cases for that.

clemera commented 4 years ago

the caller invokes completing-read-multiple, and then we can have a keybinding

I think that sounds great! Instead of a key binding we could also accept * as the input for meaning all candidates. This would mean one can not select a single element with completing-read-multiplewhich is just * easily but this shouldn't be relevant to often.

When * is returned the caller has to handle it, and decide to continue with all elements. This way we would force commands using this feature to be implemented in a way which work with or without selectrum. Another benefit I see is that the the choice in this case is not "one or all" but "one, multiple or all".

clemera commented 4 years ago

Oh I just noticed I missed the case of returning all matched candidates of current query. I don't have an idea how to implement this in a way that is independent from selectrum. Actually the comment I initially replied to seems to have already provided a solution for that.

raxod502 commented 4 years ago

Yeah, I think the thing people are mostly excited about is doing an occur operation on the list of currently matched candidates.

clemera commented 4 years ago

Yep, I missed that. Without such a concept in the default API this seems to be hard to achieve in a compliant way.

See comment below Maybe another way we could achieve that would be by using a wrapper which always rebinds commands in crm-local-completion-map and crm-local-must-match-map before calling completing-read-multiple. This rebound commands could then add all currently matching candidates to the input and submit them. When the commands using completing-read-multiple are written using this wrapper they would also work without selectrum.

Edit: Actually I was kind of confused, the wrapper is also a dependency so this does not help really. Given that it is probably not possible to do this ins a standard API compliant way I would say it's ok to add a general command that works for any completion session. Forcing people to explicitly use completing-read-multiple isn't nice. Maybe one day this option gets added to default API of completing-read and than we can easily switch to it. BTW what about making selectrum--read a private function so usage of any selectrum specific features is discouraged outside of selectrum?

clemera commented 4 years ago

I had another idea: We could avoid such an API altogether and provide selectrum commands like:

(push (cons [remap dired] 'selectrum-dired)
      selectrum-minibuffer-bindings)

(defun selectrum-dired ()
  (interactive)
  (let ((cands ()))
    (dolist (c (cl-delete-if-not
                #'file-exists-p
                (mapcar #'selectrum--get-full
                        selectrum--refined-candidates)))
      (push (if (file-directory-p c)
                (directory-file-name c)
              c)
            cands))
    (run-at-time
     0 nil
     (lambda ()
       (dired (cons "*selectrum-dired*" (nreverse cands)))
       (goto-char (point-min))))
    (abort-recursive-edit)))

This way there is no need for the caller to write a command a certain way, or to explicitly use completing-read-multiple. We also don't need to do something like ivy which knows what action to use for the candidates, the user knows better ;). It would just work with any completion commands which contain file candidates. Similar commands could be written to build an ibuffer buffer and maybe others. There is no swiper equivalent but from isearch you can call occur, too.

raxod502 commented 4 years ago

Huh. I guess using an idle timer together with abort-recursive-edit does in fact allow you to implement alternate actions without breaking the completing-read API. Very clever, I had not thought of that.

If it were done this way, I don't think I would mind alternate action support being added to Selectrum, although I think I would prefer to see alternate actions that are global across all commands (and available under special keybindings) rather than ones that are defined by the caller to selectrum-read. That way we don't fall into the trap of needing to write a wrapper for every possible Emacs function that uses completing-read.

The same approach allows not only alternate actions, but also occur-like actions, as you observed. I think this is great!

clemera commented 4 years ago

If it were done this way, I don't think I would mind alternate action support being added to Selectrum, although I think I would prefer to see alternate actions that are global across all commands (and available under special keybindings)

That would be great! Maybe we could use a prefix key which would show a menu for available categories: file-actions, buffer-actions and so on. After selecting one of them with a key another one shows up with the actual actions. For some candidates like files or buffers we could actually auto detect them with file-exists-p or get-buffer but one problem is candidate names might exists in multiple contexts.

Edit: For files we could also just check for minibuffer-completing-file-name and for buffers we could let bind a similar variable in selectrum-read-buffer so we know what type of candidates we have for these cases.

raxod502 commented 4 years ago

For some candidates like files or buffers we could actually auto detect them

As long as the auto-detection is customizable -- it needs to be clear that such functionality will necessarily make some decisions arbitrarily which may not always be correct, and therefore can be tweaked if needed by the user.

clemera commented 4 years ago

We can also detect the type of some collections by querying the metadata category. We also would need the metadata for adding support for display-sort-function and maybe others as discussed in #82.

clemera commented 3 years ago

You can get ivy-occur like functionality using embark, the corresponding command is called embark-export. In addition to that you get minibuffer actions on candidates (like ivy-actions) and some other features which come with embark (see the project readme/wiki for details). See Selectrums wiki for setup instructions.

minad commented 3 years ago

Can be closed? Embark is pretty solid now :)

raxod502 commented 3 years ago

This thread is being closed automatically by Tidier because it is labeled with "waiting on response" and has not seen any activity for 90 days. But don't worry—if you have any information that might advance the discussion, leave a comment and I will be happy to reopen the thread :)