armindarvish / consult-omni

A Powerful Versatile Omni Search inside Emacs
https://github.com/armindarvish/consult-omni/tree/main
165 stars 4 forks source link

Actions on new item for multiple sources #33

Closed WeissP closed 3 weeks ago

WeissP commented 3 weeks ago

First thanks for creating such a COOL package, I really like it!

Is your feature request related to a problem? Please describe. If I run a single consult-omni source, the on-new action can be executed perfectly. However, no on-new actions will be executed if they come from multiple sources, because the new item will be forwarded to consult-omni-default-new-function.

For me personally, this doesn't help in most cases.

Describe the solutions you'd like

  1. When I did similar things in plain consult, I normally first narrow down to a single source, and then the corresponding on-new action can be executed. However, it is actually not that intuitive, as it is always easy to forget to first narrow the sources and then press return.
  2. Add a new function to consult-omni-default-new-function, which will prompt you to choose a source and execute the on-new action of that source. I personally like this idea the most.
  3. Add or change macros for creating a new source so that actions on new items are themselves items. For example, if I want to create a new note and run (consult-omni-notes-search "emacs"), it will show:
    - how to use foo in emacs
    - how to use bar in emacs
    - ...
    - create new note: emacs

where the last item is not a note but an action to create a new note named "emacs".

armindarvish commented 3 weeks ago

@WeissP your number 3 above is already the case. When there are other candidates found like your example above, then you can put the cursor on the minibuffer input itself (and not any candidates) and hit enter (or maybe shift+enter depending on your vertico or other completion setup) then the input string is taken as a new item, and it is passed to the :on-new function. You can even do this before any potential candidates are found. I show this in the screenshot below. The first time I wait for candidates to get populated, but the second time I don't wait. I just hit enter right away and that selects the input and uses it as a new item:

consult-omni-multisource-new

armindarvish commented 3 weeks ago

@WeissP For the rest of your feature request here is my response:
I thought about what you are asking before, but in most cases, it really does not make sense to try to guess what the user wants when they type in a non-existing candidate in a multi-source search. This is because in this case consult--read only returns the string that the user types and no metadata. So we have to do extra work to guess what the user meant. Is this a new candidate for a new note or a new candidate for a goolge search, etc.? This is somewhat what the current implementation does, but instead of using the sources in the search we define all the possible options of what to do with the new candidate in consult-omni-default-new-function (see number 1 below for explanation).

Now for your work flow, I do not understand why you want to start with a multi-source search and then narrow down to just one. Why don't you simply call the interactive command for that one source if you know you want to narrow down to it anyway?

Depending on the answer you have different options.

If you are simply trying to streamline your workflow and always start with the same command, then you can:

  1. You can also customize the consult-omni-default-new-function. It is a variable that by default is bound to #'consult-omni-external-search function. That function, in turn, presents to the user options to pick from. These options are stored in the alist consult-omni–search-engine-alist. So you can add more options for other functions you need. For example, if you want to add a new note making command, then you can simply do:
(add-to-list 'consult-omni--search-engine-alist '("new note" . #'consult-omni--notes-new))

Note that the first element above is the name you get presented with and the second element is the function that the candidate will get passed to (in this case the candidate would be the string you type in the minibuffer for the new candidate).

  1. Beyond that you can also hack your own solution by defining a new function and binding consult-omni-default-new-function to it. If you want you can then figure out the state of consult narrow down and find the relevant sources and get their :on-new functions from consult-omni-sources-alist by calling consult-omni--get-source-prop with the name of the source and :on-new as prop. Then query the user to pick from one of the sources and pass your candidate to the :on-new function for that source. I won't implement this as a general solution in the package because I think the current solution with number 1 above is more meaningful and more flexible and customizable. For example it can be that the user starts a search and does some narrow down, but still decides to run that as a new search on google at the end. So instead of forcing the choices of what to do with the new item based on narrowed state, we just use a function that can do anything the user wishes. Of course in this case the user has to customize things to get the exact behaior they want, but one line of elisp her and there is not too bad for the kinf of users that choose to use consult-omni.

  2. If there is some reason number 1 and 2 are not good enough, you always have the option of embark-become. You can start with a multi-source search and narrow down and when you are done run embark-become and change it to the single-source command.

WeissP commented 3 weeks ago

Thanks for the detailed response!

I do not understand why you want to start with a multi-source search and then narrow down to just one. Why don’t you simply call the interactive command for that one source if you know you want to narrow down to it anyway?

I may have a slightly different usage. I treat this great package as a way to combine synchronous and asynchronous consult sources (but not a web search engine). Right now I have placed all common sources together as my most frequent search backend. With the same idea, I also don't want to remember different shortcut keys to create new items for different sources. And as I described above, I also don't like the idea of narrowing down to a single source :)

Here is what I did for the second idea.

(defun consult-omni--choose-new (cand &rest args)
    "Create a new item based on the chosen source."
    (interactive)
    (let* ((source (completing-read
                    "Create a new item on source: "
                    consult-omni-multi-sources))
           (action (consult-omni--get-source-prop source :on-new)))
      (funcall action cand)))
(setq consult-omni-default-new-function #'consult-omni--choose-new)

I created this issue because I thought it was a common requirement, but based on what you replied, maybe it’s not. However, anyone who happens to have a similar usage can just copy and paste my code above :)

armindarvish commented 3 weeks ago

I may have a slightly different usage. I treat this great package as a way to combine synchronous and asynchronous consult sources (but not a web search engine). Right now I have placed all common sources together as my most frequent search backend. With the same idea, I also don't want to remember different shortcut keys to create new items for different sources. And as I described above, I also don't like the idea of narrowing down to a single source :)

That makes sense. I think for the example funciton above, one should add some checks to see which sources have :on-new and only use them in comspleting-read and perhaps also check if the action is a function e.g. (if (funcitonp action) ...) just to make a it a bit more robust and uinversally applicable.

Another useful suggestion would be to add the function above under "Other" category to the search engine alist for people who want to keep both the web engines as options for new items. So it would look like this:

(defun consult-omni--choose-new (cand)
    "Create a new item based on the chosen source."
    (interactive)
    (let* ((sources (cl-remove-duplicates (delq nil (mapcar (lambda (item)
                              (when-let ((new (consult-omni--get-source-prop item :on-new))
                                         (name (consult-omni--get-source-prop item :name)))
                                (when (not (eq new #'consult-omni--default-new)) 
                                  (cons name new)))) 
                            consult-omni-multi-sources))))
           (action (consult--read sources
                                  :prompt "Create a new item on source: "
                                  :lookup #'consult--lookup-cdr
                                  )))
      (if (functionp action) 
          (funcall action cand)
        (error "Do not know how to make a new item for that source!"))))

(add-to-list 'consult-omni--search-engine-alist '("Other" . #'consult-omni--choose-new))

I think, it might be a good idea to add this to the default value for consult-omni--search-engine-alist. I am going to do that in the develop now perhaps with some better naming convention.

WeissP commented 3 weeks ago

I am going to do that in the develop now perhaps with some better naming convention.

Really looking forward to it!