[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
A solution to display completion candidates after point in a minibuffer
From: |
Gregory Heytings |
Subject: |
A solution to display completion candidates after point in a minibuffer |
Date: |
Fri, 02 Oct 2020 15:36:37 +0000 |
User-agent: |
Alpine 2.22 (NEB 394 2020-01-19) |
Hi,
Displaying completion candidates after point in a minibuffer has raised a
similar problem over the last years (see for instance bug#24293,
bug#39379, bug#43519 and bug#43572): when there are too many completion
candidates, the minibuffer prompt is hidden. The root of this problem is
that resize_mini_window() (in xdisp.c) computes the start of the visible
part of the minibuffer so as to make the end of the minibuffer contents
appear.
So far the solution to this problem has been to manually compute the size
of the completion candidates so that they do not use (together with the
prompt and the user input) more than max-mini-window-height lines.
Another solution, which as far as I know has not been used, would be to
set resize-mini-windows to nil, and to resize the miniwindow manually with
enlarge-window, but this solution also involves nontrivial computations,
and fiddles with a user setting.
The good news is that it is in fact possible to convince Emacs to do the
opposite of what resize_mini_window() does:
(defvar-local start-display-at-beginning-of-minibuffer nil)
(defun start-display-at-beginning-of-minibuffer (&rest args)
(when (and start-display-at-beginning-of-minibuffer (minibufferp))
(set-window-start-at-begin 1 (point))))
(defun set-window-start-at-begin (beg end)
(set-window-start nil beg)
(unless (pos-visible-in-window-p end nil t) (set-window-start-at-begin (+ beg
(/ (- end beg) 2)) end)))
(setq window-scroll-functions (cons 'start-display-at-beginning-of-minibuffer
window-scroll-functions))
(add-hook 'post-command-hook 'start-display-at-beginning-of-minibuffer)
This works with at least Emacs 24, 25, 26, 27 and 28.
This means that displaying completion candidates after point in a
minibuffer becomes a trivial task: it suffices to insert the completion
candidates in the minibuffer, without worrying at all about their size (or
about the size of the prompt and user input), and Emacs will display as
many of these candidates as possible, given the user preferences
(max-mini-window-height, resize-mini-windows, ...).
As a proof of concept, displaying completion candidates vertically with
icomplete is as easy as:
(setq icomplete-separator "\n")
(add-hook 'icomplete-minibuffer-setup-hook (lambda () (setq
start-display-at-beginning-of-minibuffer t)))
(defun icomplete-vertical-reformat-completions (completions)
(save-match-data
(if (string-match "^\\((.*)\\|\\[.+\\]\\)?{\\(\\(?:.\\|\n\\)+\\)}"
completions)
(format "%s \n%s" (or (match-string 1 completions) "") (match-string 2
completions))
completions)))
(advice-add 'icomplete-completions :filter-return
#'icomplete-vertical-reformat-completions)
A few comments, for the curious:
1. Obviously, when the miniwindow is forced to be small (for example with
(setq max-mini-window-height 1)), the prompt will be hidden when the
prompt and user input are too large.
2. The only drawback of the above solution is that is is not possible to
display an ellipsis ("...") at the end of the completion candidates list,
to indicate that some completion candidates are not displayed. It seems
to me that this is a minor limitation.
3. If the face used in the minibuffer has a height that is not a multiple
of the height of the default face, the last completion candidate will be
only partially visible.
4. set-window-start-at-begin is very fast, on my (not very recent)
computer it takes about a half millisecond. In the normal case, that is,
when the miniwindow is not too small, set-window-start-at-begin merely
does (set-window-start nil 1).
5. set-better-window-start could in theory enter an infinite loop (that
is, raise a "Lisp nesting exceeds 'max-lisp-eval-depth'" error) if setting
window-start near point failed. This cannot happen in practice.
6. Instead of using (+ beg (/ (- end beg) 2)) in set-better-window-start,
one could be tempted to use (window-body-width), but this does not work
when the face used in the minibuffer is not the default face. One could
also be tempted to use (/ (window-text-width nil t) (car
(window-text-pixel-size nil 1 2))), but this does not work with a variable
width face.
7. (add-hook 'post-command-hook 'start-display-at-beginning-of-minibuffer)
is necessary only with variable width faces, but it is does not harm to
use it with fixed width faces.
8. I believe there is only one thing missing in the "vertical icomplete"
implementation above, namely removing the first line when it is empty.
This can be done as follows:
(defun icomplete-vertical-reformat-completions (completions)
(save-match-data
(if (string-match "^\\((.*)\\|\\[.+\\]\\)?{\\(\\(?:.\\|\n\\)+\\)}"
completions)
(let* ((c (match-string 2 completions))
(nl (= (aref c 0) 10))
(cc (if nl (substring c 1) c)))
(format "%s \n%s" (or (match-string 1 completions) "") cc))
completions)))
Gregory
- A solution to display completion candidates after point in a minibuffer,
Gregory Heytings <=
- Re: A solution to display completion candidates after point in a minibuffer, Eli Zaretskii, 2020/10/02
- Re: A solution to display completion candidates after point in a minibuffer, Stefan Monnier, 2020/10/02
- Re: A solution to display completion candidates after point in a minibuffer, Gregory Heytings, 2020/10/02
- Re: A solution to display completion candidates after point in a minibuffer, Stefan Monnier, 2020/10/02
- RE: A solution to display completion candidates after point in a minibuffer, Drew Adams, 2020/10/02
- Re: A solution to display completion candidates after point in a minibuffer, Gregory Heytings, 2020/10/02
- Re: A solution to display completion candidates after point in a minibuffer, Stefan Monnier, 2020/10/02
- Re: A solution to display completion candidates after point in a minibuffer, Gregory Heytings, 2020/10/02