unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* A solution to display completion candidates after point in a minibuffer
@ 2020-10-02 15:36 Gregory Heytings via Emacs development discussions.
  2020-10-02 16:04 ` Eli Zaretskii
  2020-10-02 16:32 ` Stefan Monnier
  0 siblings, 2 replies; 22+ messages in thread
From: Gregory Heytings via Emacs development discussions. @ 2020-10-02 15:36 UTC (permalink / raw)
  To: emacs-devel


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



^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2020-10-04 17:00 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2020-10-02 15:36 A solution to display completion candidates after point in a minibuffer Gregory Heytings via Emacs development discussions.
2020-10-02 16:04 ` Eli Zaretskii
2020-10-02 16:14   ` Gregory Heytings via Emacs development discussions.
2020-10-02 16:20     ` Eli Zaretskii
2020-10-02 16:32 ` Stefan Monnier
2020-10-02 17:17   ` Gregory Heytings via Emacs development discussions.
2020-10-02 19:24     ` Stefan Monnier
2020-10-02 20:18       ` Drew Adams
2020-10-02 21:30       ` Gregory Heytings via Emacs development discussions.
2020-10-02 22:44         ` Stefan Monnier
2020-10-02 23:11           ` Gregory Heytings via Emacs development discussions.
2020-10-03  0:10             ` Stefan Monnier
2020-10-03  6:59               ` Gregory Heytings via Emacs development discussions.
2020-10-03  9:04                 ` Eli Zaretskii
2020-10-04 16:11                   ` Gregory Heytings via Emacs development discussions.
2020-10-04 16:21                     ` Eli Zaretskii
2020-10-04 16:52                       ` Gregory Heytings via Emacs development discussions.
2020-10-04 17:00                         ` Eli Zaretskii
2020-10-03  8:25               ` Eli Zaretskii
2020-10-03  8:07         ` Eli Zaretskii
2020-10-02 22:40       ` Gregory Heytings via Emacs development discussions.
2020-10-03 12:31       ` Gregory Heytings via Emacs development discussions.

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).