swiper icon indicating copy to clipboard operation
swiper copied to clipboard

Feature idea: Command swapping

Open justbur opened this issue 9 years ago • 22 comments

This is possibly crazy/stupid, but here are two situations that I'm thinking of

  1. ivy-switch-buffer -> search for file buffer -> "damn the file's not open" -> C-g -> ivy-find-file -> Done
  2. swiper -> search for symbol in project -> "damn wrong file" -> C-g -> counsel-ag (or whatever) -> Done

I think it would be cool if you could somehow broaden the context (or narrow it) without leaving ivy. Helm sort of has this, because it has multiple sources and you can jump between sources, but I was thinking that instead of implementing that, which I think has drawbacks, you could implement a simple way of swapping commands while preserving some state (like the minibuffer input). So maybe there's a meta command for files that has keys like, C-1 for buffers C-2 for local files C-3 for remote files. I don't know.

Another thought, but probably more complicated would be if you could dynamically add candidates from more places. So search for buffer, buffer is not there, dump file names in there too dynamically.

justbur avatar Jul 29 '16 15:07 justbur

It's a nice idea. I've had the same in mind, e.g. ivy-widen going from counsel-M-x to counsel-describe-function to a union of describe-function and describe-var. And in the other direction with ivy-narrow.

It only remains to implement it in a sane and extensible way. If only to find the time:). I've already spent today's allotment on a couple of bug fixes and the new swiper-all (which is totally awesome now, btw).

abo-abo avatar Jul 29 '16 16:07 abo-abo

Help functions are a good example, all the way up to apropos

You could have a tree, where walking down the tree is more specific and walking up is more general. Then ivy-widen would preserve input and execute the prior command in the list. Then people could define their own trees like

(setq counsel-command-tree
      '(nil
        (apropos
         (describe-function counsel-M-x)
         describe-variable)
        (counsel-find-file ivy-switch-buffer)))

justbur avatar Jul 29 '16 16:07 justbur

@abo-abo Just have tried swiper-all --- and it doens't work at the moment.

Error in post-command-hook (ivy--exhibit): (wrong-type-argument characterp ("s" . t))

habamax avatar Jul 30 '16 07:07 habamax

@habamax Please open a new issue, with more details.

abo-abo avatar Jul 30 '16 07:07 abo-abo

well, if there was exposed in documentation swiper-all function, I would definetely opened new issue.

But I have just found out from this thread that threre is awesome function implemented, tried it, it didn't work -- I let you know.

If you want to fix it -- okay, if not -- okay too.

There are no more details, the error is the same as in ivy-switch-buffer. But the workaround for buffers doesn't work for swiper-all.

habamax avatar Jul 30 '16 10:07 habamax

Just passing by to say this would be an awesome add to Ivy.

manuel-uberti avatar Aug 13 '16 07:08 manuel-uberti

I implemented some sort of swapping similar to the example that @justbur mentioned by doing:

(define-key counsel-find-file-map (kbd "C-b")
    (lambda ()
      (interactive)
      (ivy-quit-and-run (ivy-switch-buffer)))))

(define-key ivy-switch-buffer-map (kbd "C-f")
    (lambda ()
      (interactive)
      (ivy-quit-and-run (counsel-find-file))))

erreina avatar Aug 30 '16 14:08 erreina

I like switching to locate. I've just been using this for a while (requires lexical binding):

(defun my-ivy-switch-to-locate ()
  "Switch to using locate, preserving current input."
  (interactive)
  (let ((input (ivy--input)))
    (ivy-quit-and-run (counsel-locate input))))

noctuid avatar Nov 23 '17 18:11 noctuid

@noctuid Can you make a PR adding ivy-set-actions using your function?

abo-abo avatar Nov 24 '17 16:11 abo-abo

Isn't the idea of hot-swapping commands the same as Ivy actions? The only difference is that actions act on the current candidate, whereas command hot-swapping would act on the current input.

Unless I'm mistaken, this would allow piggy-backing the action machinery. Perhaps a prefix argument before M-o could be interpreted as a hot-swap dispatch instead of action dispatch. This would also preserve valuable key binding real-estate.

This implementation idea is slightly less elegant than the widen/narrow idiom (mostly because of more keybindings, though), and the current action machinery might need a mighty face-lift before piggy-backing becomes as easy as I'm suggesting, but the 3D action machinery already exists and has the potential to be much more flexible and customisable than 2D widen/narrow.

Edit: When I say 2D/3D I actually mean linear/exponential, the idea being that widen/narrow can only traverse commands along a single axis, whereas actions can connect any one command with any other command, potentially forming a complete graph in the graph theory sense.

basil-conto avatar Feb 10 '18 15:02 basil-conto

@noctuid

requires lexical binding

Just to be clear, your function does not in and of itself require lexical-binding. It is probably the ivy/counsel functions you are calling which depend on lexical-binding.

basil-conto avatar Feb 10 '18 15:02 basil-conto

Just to be clear, your function does not in and of itself require lexical-binding. It is probably the ivy/counsel functions you are calling which depend on lexical-binding.

I don't quite understand your meaning, but that function itself does require lexical binding. I only mentioned this because the function will not work if normally evaluated (e.g. in the scratch buffer). The issue is not that ivy-quit-and-run is used; the issue is that there is a let around it. The body for ivy-quit-and-run ends up in a lambda, so lexical binding is required for input to be bound correctly.

noctuid avatar Feb 11 '18 01:02 noctuid

Warning: The following is 100% off-topic and may contain traces of pedantry.

@noctuid

I don't quite understand your meaning [...]

What I am saying is that none of the Elisp language features (defun, let, etc.) used in my-ivy-switch-to-locate depend on lexical-binding. Whether the local variable input is bound dynamically or lexically would normally make no difference.

[...] but that function itself does require lexical binding. I only mentioned this because the function will not work if normally evaluated [...]

The reason the type of scoping of input does make a difference here is the fact that the macro ivy-quit-and-run depends on lexical-binding. The macro could well have been written to support both types of scoping (that run-at-time doesn't look dubious at all!), and then your function could be correctly evaluated even in Emacs 23 (which lacks lexical scoping) without changing a single word in it. This is what I mean when I say your function does not, in and of itself, depend on lexical features; rather it is calling macros/functions which are written in lexical Elisp (which is a different programming language altogether, despite appearances).

[...] (e.g. in the scratch buffer).

As a side note, you can enable lexical-binding in the *scratch* buffer like any other buffer. I always do this in my configuration, and there was recently some discussion on emacs-devel about enabling this by default in a future release.

The issue is not that ivy-quit-and-run is used; the issue is that there is a let around it.

The issue is not that there is a let around it, or that ivy-quit-and-run is used, but rather that ivy-quit-and-run depends on lexical scoping by placing the given body in a lambda. This is a leaky implementation detail which could be avoided by ivy if there was reason to.

The body for ivy-quit-and-run ends up in a lambda so lexical binding is required for input to be bound correctly.

Indeed, but that is a requirement/limitation of ivy, not your function, as I describe above.


P.S. Sorry for bringing this up; there really is little anyone could gain, even in the case of consensus on the definition of separation of concerns which I present above.

basil-conto avatar Feb 11 '18 02:02 basil-conto

Isn't the idea of hot-swapping commands the same as Ivy actions? The only difference is that actions act on the current candidate, whereas command hot-swapping would act on the current input.

This is worthwhile to investigate as well. But I think the value of the narrow/widen pattern is the options are tightly coupled to the command at hand, grouping related information together.

abo-abo avatar Feb 11 '18 18:02 abo-abo

The issue is not that there is a let around it, or that ivy-quit-and-run is used, but rather that ivy-quit-and-run depends on lexical scoping by placing the given body in a lambda.

Indeed, but that is a requirement/limitation of ivy, not your function, as I describe above.

The issue is exactly that there is a let around the lambda. Lexical scoping is required for this to be a closure. It is as simple as that.

Lexical scoping would not be necessary if there were no bindings around the lambda. ivy-quit-and-run can work fine in cases without lexical scoping. This is not an issue of ivy-quit-and-run inherently requiring lexical scoping. The only reason my function doesn't work without lexical scoping is because it uses let to make bindings around a lambda.

noctuid avatar Feb 11 '18 18:02 noctuid

@noctuid

The issue is exactly that there is a let around the lambda. Lexical scoping is required for this to be a closure. It is as simple as that. Lexical scoping would not be necessary if there were no bindings around the lambda. ivy-quit-and-run can work fine in cases without lexical scoping. This is not an issue of ivy-quit-and-run inherently requiring lexical scoping. The only reason my function doesn't work without lexical scoping is because it uses let to make bindings around a lambda.

Yes, this is what I said in my last reply. My (pointless) point is that, the fact that ivy-quit-and-run puts its arguments into a lambda, thus requiring lexical scope, is an implementation detail of ivy-quit-and-run. For example, a patch for ivy.el could theoretically land tomorrow which removed the lambda (this is actually something I want to look into, because that run-at-time looks like a bit of a kludge). In this case, you wouldn't have had to change a single word and yet my-ivy-switch-to-locate would suddenly work irrespective of the scoping used to evaluate it.

I understand if you don't agree with this way of looking at things from a separation of concerns standpoint; but my (pointless) point still stands from a technical standpoint. Anyway, sorry again for inciting this off-topic discussion.

basil-conto avatar Feb 12 '18 13:02 basil-conto

@abo-abo

Isn't the idea of hot-swapping commands the same as Ivy actions? The only difference is that actions act on the current candidate, whereas command hot-swapping would act on the current input.

This is worthwhile to investigate as well. But I think the value of the narrow/widen pattern is the options are tightly coupled to the command at hand, grouping related information together.

Indeed, this is why I think it's slightly more elegant. But my guess is that the number of such groups of related functionality might turn out to be a bit limited. Perhaps the ideal implementation could combine the best of both worlds.

basil-conto avatar Feb 12 '18 13:02 basil-conto

because that run-at-time looks like a bit of a kludge

While I agree, the Emacs command loop and recursive minibuffer quitting is a mystery/maze to me. I'm just happy ivy-quit-and-run works at all, and with a small amount to code to boot. Improvements welcome, of course.

See also this related post: https://oremacs.com/2015/07/16/callback-quit/.

abo-abo avatar Feb 12 '18 19:02 abo-abo

Here is a reddit discussion about a related feature request: the poster suggests interactive scope change via a simple query language like any search engine provides:

Problem: There are too many helm-* or counsel/ivy-* functions.

Firstly, I don't want to manage, create, and remember many bindings. Secondly, its an effort on a brain(I need to do X, oh I need to call func counsel-X, what's the binding? Ah heck I'll just execute-extended-command it).

Ideally, I want one function and one simple button that does the following: By default, show me open buffers and files in default-directory. If I type src/ switch to find-file like functionality. If I type b:feature/make-it-work switch to a branch. If I type m: start a Maildir search with counsel-mu4e. Maybe if I type code: it does counsel-ag.

So essentially a simple query-like language to search stuff inside emacs that I get to define.

I do find this idea very interesting and maybe it contributes something to this issue here.

novoid avatar Mar 01 '18 09:03 novoid

Maybe something like this could be useful, but those examples seem to be worse than the current method for doing things. Running the hypothetical counsel-X followed b: or m: requires just as much memorization as would be required for a keybinding but requires extra keypresses. Typing code: doesn't seem much better than just using counsel-M-x (and maybe aliases, but counsel-ag is a lot more descriptive than just code and easily matched). I don't really feel like a middleground between execute-extended-command/counsel-M-x and keybindings is needed. Maybe a counsel-counsel for only ivy/counsel commands.

noctuid avatar Mar 01 '18 20:03 noctuid

@noctuid I can follow your arguments. In the end, it's a trade-off between "using a separate keybinding for each command" which takes a few to many keybindings I have to memorize and "using one keybinding plus a mnemonic query language".

In my opinion, there are good arguments for both. The one should not replace the other. With this query language, commands could be solved via tab-completion and mnemonic enhanced commands are easier to remember IMO.

In my world, I tend to use keybindings for things I use on a daily basis and stick to M-x foo for the other things.

And "search" is quite often enhanced via those query keywords separated by colons. This is a common pattern which would be a dramatic improvement for people who are not using all those counsel-foo commands with separate keybindings.

YMMV of course.

novoid avatar Mar 02 '18 08:03 novoid

I really like this idea, and I've got this.

I think this is quite a good solution to the basics. I wanted the implementation to satisfy all the ideas in the original issue thread, so:

  • Functions to support switching to any other ivy function.
  • Keybindings for quickly switching between common commands. (I've only added a few because this is really an RFC at this point, but it should be clear how more could be included)
  • Personally, I also think the mechanism should be as flexible as possible, allowing switching to any command (or at least many commands). Hence ivy-become-any, which takes a key sequence like describe-key does, and switches to the corresponding command (it assumes that the command is appropriate, taking a single string argument).

I don't think its ready to make a PR yet. The last thing to implement is narrow/widen commands. I've held off from this yet though because I'm not sure I personally would use it, so I don't know how others would want it to work.

Mostly I'm wondering about whether widening/narrowing should be linear. A linear pathway seems to be @abo-abo 's original sentiment and would certainly be easier to implement. But I'm not sure how many commands really fit into linear hierarchies.

The other thing is less often used commands. If we stick to only commonly used commands (with keybindings) and logically wider/narrower commands, then structurally similar commands that people might want to switch to are left out of the feature. For example, I might be using counsel-describe-variable to look for something that is actually a face. Realising, I decide to switch to counsel-describe-face. This doesn't have a keybinding because it's so rarely used, but it does have something in common with counsel-describe-variable (they're both describe-* commands), so I feel it should be possible to switch to it. On the other hand, the more common switches should still be as quick and easy as possible.

One solution to this is to implement switching to any command the user chooses from a list, like in counsel-M-x. But by the time you've run the swith command, searched and selected, I think you've probably switched context enough that it isn't worth it.

A better solution this might be a default/alternate pattern, like ivy actions. I'm not sure how the data could be stored or accessed, but for every command there could be:

  • a default 'bigger' command (with wider scope)
  • a default 'smaller' command (with narrower scope)
  • a list of other commands, with associated letters other than 'n' and 'w'.

There could then be three keybindings in the ivy minibuffer, for:

  • widen (switch to default bigger command)
  • narrow (switch to default smaller command)
  • choose command to switch to (select from a list with an actions-like interface. In the list, 'w' is always the default bigger command, and 'n' the smaller, to reflect the pattern with 'o' in the actions list).

This would work, but there would be a lot going on, so maybe just what I've got for now might be a start?

Hugo-Heagren avatar Oct 02 '21 16:10 Hugo-Heagren