muffinmad / emacs-mini-frame

Show minibuffer in child frame on read-from-minibuffer
GNU General Public License v3.0
321 stars 20 forks source link

Selectively enabling mini-frame #20

Closed gcv closed 3 years ago

gcv commented 3 years ago

I'm trying to selectively enable mini-frame for some commands, and it doesn't quite work as I'd like.

Specifically, I want to enable mini-frame with Selectrum for specific commands, rather than wholesale. Pretty sure my approach will generalize to, e.g., icomplete-vertical.

Consider the following code:

(defun /with-selectrum (fn)
  (let ((status-selectrum selectrum-mode))
    (unwind-protect
        (progn
          (selectrum-mode 1)
          (if window-system
              (mini-frame-read-from-minibuffer (lambda () (call-interactively fn)))
            (call-interactively fn)))
      (unless status-selectrum (selectrum-mode -1)))))

(defun /selectrum-M-x ()
  (interactive)
  (/with-selectrum #'execute-extended-command))

(defun /selectrum-find-file ()
  (interactive)
  (/with-selectrum #'find-file))

(global-set-key (kbd "M-x") #'/selectrum-M-x)
(global-set-key (kbd "C-x C-f") #'/selectrum-find-file)

M-x works fine, but C-x C-f runs into a couple of kinds of trouble.

  1. The newly-visited file opens in a split window instead of the current window. At first, I thought I used call-interactively incorrectly, but the split doesn't happen if I change /with-selectrum to not use mini-frame.

  2. When attempting to visit a file which is a symlink to a source-controlled file someplace else, i.e., behavior governed by vc-follow-symlinks, the prompt appears in the mini-frame instead of the minibuffer. When mini-frame-mode and selectrum-mode are enabled normally, this does not occur. Which led me to try just temporarily enabling mini-frame-mode along with selectrum-mode, but this had other problems (notably visibly worse performance, as the mini frame has to be recreated on each command).

I'll be grateful for any guidance you might have on this.

muffinmad commented 3 years ago

The issue with split window on find-file can be reproduced like this in emacs -Q:

M-x load-file mini-frame.el

Insert this code into *scratch* buffer:

(mini-frame-read-from-minibuffer (lambda () (call-interactively 'find-file)))
M-x eval-buffer

Let's see what's happens here:

  1. mini-frame-read-from-minibuffer creates and shows the minibuffer-only frame.
  2. mini-frame-read-from-minibuffer calls find-file.
  3. find-file calls find-file-read-args to get filename using currently selected miniwindow.
  4. find-file calls pop-to-buffer-same-window to show the file. But pop-to-buffer-same-window can't show the file in the current window:

    Specifically, if the selected window is neither a minibuffer window (as reported by window-minibuffer-p), nor is dedicated to another buffer (see window-dedicated-p), BUFFER will be displayed in the currently selected window; otherwise it will be displayed in another window.

So the new window on non minibuffer-only frame is created.

  1. mini-frame-read-from-minibuffer hides the minibuffer-only frame and selects the frame that was previously selected.

Now, the normal behaviour, when we only advise read-from-minibuffer and read-string functions:

  1. find-file calls find-file-read-args to get filename.
  2. find-file-read-args somewhere down the stack calls read-from-minibuffer.
  3. Because we advised read-from-minibuffer, mini-frame-read-from-minibuffer kicks in.
  4. mini-frame-read-from-minibuffer creates and shows the minibuffer-only frame.
  5. mini-frame-read-from-minibuffer calls read-from-minibuffer.
  6. mini-frame-read-from-minibuffer hides the minibuffer-only frame and selects the frame that was previously selected.
  7. find-file calls pop-to-buffer-same-window to show the file.

That's why the mini-frame-read-from-minibuffer must be called with the function as close to actually read user input as possible.

I think one possible solution would be to modify /with-selectrum like this:

(defun /with-selectrum (fn)
  (let ((status-selectrum selectrum-mode))
    (unwind-protect
        (progn
          (selectrum-mode 1)
          (when window-system
        (advice-add 'read-from-minibuffer :around #'mini-frame-read-from-minibuffer))
      (call-interactively fn))
      (advice-remove 'read-from-minibuffer #'mini-frame-read-from-minibuffer)
      (unless status-selectrum (selectrum-mode -1)))))

Another solution would be to use the mini-frame-advise-functions option (added recently ;)). If you interested to show mini-frame only on M-x and find-file:

(when window-system
  (setq mini-frame-advice-functions '(find-file-read-args read-extended-command))
  (mini-frame-mode))

(defun /with-selectrum (fn)
  (let ((status-selectrum selectrum-mode))
    (unwind-protect
        (progn
          (selectrum-mode 1)
      (call-interactively fn))
      (unless status-selectrum (selectrum-mode -1)))))

Hope it will help to make it work for you.

BTW from window-system docstring:

Use of this function as a predicate is deprecated. Instead, use display-graphic-p or any of the other display-*-p predicates which report frame's specific UI-related capabilities.

gcv commented 3 years ago

This is fantastic. Thank you so much for your time. With your hints, I achieved the exact effect I wanted. mini-frame-advice-functions is a fantastic feature, also.

muffinmad commented 3 years ago

FYI mini-frame-read-from-minibuffer is checking display-graphic-p now so you can skip that check in your code.

gcv commented 3 years ago

Perfect. Thanks again!