oantolin / embark

Emacs Mini-Buffer Actions Rooted in Keymaps
GNU General Public License v3.0
917 stars 56 forks source link

Execute actions programatically #477

Closed kurnevsky closed 2 years ago

kurnevsky commented 2 years ago

It would be nice to have a way to execute some action without triggering embark prompt. For instance, I'd like to bind C-k to kill-buffer while executing consult-buffer. Is it possible to do so?

minad commented 2 years ago

You can bind a keyboard macro to the key C-k.

kurnevsky commented 2 years ago

Well, I'd consider this a hack :) But I can live with this if there is no other way and it is not going to be implemented. Since @minad you are here, is it possible to kill a buffer from consult-buffer and don't change buffer ordering? When I kill it with embark without closing minibuffer the ordering is changed - buffer above the killed one moves to the first position together with the pointer. I'm trying to replace ivy-switch-buffer with consult-buffer, and ivy-switch-buffer has really convenient way of killing :)

oantolin commented 2 years ago

You don't need Embark for this at all:

(defun kill-target-buffer ()
  (interactive)
  (kill-buffer (vertico--candidate)))

Substitute the (vertico--candidate) for the appropriate expression for whatever completion UI you use.

You can, if you wish, reuse Embark's target picking mechanism:

(defun kill-target-buffer ()
  (interactive)
  (if-let ((buffer (seq-some
                      (lambda (target)
                        (when (eq (plist-get target :type) 'buffer)
                          (plist-get target :target)))
                      (embark--targets))))
      (kill-buffer buffer)
    (user-error "No buffer target found")))

That way you can kill buffers in ibuffer too, for example.

kurnevsky commented 2 years ago

You don't need Embark for this at all:

This function has to be a bit more complex:

(let* ((candidate (vertico--candidate))
    (name (substring candidate 0 (1- (length candidate)))))
  (kill-buffer name))

For some reason candidate contains some unicode character additionally to buffer name.

Also it doesn't work that smoothly - vertico doesn't delete buffer from the candidates list when it's killed.

minad commented 2 years ago

@kurnevsky Yes, these issues that you observe with the programmatic solution are all expected. Embark does a bit more than the minimal kill-target-buffer function by @oantolin (removing the unicode characters, restarting completion such that killed buffers don't appear anymore in the list, ...). These issues are solved if you use Embark instead via a keyboard macro. The name Emacs means "Editor MACroS". Using keyboard macros is not a hack. Why do you believe that? It is the most direct solution to shorten key sequences, in this case C-k = C-. k.

oantolin commented 2 years ago

Oh, you're right, @kurnevsky, sorry about that! I forgot about consult-buffer's unicode tofu. I did test it though, I must have tested with switch-to-buffer by accident (I bind consult-buffer to C-c b and sometimes still type C-x b by accident).

Vertico is by design fairly static and does not update candidate lists. You can use (embark--restart) to rerun the current command keeping what has been typed into the minibuffer.

I also think a keyboard macro is a perfectly sensible alternative here, but I hope I showed writing your own functions to do things like this is fairly easy as well.

minad commented 2 years ago

@oantolin Is there an easy way to write the equivalent to the C-. k keyboard macro as an Elisp function? I think this is not even that trivial due to the control flow of embark-act. I think a keyboard macro is the most direct way to go here and it is definitely not a hack.

oantolin commented 2 years ago

Yeah, keyboard macro is easier, but this function is not too awful, I hope:

(defun kill-target-buffer ()
  (interactive)
  (if-let ((buffer (seq-find
                      (lambda (target)
                        (eq (plist-get target :type) 'buffer))
                      (embark--targets))))
      (embark--act 'kill-buffer buffer)
    (user-error "No buffer target found")))

Using embark--targets ensures all target finders and candidate transformers are applied, and using embark--act ensures all pre-action, target injection and post-action hooks are run (particularly embark--restart in this case).

kurnevsky commented 2 years ago

Thanks everyone! I like this kill-target-buffer function :) The only problem left is that buffer preview messes up buffer ordering when embark--restart is executed. But with previews turned off it works almost perfectly :)

oantolin commented 2 years ago

I had completely forgotten, but I had written about this topic on reddit about 7 months ago.

hmelman commented 2 years ago

I had completely forgotten, but I had written about this topic on reddit about 7 months ago.

That's a really good comment. I suggest putting it in the embark README or an FAQ or at least the wiki.