20

When working on a maximized Emacs frame, having the content of the minibuffer / echo area display at the bottom of the screen can be hard to see, but also causes to lose focus on the text being edited when typing a command – which is quite an issue in terms of usability.

Is there a way to either move the minibuffer / echo area in the centre of the current emacs buffer, over the the text being edited when minibuffer is active, or, better, to capture the content of the minibuffer and display it also in the centre of the current buffer whilst typing the command?

  • For completeness, I should add that my current solution to this usability issue is to increase the font size of the minibuffer, but it looks silly on non-maximized frames. – Sébastien Le Callonnec Oct 11 '14 at 11:24
10

Popup Minibuffer at the Center

Here’s a way to do exactly what you asked: display the minibuffer at the center of the screen.

  1. Have a separate frame for the minibuffer
  2. Position it at the center
  3. Raise this frame whenever the minibuffer gains focus.

That is relatively easy to achieve, and is what you’ll get with the oneonone.el package (as @Drew points out). The problem with this approach, is that the minibuffer is also used for messages, so keeping it hidden is not something very convenient. Not to worry! I came up with another solution.

  1. Have a second minibuffer displayed on a separate frame.
  2. Position it at the center.
  3. Use the second frame for interactive commands, while the original (first) minibuffer is used for echoing messages.

Implementation

To implement this, we need to create the minibuffer frame at emacs startup.

(defvar endless/popup-frame-parameters
  '((name . "MINIBUFFER")
    (minibuffer . only)
    (height . 1)
    ;; Ajust this one to your preference.
    (top . 200))
  "Parameters for the minibuffer popup frame.")

(defvar endless/minibuffer-frame
  (let ((mf (make-frame endless/popup-frame-parameters)))
    (iconify-frame mf) mf)
  "Frame holding the extra minibuffer.")

(defvar endless/minibuffer-window
  (car (window-list endless/minibuffer-frame t))
  "")

Then we patch read-from-minibuffer to use this second minibuffer, instead of the original frame’s minibuffer.

(defmacro with-popup-minibuffer (&rest body)
  "Execute BODY using a popup minibuffer."
  (let ((frame-symbol (make-symbol "selected-frame")))
    `(let* ((,frame-symbol (selected-frame)))
       (unwind-protect 
           (progn 
             (make-frame-visible endless/minibuffer-frame)
             (when (fboundp 'point-screen-height)
               (set-frame-parameter
                endless/minibuffer-frame 
                'top (point-screen-height)))
             (select-frame-set-input-focus endless/minibuffer-frame 'norecord)
             ,@body)
         (select-frame-set-input-focus ,frame-symbol)))))

(defun use-popup-minibuffer (function)
  "Rebind FUNCTION so that it uses a popup minibuffer."
  (let* ((back-symb (intern (format "endless/backup-%s" function)))
         (func-symb (intern (format "endless/%s-with-popup-minibuffer"
                                    function)))
         (defs `(progn
                  (defvar ,back-symb (symbol-function ',function))
                  (defun ,func-symb (&rest rest)
                    ,(format "Call `%s' with a poupup minibuffer." function)
                    ,@(list (interactive-form function))
                    (with-popup-minibuffer 
                     (apply ,back-symb rest))))))
    (message "%s" defs)
    (when (and (boundp back-symb) (eval back-symb))
      (error "`%s' already defined! Can't override twice" back-symb))
    (eval defs)
    (setf (symbol-function function) func-symb)))

;;; Try at own risk.
(use-popup-minibuffer 'read-from-minibuffer)
;;; This will revert the effect.
;; (setf (symbol-function #'read-from-minibuffer) endless/backup-read-from-minibuffer)
;; (setq endless/backup-read-from-minibuffer nil)

This might not work with absolutely everything, but it worked on everything I tried so far---find-file, ido-switch-buffer, eval-expression. If you do find any exceptions, you can patch these functions on a case-by-case basis by calling use-popup-minibuffer on them.

Position Near Point

To position this minibuffer frame near the height of the point, simply define something like the following function. It’s not perfect (in fact, it's awful at a lot of cases), but it does a decent job of estimating point height.

(defun point-screen-height ()
  (* (/ (face-attribute 'default :height) 10) 2
     (- (line-number-at-pos (point))
        (line-number-at-pos (window-start)))))
  • Instead of messing around with (setf (symbol-function function) ...) you'd be better off using advice-add (or the older ad-add-advice). – Stefan Dec 11 '14 at 18:50
6

Minibuffer at the top

Displaying the minibuffer at the middle of the screen is complicated. A reasonable alternative (which you may or may not find more readable) is to move it to the top.

You can configure the location (or existence) of a frame’s minibuffer with the minibuffer frame parameter in the initial-frame-alist variable.

Separate Frame

Setting the parameter to nil will configure the frame to have no minibuffer, and Emacs will automatically create a separate minibuffer frame for you. You can position these two frames using your OS’ window manager, or you can do it from Emacs.

The following snippet examplifies how to place the minibuffer frame at the top, with the regular frame just below it, but you’ll definitely have to change the numbers to account for you monitor size.

(setq initial-frame-alist
      '((left . 1) (minibuffer . nil)
        ;; You'll need to adjust the following 3 numbers.
        (top . 75) ; In pixels
        (width . 127) ; In chars
        (height . 31)))

(setq minibuffer-frame-alist
      '((top . 1) (left . 1) (height . 2)
        ;; You'll need to adjust the following number.
        (width . 127)))

The quickest way to adjust this, is to resize the frames in your window manager, and then evaluate (frame-parameters) to see parameter values you have.

Same Frame

Another possibility is to set the minibuffer parameter to a window. Unfortunately, I was unable to get this to work for a window in the same frame.

5

Library oneonone.el gives you this out of the box.

Just customize option 1on1-minibuffer-frame-top/bottom to the position you want for the standalone minibuffer frame. The value is the number of pixels from the top (if non-negative) or the bottom (if negative) of your display. For example, set the value to your display height divided by 2, to center it vertically.

If you instead want it to be positioned dynamically, say in the vertical center of the current frame, then you can do that by (a) leaving 1on1-minibuffer-frame-top/bottom = nil and (b) advising function 1on1-set-minibuffer-frame-top/bottom to bind that variable dynamically to the appropriate position. For example:

(defadvice 1on1-set-minibuffer-frame-top/bottom (around center activate)
  (let ((1on1-minibuffer-frame-top/bottom  (my-dynamic-position ...)))
    ad-do-it))

(Define function my-dynamic-position to calculate the position you want, based on whatever criteria you want (current window/frame size & position, phase of the moon,...).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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