10

I am trying to bind a key in minibuffer-local-map to a command which uses abort-recursive-edit or exit-minibuffer and then proceeds executing more elisp code. So I want to exit the minibuffer and then continue from the context before. The only solution I have found so far is to use run-with-timer like in this contrived example:

(defun example ()
  (interactive)
  ;; This is my current workaround
  ;; (run-with-timer 0.1 nil 'split-window-vertically)
  (abort-recursive-edit) 
  ;; This code won't be executed
  (split-window-vertically))

Is there a cleaner/better way to do something like this? I could try to setup everything with with-current-window and stuff like that and exit the minibuffer in the last instruction but maybe there is a better alternative?

1
  • If you happen to have the embark package and not ivy, you can use (embark--quit-and-run FN ARGS) (which seems like it was inspired by abo-abo's ivy implementation)
    – unhammer
    Commented Oct 4, 2023 at 9:08

3 Answers 3

5

I don't know of a way to do what you request.

As you say in your question, and as @abo-abo shows, a workaround is to use a timer to execute the code you want run after the minibuffer is exited.

The more typical approach is to execute the code from the minibuffer (i.e., before exiting). To do that, you need to set up and use the proper context, which in your case means select the window that you want to split. But your question was how to avoid doing this.

The reason you cannot (as far as I know) exit the minibuffer and then invoke the remaining code in your command is that exiting the minibuffer performs a non-local exit, by throwing to a catch tag outside the command's invocation stack (typically tag exit, but you can also exit to the top-level).

8

I wrote about this once.

The code:

(defmacro ivy-quit-and-run (&rest body)
  "Quit the minibuffer and run BODY afterwards."
  `(progn
     (put 'quit 'error-message "")
     (run-at-time nil nil
                  (lambda ()
                    (put 'quit 'error-message "Quit")
                    ,@body))
     (minibuffer-keyboard-quit)))
1
  • That looks like a good general workaround, thanks.
    – clemera
    Commented Mar 15, 2016 at 9:56
0

To exit the minibuffer and run a function, throw the exit tag with the function as the value:

(defun example ()
    (interactive)
    (throw 'exit #'my-function))

(Calling throw with the exit symbol as the tag is how functions like exit-minibuffer work - they just pass nil as the value instead of a function.)

I have some code where I use this to exit the minibuffer from a keybind with a custom error:

(throw 'exit
    (lambda ()
        (user-error "My custom error")))

(If you just try calling user-error directly from a keybind in the minibuffer, it won't exit the minibuffer.)

The function passed in the exit throw runs while the minibuffer is still selected, so use something like (get-mru-window) and (last-buffer) if you want to act on the window or buffer you were on before the minibuffer.

However, for the specific use-case of splitting windows, you have other problems: by default minibuffer-exit-hook contains minibuffer-restore-windows, and read-minibuffer-restore-windows is t by default. So you'd need to either temporarily remove that function from the hook, or temporarily set that variable to nil.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.