GNU bug report logs -
#74561
[PATCH] Allow limiting the size of *Completions*
Previous Next
Reported by: Spencer Baugh <sbaugh <at> janestreet.com>
Date: Wed, 27 Nov 2024 20:26:02 UTC
Severity: wishlist
Tags: patch
Done: Stefan Monnier <monnier <at> iro.umontreal.ca>
Bug is archived. No further changes may be made.
To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 74561 in the body.
You can then email your comments to 74561 AT debbugs.gnu.org in the normal way.
Toggle the display of automated, internal messages from the tracker.
Report forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Wed, 27 Nov 2024 20:26:02 GMT)
Full text and
rfc822 format available.
Acknowledgement sent
to
Spencer Baugh <sbaugh <at> janestreet.com>
:
New bug report received and forwarded. Copy sent to
bug-gnu-emacs <at> gnu.org
.
(Wed, 27 Nov 2024 20:26:02 GMT)
Full text and
rfc822 format available.
Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
Tags: patch
From profiling, the main bottleneck in completion over large
completion sets is display-completion-list, when there are many
available candidates. For example, in my large monorepo, when
completing over the 589196 files or the 73897 branches, even with the
candidates narrowed down by typing some prefix to complete, TAB (when
it shows *Completions*) or ? is slow, mostly in
display-completion-list.
By adding completions-list-max with a very large default, performance
is greatly improved in these situations without impacting the normal
case of completion on reasonably sized sets.
Limiting the work done by display-completion-list is also important
for packages which auto-update *Completions* inside while-no-input:
since display-completion-list doesn't do anything which reads input,
while-no-input won't interrupt it. Such packages can instead just
bind completions-list-max to a smaller value.
* lisp/minibuffer.el (display-completion-list): Add FULL-COUNT
argument.
(completions-list-max): Add.
(minibuffer-completion-help): Truncate completions based on
completions-list-max.
In GNU Emacs 29.2.50 (build 9, x86_64-pc-linux-gnu, X toolkit, cairo
version 1.15.12, Xaw scroll bars) of 2024-11-20 built on
igm-qws-u22796a
Repository revision: 28dc0b6f9987e0def7dff4deaa23aa60f021d2a7
Repository branch: emacs-29
Windowing system distributor 'The X.Org Foundation', version 11.0.12011000
System Description: Rocky Linux 8.10 (Green Obsidian)
Configured using:
'configure --with-x-toolkit=lucid --without-gpm --without-gconf
--without-selinux --without-imagemagick --with-modules --with-gif=no
--with-tree-sitter --with-native-compilation=aot
PKG_CONFIG_PATH=/usr/local/home/garnish/libtree-sitter/0.22.6-1/lib/pkgconfig/'
[0001-Allow-limiting-the-size-of-Completions.patch (text/patch, attachment)]
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Wed, 27 Nov 2024 23:24:01 GMT)
Full text and
rfc822 format available.
Message #8 received at 74561 <at> debbugs.gnu.org (full text, mbox):
FWIW, Icicles has had this since 2010.
`C-h v icicle-max-candidates':
icicle-max-candidates is a variable defined in `icicles-opt.el'.
Its value is nil
Documentation:
Non-nil means truncate completion candidates to at most this many.
If you use library `doremi.el' then you can use `C-x #' during
completion to increment or decrement the option value using the
vertical arrow keys or the mouse wheel. A numeric prefix argument for
`C-x #' sets the increment size. A plain prefix argument (`C-u')
resets `icicle-max-candidates' to nil, meaning no truncation.
If the value is an integer and you use Do Re Mi (library `doremi.el')
then you can use multi-command `icicle-increment-option' anytime to
change the option value incrementally.
You can customize this variable.
If the list of candidates shown in `*Completions*' is truncated
because of `icicle-max-candidates' then:
1. Mode-line minor-mode lighter `Icy' is suffixed by `...'. If you
see `...' you know that if you increase `icicle-max-candidates'
(e.g. by using `C-x #') then more candidates will be available.
2. The mode-line of buffer *Completions*' shows the number of
candidates - e.g., `567 candidates'. If the display is truncated
then it shows the number displayed and the total number - e.g.,
`149/567 candidates shown'.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Thu, 28 Nov 2024 06:45:02 GMT)
Full text and
rfc822 format available.
Message #11 received at 74561 <at> debbugs.gnu.org (full text, mbox):
(Adding Stefan to the discussion.)
> Cc: dmitry <at> gutov.dev, juri <at> linkov.net
> Date: Wed, 27 Nov 2024 15:25:19 -0500
> From: Spencer Baugh via "Bug reports for GNU Emacs,
> the Swiss army knife of text editors" <bug-gnu-emacs <at> gnu.org>
>
> >From profiling, the main bottleneck in completion over large
> completion sets is display-completion-list, when there are many
> available candidates. For example, in my large monorepo, when
> completing over the 589196 files or the 73897 branches, even with the
> candidates narrowed down by typing some prefix to complete, TAB (when
> it shows *Completions*) or ? is slow, mostly in
> display-completion-list.
>
> By adding completions-list-max with a very large default, performance
> is greatly improved in these situations without impacting the normal
> case of completion on reasonably sized sets.
>
> Limiting the work done by display-completion-list is also important
> for packages which auto-update *Completions* inside while-no-input:
> since display-completion-list doesn't do anything which reads input,
> while-no-input won't interrupt it. Such packages can instead just
> bind completions-list-max to a smaller value.
IMO, that's the wrong way of solving this issue. A Lisp program can
hardly know what limitation to impose in each case. It could very
well set a limitation that leaves the important candidates out.
(And, btw, the fact that completion is slow when there are many
candidates is the wrong rationale for this kind of feature. If I
magically speed up completion by 2 orders of magnitude, would you drop
the proposal and agree to showing 589196 candidates to the user?)
IMO, this is something for the user to specify, not for Lisp programs
or global options. So the better solution for this is to ask the user
whether to show all the candidates and how many of them to show, and
the user's response will probably be different depending on the
context and the circumstances.
This is what Bash does:
User: TAB
Bash: Display all 4741 possibilities? (y or n)
Why shouldn't Emacs learn from Bash, let alone improve it (Bash
doesn't let you ask for the first 1000 possibilities, for example, or
for the last 110)?
The proposal here does not allow such UI, AFAIU. So I think we should
instead add a mechanism for supporting such a UI, and include in it
the capability for the user to tell Emacs how many candidates to show.
Then we could improve the completion interaction by using that
mechanism.
> >From profiling, the main bottleneck in completion over large
> completion sets is display-completion-list, when there are many
> available candidates. For example, in my large monorepo, when
> completing over the 589196 files or the 73897 branches, even with the
> candidates narrowed down by typing some prefix to complete, TAB (when
> it shows *Completions*) or ? is slow, mostly in
> display-completion-list.
If display-completion-list takes most of the time (something that is
expected, I think), then the problem is in display, and any
improvements should be there first. For example, could it be that it
is slow due to long lines? if not, what makes display-completion-list
so slow? More detailed analysis needed, IMO.
This is independent of the larger issue above: whatever we do to limit
the number of candidates, it's ridiculous to spend most of the time in
showing the candidates on display, so we should make that as fast as
possible, regardless of the rest.
> Limiting the work done by display-completion-list is also important
> for packages which auto-update *Completions* inside while-no-input:
> since display-completion-list doesn't do anything which reads input,
> while-no-input won't interrupt it.
Here's another immediate hint for improving the UX: make it so
display-completion-list could be interrupted. Again, limiting the
number of completions is blunt instrument, which misses opportunities
for important improvements.
> +If FULL-COUNT is non-nil, it's used as the total number of
> +completions."
This is hard to parse. Does FULL-COUNT have to be a number? if so,
the doc string should say so. And how is "total number of
completions" used by the function? without knowing that, this addition
to the doc string is not useful.
> + (completion--insert-strings completions group-fun)
> + (when (and full-count (/= full-count (length completions)))
> + (newline)
> + (insert (propertize
> + (format "Displaying %s of %s possible completions.\n"
> + (length completions) full-count)
> + 'face
> + 'shadow)))))
That leaves no possibility to display the next portion of the
candidates. Why not? couldn't some UI want to present such a feature?
> +(defcustom completions-list-max 10000
> + "Maximum number of completions for `minibuffer-completion-help' to list.
"to show" or "to display", not "to list". The latter is ambiguous,
whereas minibuffer-completion-help's job is to display the candidates.
> +After the completions are sorted, any beyond this amount are
> +discarded and a message about truncation is inserted.
Passive tense alert!
> This can
> +improve performance when displaying large numbers of completions."
Performance is not the only reason to avoid showing a very long list
of completions. If we want to include in the doc string the reasons
to use this variable, let's mention the other reasons.
More generally, since this is a user option, we should perhaps have a
variable that takes the default value from the option, and let Lisp
programs bind that variable, not the option. Suppose we have a
situation with nested completion -- would we want the outer one affect
the inner one if it binds the option to some value?
> + (when completions-list-max
> + (setq full-count (length completions))
> + (when (< completions-list-max full-count)
> + (setq completions (take completions-list-max completions))))
> +
This assumes that the process of producing the completion can never be
the bottleneck, but that is not a given. How about extending this to
allow stopping the process of collecting the candidates once the limit
is reached?
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Thu, 28 Nov 2024 18:38:02 GMT)
Full text and
rfc822 format available.
Message #14 received at submit <at> debbugs.gnu.org (full text, mbox):
> +(defcustom completions-list-max 10000
> + "Maximum number of completions for `minibuffer-completion-help' to list.
10000 is too small default value. I often use 'C-x 8 RET TAB'
to browse the full list of 50866 Unicode characters
and use Isearch to find a character by name. And it's quite fast -
usually takes only 1-2 seconds to show the completion list.
An alternative would be to populate the list lazily by small chunks
added with an idle timer. But not sure it's worth the complexity.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Fri, 29 Nov 2024 02:37:01 GMT)
Full text and
rfc822 format available.
Message #17 received at 74561 <at> debbugs.gnu.org (full text, mbox):
> > +(defcustom completions-list-max 10000
> > + "Maximum number of completions for `minibuffer-completion-help' to
> list.
>
> 10000 is too small default value. I often use 'C-x 8 RET TAB'
> to browse the full list of 50866 Unicode characters
> and use Isearch to find a character by name. And it's quite fast -
> usually takes only 1-2 seconds to show the completion list.
Why have a count as default? The option I
have (`icicle-max-candidates') has default
value `nil', meaning there's no limit by
default.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Fri, 29 Nov 2024 04:14:02 GMT)
Full text and
rfc822 format available.
Message #20 received at 74561 <at> debbugs.gnu.org (full text, mbox):
> From profiling, the main bottleneck in completion over large
> completion sets is display-completion-list, when there are many
> available candidates.
Hmm... interesting. I expected it would be the computation of faces
from things like `completion-pcm--hilit-commonality`.
Do you happen to know which part of `display-completion-list` is the
most costly? is it the actual insertion into the buffer?
I think we should try and fill the buffer lazily. We don't have much
experience with "jit" populating a buffer (the closest I can think of is
the `vlf` package, which doesn't do "jit", IIRC), so it may take some
trial-and-error until we have something that works, but it
seems worthwhile.
Stefan
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Fri, 29 Nov 2024 14:46:05 GMT)
Full text and
rfc822 format available.
Message #23 received at 74561 <at> debbugs.gnu.org (full text, mbox):
Stefan Monnier <monnier <at> iro.umontreal.ca> writes:
>> From profiling, the main bottleneck in completion over large
>> completion sets is display-completion-list, when there are many
>> available candidates.
>
> Hmm... interesting. I expected it would be the computation of faces
> from things like `completion-pcm--hilit-commonality`.
>
> Do you happen to know which part of `display-completion-list` is the
> most costly? is it the actual insertion into the buffer?
A simple way to profile this is:
(progn
(require 'profiler)
(profiler-reset)
(profiler-cpu-start 10000)
(completing-read ":" (mapcar #'number-to-string (number-sequence 0 100000)))
(profiler-cpu-stop)
(setq profiler-cpu-log (profiler-cpu-log))
(profiler-report-cpu))
The biggest individual contributors of runtime are
set/add-text-properties and insert.
> I think we should try and fill the buffer lazily. We don't have much
> experience with "jit" populating a buffer (the closest I can think of is
> the `vlf` package, which doesn't do "jit", IIRC), so it may take some
> trial-and-error until we have something that works, but it
> seems worthwhile.
Yes, I think filling the *Completions* buffer lazily would be way better
than limiting the size of the buffer, thanks everyone for your comments.
I think it would be sufficient to do something simple, and just split
filling the *Completions* buffer into two parts:
- In minibuffer-completion-help, insert only enough completion
candidates that the part of the *Completions* buffer that's displayed in
a window initially looks normal. Save the rest of the completions in a
buffer-local in *Completions*.
- Upon any kind of interaction with the *Completions* buffer, insert all
the rest of the completion candidates in completions--finish-populate.
We could be lazier than this, but this is probably sufficient to give a
big speedup, since I think *Completions* is rendered much more often
than it's interacted with.
So to do this, all we need is a way to do completions--finish-populate
at the right time.
I'm not sure when to do that. Maybe we could just call it in
window-selection-change-functions and with-minibuffer-completions-window
and maybe a few other places?
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Sat, 30 Nov 2024 17:18:02 GMT)
Full text and
rfc822 format available.
Message #26 received at 74561 <at> debbugs.gnu.org (full text, mbox):
On 29/11/2024 16:45, Spencer Baugh wrote:
>> Hmm... interesting. I expected it would be the computation of faces
>> from things like `completion-pcm--hilit-commonality`.
>>
>> Do you happen to know which part of `display-completion-list` is the
>> most costly? is it the actual insertion into the buffer?
> A simple way to profile this is:
> (progn
> (require 'profiler)
> (profiler-reset)
> (profiler-cpu-start 10000)
> (completing-read ":" (mapcar #'number-to-string (number-sequence 0 100000)))
> (profiler-cpu-stop)
> (setq profiler-cpu-log (profiler-cpu-log))
> (profiler-report-cpu))
>
> The biggest individual contributors of runtime are
> set/add-text-properties and insert.
Buffer text properties are implemented in terms of plists, so it makes
sense that they're consing and contribute to GC churn.
Actually limiting the completions buffer to N completions makes a lot of
sense from my POV - not in the least because it corresponds to rendering
a completion popup (which only shows N lines), with similar big-O
complexity.
I suppose supporting full C-s searches is something of a complication,
though. In Xref buffer, we do such adjustments in post-command-hook (for
line truncation).
Severity set to 'wishlist' from 'normal'
Request was from
Stefan Kangas <stefankangas <at> gmail.com>
to
control <at> debbugs.gnu.org
.
(Thu, 02 Jan 2025 01:57:03 GMT)
Full text and
rfc822 format available.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Mon, 13 Jan 2025 19:41:02 GMT)
Full text and
rfc822 format available.
Message #31 received at 74561 <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
Spencer Baugh <sbaugh <at> janestreet.com> writes:
> Stefan Monnier <monnier <at> iro.umontreal.ca> writes:
>> I think we should try and fill the buffer lazily. We don't have much
>> experience with "jit" populating a buffer (the closest I can think of is
>> the `vlf` package, which doesn't do "jit", IIRC), so it may take some
>> trial-and-error until we have something that works, but it
>> seems worthwhile.
>
> Yes, I think filling the *Completions* buffer lazily would be way better
> than limiting the size of the buffer, thanks everyone for your comments.
>
> I think it would be sufficient to do something simple, and just split
> filling the *Completions* buffer into two parts:
>
> - In minibuffer-completion-help, insert only enough completion
> candidates that the part of the *Completions* buffer that's displayed in
> a window initially looks normal. Save the rest of the completions in a
> buffer-local in *Completions*.
>
> - Upon any kind of interaction with the *Completions* buffer, insert all
> the rest of the completion candidates in completions--finish-populate.
>
> We could be lazier than this, but this is probably sufficient to give a
> big speedup, since I think *Completions* is rendered much more often
> than it's interacted with.
>
> So to do this, all we need is a way to do completions--finish-populate
> at the right time.
>
> I'm not sure when to do that. Maybe we could just call it in
> window-selection-change-functions and with-minibuffer-completions-window
> and maybe a few other places?
I've done this in the attached patch and it seems to work well. As a
fallback, there's also a button at the end of the *Completions* buffer
to finish filling it, in case something goes wrong with the automatic
laziness.
Incidentally, filling the buffer lazily allows the default completion
frontend to start using completion-lazy-hilit, which will be another
nice performance benefit (which can happen in a later follow-up patch).
[0001-Lazily-insert-candidates-in-Completions.patch (text/x-diff, inline)]
From d0ff52fbe81b4eea0bc319c14df269d90cfba4d2 Mon Sep 17 00:00:00 2001
From: Spencer Baugh <sbaugh <at> catern.com>
Date: Mon, 13 Jan 2025 14:32:18 -0500
Subject: [PATCH] Lazily insert candidates in *Completions*
From profiling, the main bottleneck in completion over large
completion sets is display-completion-list, when there are many
available candidates. For example, in my large monorepo, when
completing over the 589196 files or the 73897 branches, even
with the candidates narrowed down by typing some prefix to
complete, TAB (when it shows *Completions*) or ? is slow, mostly
in display-completion-list.
However, rendering all the completion candidates is unnecessary
if the *Completions* window is never scrolled to see those
candiates. By eagerly inserting only some candidates and lazily
inserting the remaining only when necessary, performance is much
improved.
* lisp/minibuffer.el (completions-insert-lazily): Add defcustom
for inserting completions lazily. (bug#74561)
(completion--lazy-insert-completions)
(completion--lazy-insert-group-fun)
(completion--lazy-insert-start, completion--lazy-insert-end):
Add.
(completion--insert-strings): Check completions-insert-lazily.
(completion--lazy-display-completion-list): Add.
(with-minibuffer-completions-window): Call
completion--lazy-display-completion-list.
* lisp/simple.el (completion-setup-function): Preserve
buffer-locals required for lazy completion insertion.
(switch-to-completions): Call
completion--lazy-display-completion-list.
---
lisp/minibuffer.el | 42 ++++++++++++++++++++++++++++++++++++++++--
lisp/simple.el | 11 ++++++++++-
2 files changed, 50 insertions(+), 3 deletions(-)
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 1f33bda5f65..dc6999c7532 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -2230,6 +2230,19 @@ completions-header-format
(string :tag "Format string for heading line"))
:version "29.1")
+(defcustom completions-insert-lazily 200
+ "If non-nil, completions are inserted lazily.
+
+When a number, only that many completions are inserted, and the
+rest are inserted lazily."
+ :type '(choice (const :tag "Render all completions immediately" nil)
+ (integer :tag "Only render this many completions")))
+
+(defvar-local completion--lazy-insert-completions nil)
+(defvar-local completion--lazy-insert-group-fun nil)
+(defvar-local completion--lazy-insert-start nil)
+(defvar-local completion--lazy-insert-end nil)
+
(defun completion--insert-strings (strings &optional group-fun)
"Insert a list of STRINGS into the current buffer.
The candidate strings are inserted into the buffer depending on the
@@ -2251,14 +2264,26 @@ completion--insert-strings
;; Don't allocate more columns than we can fill.
;; Windows can't show less than 3 lines anyway.
(max 1 (/ (length strings) 2))))
- (colwidth (/ wwidth columns)))
+ (colwidth (/ wwidth columns))
+ is-truncated)
(unless (or tab-stop-list (null completion-tab-width)
(zerop (mod colwidth completion-tab-width)))
;; Align to tab positions for the case
;; when the caller uses tabs inside prefix.
(setq colwidth (- colwidth (mod colwidth completion-tab-width))))
+ (when (and completions-insert-lazily (< completions-insert-lazily (length strings)))
+ (setq is-truncated t)
+ (setq-local completion--lazy-insert-completions strings)
+ (setq-local completion--lazy-insert-group-fun group-fun)
+ (setq-local completion--lazy-insert-start (point-marker))
+ (setq strings (take completions-insert-lazily strings)))
(funcall (intern (format "completion--insert-%s" completions-format))
- strings group-fun length wwidth colwidth columns))))
+ strings group-fun length wwidth colwidth columns)
+ (when is-truncated
+ (newline)
+ (insert-button "[Completions truncated, click here to insert the rest.]"
+ 'action #'completion--lazy-display-completion-list)
+ (setq-local completion--lazy-insert-end (point-marker))))))
(defun completion--insert-horizontal (strings group-fun
length wwidth
@@ -2507,6 +2532,18 @@ display-completion-list
(run-hooks 'completion-setup-hook)
nil)
+(defun completion--lazy-display-completion-list (&optional _button)
+ (when completion--lazy-insert-completions
+ (let ((completions-insert-lazily nil)
+ (standard-output (current-buffer))
+ (inhibit-read-only t))
+ (delete-region completion--lazy-insert-start completion--lazy-insert-end)
+ (save-excursion
+ (goto-char completion--lazy-insert-start)
+ (completion--insert-strings
+ completion--lazy-insert-completions completion--lazy-insert-group-fun)
+ (setq-local completion--lazy-insert-completions nil)))))
+
(defvar completion-extra-properties nil
"Property list of extra properties of the current completion job.
These include:
@@ -4962,6 +4999,7 @@ with-minibuffer-completions-window
(get-buffer-window "*Completions*" 0)))))
(when window
(with-selected-window window
+ (completion--lazy-display-completion-list)
,@body))))
(defcustom minibuffer-completion-auto-choose t
diff --git a/lisp/simple.el b/lisp/simple.el
index c47ea8660f9..44221ba1bed 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -10455,10 +10455,18 @@ completion-setup-function
(buffer-substring (minibuffer-prompt-end) (point)))))))
(with-current-buffer standard-output
(let ((base-position completion-base-position)
- (insert-fun completion-list-insert-choice-function))
+ (insert-fun completion-list-insert-choice-function)
+ (lazy-completions completion--lazy-insert-completions)
+ (lazy-group-fun completion--lazy-insert-group-fun)
+ (lazy-start completion--lazy-insert-start)
+ (lazy-end completion--lazy-insert-end))
(completion-list-mode)
(when completions-highlight-face
(setq-local cursor-face-highlight-nonselected-window t))
+ (setq-local completion--lazy-insert-completions lazy-completions)
+ (setq-local completion--lazy-insert-group-fun lazy-group-fun)
+ (setq-local completion--lazy-insert-start lazy-start)
+ (setq-local completion--lazy-insert-end lazy-end)
(setq-local completion-base-position base-position)
(setq-local completion-list-insert-choice-function insert-fun))
(setq-local completion-reference-buffer mainbuf)
@@ -10504,6 +10512,7 @@ switch-to-completions
(progn (minibuffer-completion-help)
(get-buffer-window "*Completions*" 0)))))
(select-window window)
+ (completion--lazy-display-completion-list)
(when (bobp)
(cond
((and (memq this-command '(completion-at-point minibuffer-complete))
--
2.44.0
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Tue, 11 Feb 2025 15:04:01 GMT)
Full text and
rfc822 format available.
Message #34 received at 74561 <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
Improved the patch further on feedback from Stefan. It now also uses
completion-lazy-hilit, which further improves performance.
I'm not sure if completion-lazy-hilit-fn needs to be saved and restored
(like group-fun is being saved and restored). The current version of
this patch doesn't save completion-lazy-hilit-fn, and that seems to work
fine, because completion-lazy-hilit-fn hasn't changed by the time we
call completion--lazy-insert-strings. But maybe this is not robust in
some case?
[0001-Lazily-highlight-and-insert-candidates-in-Completion.patch (text/x-diff, inline)]
From 454ce335856c6ab550447f7ed1d79dbb67d6f3ef Mon Sep 17 00:00:00 2001
From: Spencer Baugh <sbaugh <at> catern.com>
Date: Mon, 13 Jan 2025 14:32:18 -0500
Subject: [PATCH] Lazily highlight and insert candidates in *Completions*
From profiling, the main bottleneck in completion over large
completion sets is display-completion-list, when there are many
available candidates. For example, in my large monorepo, when
completing over the 589196 files or the 73897 branches, even
with the candidates narrowed down by typing some prefix to
complete, TAB (when it shows *Completions*) or ? is slow, mostly
in display-completion-list.
However, rendering all the completion candidates is unnecessary if
the *Completions* window is never scrolled to see those candiates. By
eagerly inserting only some candidates and lazily highlighting and
inserting the remaining candidates only when necessary, performance is
much improved.
* lisp/minibuffer.el (completions-insert-lazily): Add defcustom
for inserting completions lazily. (bug#74561)
(completion--lazy-insert-completions)
(completion--lazy-insert-group-fun)
(completion--lazy-insert-start, completion--lazy-insert-end):
Add.
(completion--insert-strings): Check completions-insert-lazily.
(minibuffer-completion-help): Set completion-lazy-hilit.
(completion--lazy-insert-strings): Add.
(with-minibuffer-completions-window): Call
completion--lazy-insert-strings.
* lisp/simple.el (completion-setup-function): Preserve
buffer-locals required for lazy completion insertion.
(switch-to-completions): Call
completion--lazy-insert-strings.
---
lisp/minibuffer.el | 49 ++++++++++++++++++++++++++++++++++++++++++++--
lisp/simple.el | 5 ++++-
2 files changed, 51 insertions(+), 3 deletions(-)
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 1f33bda5f65..9dca6bb2d15 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -2230,6 +2230,16 @@ completions-header-format
(string :tag "Format string for heading line"))
:version "29.1")
+(defcustom completions-insert-lazily 200
+ "If non-nil, completions are inserted lazily.
+
+When a number, only that many completions are inserted, and the
+rest are inserted lazily."
+ :type '(choice (const :tag "Render all completions immediately" nil)
+ (integer :tag "Only render this many completions")))
+
+(defvar-local completion--lazy-insert-button nil)
+
(defun completion--insert-strings (strings &optional group-fun)
"Insert a list of STRINGS into the current buffer.
The candidate strings are inserted into the buffer depending on the
@@ -2257,8 +2267,41 @@ completion--insert-strings
;; Align to tab positions for the case
;; when the caller uses tabs inside prefix.
(setq colwidth (- colwidth (mod colwidth completion-tab-width))))
- (funcall (intern (format "completion--insert-%s" completions-format))
- strings group-fun length wwidth colwidth columns))))
+ (let ((truncated-strings
+ (and completions-insert-lazily
+ (< completions-insert-lazily (length strings))
+ (take completions-insert-lazily strings)))
+ (start (point)))
+ (funcall (intern (format "completion--insert-%s" completions-format))
+ (mapcar (lambda (candidate)
+ (if (consp candidate)
+ (setcar candidate (completion-lazy-hilit (car candidate)))
+ (completion-lazy-hilit candidate)))
+ (or truncated-strings strings))
+ group-fun length wwidth colwidth columns)
+ (when truncated-strings
+ (newline)
+ (setq-local completion--lazy-insert-button
+ (insert-button "[Completions truncated, click here to insert the rest.]"
+ 'action #'completion--lazy-insert-strings))
+ (button-put completion--lazy-insert-button 'group-fun group-fun)
+ (button-put completion--lazy-insert-button 'completions-start (copy-marker start))
+ (button-put completion--lazy-insert-button 'completion-strings strings))))))
+
+(defun completion--lazy-insert-strings (&optional button)
+ (setq button (or button completion--lazy-insert-button))
+ (when button
+ (let ((completions-insert-lazily nil)
+ (completion-lazy-hilit t)
+ (standard-output (current-buffer))
+ (inhibit-read-only t)
+ (group-fun (button-get button 'group-fun))
+ (strings (button-get button 'completion-strings)))
+ (save-excursion
+ (goto-char (button-get button 'completions-start))
+ (delete-region (point) (button-end button))
+ (setq-local completion--lazy-insert-button nil)
+ (completion--insert-strings strings group-fun)))))
(defun completion--insert-horizontal (strings group-fun
length wwidth
@@ -2620,6 +2663,7 @@ minibuffer-completion-help
(end (or end (point-max)))
(string (buffer-substring start end))
(md (completion--field-metadata start))
+ (completion-lazy-hilit completions-insert-lazily)
(completions (completion-all-completions
string
minibuffer-completion-table
@@ -4962,6 +5006,7 @@ with-minibuffer-completions-window
(get-buffer-window "*Completions*" 0)))))
(when window
(with-selected-window window
+ (completion--lazy-insert-strings)
,@body))))
(defcustom minibuffer-completion-auto-choose t
diff --git a/lisp/simple.el b/lisp/simple.el
index e1c0dd4a092..57553343c04 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -10455,10 +10455,12 @@ completion-setup-function
(buffer-substring (minibuffer-prompt-end) (point)))))))
(with-current-buffer standard-output
(let ((base-position completion-base-position)
- (insert-fun completion-list-insert-choice-function))
+ (insert-fun completion-list-insert-choice-function)
+ (lazy-button completion--lazy-insert-button))
(completion-list-mode)
(when completions-highlight-face
(setq-local cursor-face-highlight-nonselected-window t))
+ (setq-local completion--lazy-insert-button lazy-button)
(setq-local completion-base-position base-position)
(setq-local completion-list-insert-choice-function insert-fun))
(setq-local completion-reference-buffer mainbuf)
@@ -10504,6 +10506,7 @@ switch-to-completions
(progn (minibuffer-completion-help)
(get-buffer-window "*Completions*" 0)))))
(select-window window)
+ (completion--lazy-insert-strings)
(when (bobp)
(cond
((and (memq this-command '(completion-at-point minibuffer-complete))
--
2.44.0
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Tue, 11 Feb 2025 15:35:02 GMT)
Full text and
rfc822 format available.
Message #37 received at 74561 <at> debbugs.gnu.org (full text, mbox):
> Cc: João Távora <joaotavora <at> gmail.com>,
> dmitry <at> gutov.dev, 74561 <at> debbugs.gnu.org,
> Stefan Monnier <monnier <at> iro.umontreal.ca>, juri <at> linkov.net
> From: sbaugh <at> catern.com
> Date: Tue, 11 Feb 2025 15:03:31 +0000 (UTC)
>
> +(defcustom completions-insert-lazily 200
> + "If non-nil, completions are inserted lazily.
Does the default value mean a change in user-facing behavior of
completion commands?
> +When a number, only that many completions are inserted, and the
Please use "If", not "When". The latter could be misinterpreted as
meaning some time-related condition, which is not what you mean here.
Also, what about the :version tag?
And this user option needs a NEWS entry.
Thanks.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Tue, 11 Feb 2025 19:45:01 GMT)
Full text and
rfc822 format available.
Message #40 received at 74561 <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
Eli Zaretskii <eliz <at> gnu.org> writes:
>> Cc: João Távora <joaotavora <at> gmail.com>,
>> dmitry <at> gutov.dev, 74561 <at> debbugs.gnu.org,
>> Stefan Monnier <monnier <at> iro.umontreal.ca>, juri <at> linkov.net
>> From: sbaugh <at> catern.com
>> Date: Tue, 11 Feb 2025 15:03:31 +0000 (UTC)
>>
>> +(defcustom completions-insert-lazily 200
>> + "If non-nil, completions are inserted lazily.
>
> Does the default value mean a change in user-facing behavior of
> completion commands?
Nope, no user-facing behavior changes. In fact, this probably shouldn't
be user-customizable at all, since it's an internal optimization with
essentially no cost. So I've changed this into a defvar
completions--insert-lazily.
>> +When a number, only that many completions are inserted, and the
>
> Please use "If", not "When". The latter could be misinterpreted as
> meaning some time-related condition, which is not what you mean here.
Done.
> Also, what about the :version tag?
No longer a defcustom.
> And this user option needs a NEWS entry.
Added a NEWS entry for the change in general.
Updated patch:
[0001-Lazily-highlight-and-insert-candidates-in-Completion.patch (text/x-patch, inline)]
From fd4ca55084fa24998c09cabbb6e7dd2a88164abd Mon Sep 17 00:00:00 2001
From: Spencer Baugh <sbaugh <at> catern.com>
Date: Mon, 13 Jan 2025 14:32:18 -0500
Subject: [PATCH] Lazily highlight and insert candidates in *Completions*
From profiling, the main bottleneck in completion over large
completion sets is display-completion-list, when there are many
available candidates. For example, in my large monorepo, when
completing over the 589196 files or the 73897 branches, even
with the candidates narrowed down by typing some prefix to
complete, TAB (when it shows *Completions*) or ? is slow, mostly
in display-completion-list.
However, rendering all the completion candidates is unnecessary if
the *Completions* window is never scrolled to see those candiates. By
eagerly inserting only some candidates and lazily highlighting and
inserting the remaining candidates only when necessary, performance is
much improved.
* lisp/minibuffer.el (completion--insert-strings): Insert completions
lazily. (bug#74561)
(completion--lazy-insert-strings)
(completions--insert-lazily)
(completions--lazy-insert-button) Add.
(minibuffer-completion-help): Set completion-lazy-hilit.
(with-minibuffer-completions-window): Call
completion--lazy-insert-strings.
* lisp/simple.el (completion-setup-function): Preserve
buffer-locals required for lazy completion insertion.
(switch-to-completions): Call
completion--lazy-insert-strings.
---
etc/NEWS | 10 ++++++++++
lisp/minibuffer.el | 47 ++++++++++++++++++++++++++++++++++++++++++++--
lisp/simple.el | 5 ++++-
3 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS
index 9fe46d818bd..61a6d9a3319 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -176,6 +176,16 @@ will still be on that candidate after "*Completions*" is updated with a
new list of completions. The candidate is automatically deselected when
the "*Completions*" buffer is hidden.
+---
+*** "*Completions*" is displayed faster with many completion candidates.
+The "*Completions*" buffer is now created and displayed faster when it
+contains many completion candidates. When intially created and
+displayed in a window, only enough completion candidates are inserted
+into the "*Completions*" buffer to fill the window. The remaining
+completion candidates are inserted automatically if you run a command
+which interacts with "*Completions*" buffer, such as
+'switch-to-completions'.
+
** Windows
+++
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index b401e4a920c..d08874f1cbb 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -2230,6 +2230,14 @@ completions-header-format
(string :tag "Format string for heading line"))
:version "29.1")
+(defvar completions--insert-lazily 200
+ "If non-nil, completions are inserted lazily.
+
+Only this many completions are inserted, and the rest are inserted
+lazily by `completion--lazy-insert-strings'.")
+
+(defvar-local completions--lazy-insert-button nil)
+
(defun completion--insert-strings (strings &optional group-fun)
"Insert a list of STRINGS into the current buffer.
The candidate strings are inserted into the buffer depending on the
@@ -2257,8 +2265,41 @@ completion--insert-strings
;; Align to tab positions for the case
;; when the caller uses tabs inside prefix.
(setq colwidth (- colwidth (mod colwidth completion-tab-width))))
- (funcall (intern (format "completion--insert-%s" completions-format))
- strings group-fun length wwidth colwidth columns))))
+ (let ((truncated-strings
+ (and completions--insert-lazily
+ (< completions--insert-lazily (length strings))
+ (take completions--insert-lazily strings)))
+ (start (point)))
+ (funcall (intern (format "completion--insert-%s" completions-format))
+ (mapcar (lambda (candidate)
+ (if (consp candidate)
+ (setcar candidate (completion-lazy-hilit (car candidate)))
+ (completion-lazy-hilit candidate)))
+ (or truncated-strings strings))
+ group-fun length wwidth colwidth columns)
+ (when truncated-strings
+ (newline)
+ (setq-local completions--lazy-insert-button
+ (insert-button "[Completions truncated, click here to insert the rest.]"
+ 'action #'completion--lazy-insert-strings))
+ (button-put completions--lazy-insert-button 'group-fun group-fun)
+ (button-put completions--lazy-insert-button 'completions-start (copy-marker start))
+ (button-put completions--lazy-insert-button 'completion-strings strings))))))
+
+(defun completion--lazy-insert-strings (&optional button)
+ (setq button (or button completions--lazy-insert-button))
+ (when button
+ (let ((completions--insert-lazily nil)
+ (completion-lazy-hilit t)
+ (standard-output (current-buffer))
+ (inhibit-read-only t)
+ (group-fun (button-get button 'group-fun))
+ (strings (button-get button 'completion-strings)))
+ (save-excursion
+ (goto-char (button-get button 'completions-start))
+ (delete-region (point) (button-end button))
+ (setq-local completions--lazy-insert-button nil)
+ (completion--insert-strings strings group-fun)))))
(defun completion--insert-horizontal (strings group-fun
length wwidth
@@ -2644,6 +2685,7 @@ minibuffer-completion-help
(end (or end (point-max)))
(string (buffer-substring start end))
(md (completion--field-metadata start))
+ (completion-lazy-hilit completions--insert-lazily)
(completions (completion-all-completions
string
minibuffer-completion-table
@@ -4986,6 +5028,7 @@ with-minibuffer-completions-window
(get-buffer-window "*Completions*" 0)))))
(when window
(with-selected-window window
+ (completion--lazy-insert-strings)
,@body))))
(defcustom minibuffer-completion-auto-choose t
diff --git a/lisp/simple.el b/lisp/simple.el
index e1c0dd4a092..57553343c04 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -10455,10 +10455,12 @@ completion-setup-function
(buffer-substring (minibuffer-prompt-end) (point)))))))
(with-current-buffer standard-output
(let ((base-position completion-base-position)
- (insert-fun completion-list-insert-choice-function))
+ (insert-fun completion-list-insert-choice-function)
+ (lazy-button completion--lazy-insert-button))
(completion-list-mode)
(when completions-highlight-face
(setq-local cursor-face-highlight-nonselected-window t))
+ (setq-local completion--lazy-insert-button lazy-button)
(setq-local completion-base-position base-position)
(setq-local completion-list-insert-choice-function insert-fun))
(setq-local completion-reference-buffer mainbuf)
@@ -10504,6 +10506,7 @@ switch-to-completions
(progn (minibuffer-completion-help)
(get-buffer-window "*Completions*" 0)))))
(select-window window)
+ (completion--lazy-insert-strings)
(when (bobp)
(cond
((and (memq this-command '(completion-at-point minibuffer-complete))
--
2.39.3
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Tue, 11 Feb 2025 20:08:02 GMT)
Full text and
rfc822 format available.
Message #43 received at 74561 <at> debbugs.gnu.org (full text, mbox):
Spencer Baugh via "Bug reports for GNU Emacs, the Swiss army knife of
text editors" <bug-gnu-emacs <at> gnu.org> writes:
> Nope, no user-facing behavior changes. In fact, this probably shouldn't
> be user-customizable at all, since it's an internal optimization with
> essentially no cost. So I've changed this into a defvar
> completions--insert-lazily.
SGTM, but I have a question:
What happens on very large displays? Is 200 always going to be enough
completions? I imagine some users will be doing unusual things like
having the completion buffer take up their whole 42" 5K monitor.
So should this perhaps be calculated dynamically based on the geometry
of the window, somehow?
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Wed, 12 Feb 2025 12:12:02 GMT)
Full text and
rfc822 format available.
Message #46 received at 74561 <at> debbugs.gnu.org (full text, mbox):
> From: Spencer Baugh <sbaugh <at> janestreet.com>
> Cc: sbaugh <at> catern.com, juri <at> linkov.net, dmitry <at> gutov.dev,
> joaotavora <at> gmail.com, 74561 <at> debbugs.gnu.org, monnier <at> iro.umontreal.ca
> Date: Tue, 11 Feb 2025 14:44:46 -0500
>
> Eli Zaretskii <eliz <at> gnu.org> writes:
>
> >> Cc: João Távora <joaotavora <at> gmail.com>,
> >> dmitry <at> gutov.dev, 74561 <at> debbugs.gnu.org,
> >> Stefan Monnier <monnier <at> iro.umontreal.ca>, juri <at> linkov.net
> >> From: sbaugh <at> catern.com
> >> Date: Tue, 11 Feb 2025 15:03:31 +0000 (UTC)
> >>
> >> +(defcustom completions-insert-lazily 200
> >> + "If non-nil, completions are inserted lazily.
> >
> > Does the default value mean a change in user-facing behavior of
> > completion commands?
>
> Nope, no user-facing behavior changes.
The NEWS entry seems to imply that the behavior did change. So either
the NEWS entry needs to be augmented, or there's some misunderstanding
between us regarding what "behavior change" means.
Thanks.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Fri, 14 Feb 2025 20:55:01 GMT)
Full text and
rfc822 format available.
Message #49 received at 74561 <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
Stefan Kangas <stefankangas <at> gmail.com> writes:
> Spencer Baugh via "Bug reports for GNU Emacs, the Swiss army knife of
> text editors" <bug-gnu-emacs <at> gnu.org> writes:
>
>> Nope, no user-facing behavior changes. In fact, this probably shouldn't
>> be user-customizable at all, since it's an internal optimization with
>> essentially no cost. So I've changed this into a defvar
>> completions--insert-lazily.
>
> SGTM, but I have a question:
>
> What happens on very large displays? Is 200 always going to be enough
> completions? I imagine some users will be doing unusual things like
> having the completion buffer take up their whole 42" 5K monitor.
>
> So should this perhaps be calculated dynamically based on the geometry
> of the window, somehow?
Yes, good point, it definitely should be calculated dynamically.
The easiest way to do this correctly is to insert completion candidates
until we have at least frame-height lines in the *Completions* buffer.
Then no matter how the window gets resized afterwards (and it will
indeed be resized, by fit-window-to-buffer) there will be enough
completion candidates that the displayed part of the window is full.
I've done this in the attached patch.
In the process, I realized that this optimization doesn't help
completions-format=vertical without a lot of effort, since that fills
the entire first column before continuing to the second column. That's
fine, the optimization works for other values of completions-format, and
I can straightforwardly extend the optimization to
completions-format=vertical in later changes.
I've also updated NEWS to be more clear about the lack of user-facing
behavior changes.
BTW, the current implementation uses throw and catch to interrupt the
process of inserting completions when enough have been inserted. This
opens up the possibility of further optimization later: instead of
throwing the symbol "truncated" as I do now, I could throw a function, a
continuation which could be called to continue inserting the completions
from where I left off. That would avoid duplicating some work, and
therefore make things even faster.
[0001-Lazily-highlight-and-insert-candidates-in-Completion.patch (text/x-patch, inline)]
From 3641bb9cb64b671ca0abd846b1b32d760884bb43 Mon Sep 17 00:00:00 2001
From: Spencer Baugh <sbaugh <at> catern.com>
Date: Mon, 13 Jan 2025 14:32:18 -0500
Subject: [PATCH] Lazily highlight and insert candidates in *Completions*
From profiling, the main bottleneck in completion over large
completion sets is display-completion-list, when there are many
available candidates. For example, in my large monorepo, when
completing over the 589196 files or the 73897 branches, even
with the candidates narrowed down by typing some prefix to
complete, TAB (when it shows *Completions*) or ? is slow, mostly
in display-completion-list.
However, rendering all the completion candidates is unnecessary if
the *Completions* window is never scrolled to see those candiates. By
eagerly inserting only some candidates and lazily highlighting and
inserting the remaining candidates only when necessary, performance is
much improved.
* lisp/minibuffer.el (completion--insert-strings): Insert
completions lazily. (bug#74561)
(completion--lazy-insert-strings, completions--insert-lazily)
(completion--newline, completions--lazy-insert-button): Add.
(completion--insert-horizontal, completion--insert-vertical)
(completion--insert-one-column): Add LINES argument.
(completion--insert): Call completion-lazy-hilit.
(minibuffer-completion-help): Set completion-lazy-hilit.
(with-minibuffer-completions-window): Call
completion--lazy-insert-strings.
* lisp/simple.el (completion-setup-function): Preserve
buffer-locals required for lazy completion insertion.
(switch-to-completions): Call completion--lazy-insert-strings.
---
etc/NEWS | 10 ++++++++
lisp/minibuffer.el | 64 +++++++++++++++++++++++++++++++++++++++-------
lisp/simple.el | 5 +++-
3 files changed, 69 insertions(+), 10 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS
index 9fe46d818bd..f9cb7b4a247 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -176,6 +176,16 @@ will still be on that candidate after "*Completions*" is updated with a
new list of completions. The candidate is automatically deselected when
the "*Completions*" buffer is hidden.
+---
+*** "*Completions*" is displayed faster with many completion candidates.
+As always, if there are more completion candidates than can be displayed
+in the current frame, only a subset of the candidates is displayed.
+This process is now faster: only that subset of the candidates is
+actually inserted into "*Completions*" until you run a command which
+interacts with the text of the "*Completions*" buffer. This
+optimization only applies when 'completions-format' is 'horizontal' or
+'one-column'.
+
** Windows
+++
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index b401e4a920c..af13cb8d79a 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -2230,6 +2230,15 @@ completions-header-format
(string :tag "Format string for heading line"))
:version "29.1")
+(defvar-local completions--lazy-insert-button nil)
+
+(defvar completion--insert-strings-lazily t
+ "If non-nil, `completion--insert-strings' inserts strings lazily.
+
+Candidate strings are inserted until the buffer contents has enough
+lines to fill the current frame. The remainder will be inserted by
+`completion--lazy-insert-strings'.")
+
(defun completion--insert-strings (strings &optional group-fun)
"Insert a list of STRINGS into the current buffer.
The candidate strings are inserted into the buffer depending on the
@@ -2251,18 +2260,53 @@ completion--insert-strings
;; Don't allocate more columns than we can fill.
;; Windows can't show less than 3 lines anyway.
(max 1 (/ (length strings) 2))))
- (colwidth (/ wwidth columns)))
+ (colwidth (/ wwidth columns))
+ (lines (when completion--insert-strings-lazily (frame-height))))
(unless (or tab-stop-list (null completion-tab-width)
(zerop (mod colwidth completion-tab-width)))
;; Align to tab positions for the case
;; when the caller uses tabs inside prefix.
(setq colwidth (- colwidth (mod colwidth completion-tab-width))))
- (funcall (intern (format "completion--insert-%s" completions-format))
- strings group-fun length wwidth colwidth columns))))
+ (let ((start (point))
+ (truncated
+ (catch 'completions-truncated
+ (funcall (intern (format "completion--insert-%s" completions-format))
+ strings group-fun length wwidth colwidth columns lines))))
+ (when (eq truncated 'truncated)
+ (newline)
+ ;; If there's a bug which causes us to not insert the remaining
+ ;; completions automatically, the user can at least press this button.
+ (setq-local completions--lazy-insert-button
+ (insert-button "[Completions truncated, click here to insert the rest.]"
+ 'action #'completion--lazy-insert-strings))
+ (button-put completions--lazy-insert-button 'group-fun group-fun)
+ (button-put completions--lazy-insert-button 'completions-start (copy-marker start))
+ (button-put completions--lazy-insert-button 'completion-strings strings))))))
+
+(defun completion--lazy-insert-strings (&optional button)
+ (setq button (or button completions--lazy-insert-button))
+ (when button
+ (let ((completion--insert-strings-lazily nil)
+ (completion-lazy-hilit t)
+ (standard-output (current-buffer))
+ (inhibit-read-only t)
+ (group-fun (button-get button 'group-fun))
+ (strings (button-get button 'completion-strings)))
+ (save-excursion
+ (goto-char (button-get button 'completions-start))
+ (delete-region (point) (button-end button))
+ (setq-local completions--lazy-insert-button nil)
+ (completion--insert-strings strings group-fun)))))
+
+(defun completion--newline (wanted-lines)
+ "Insert a newline and throw if there are more than WANTED-LINES."
+ (newline)
+ (when (and wanted-lines (> (line-number-at-pos) wanted-lines))
+ (throw 'completions-truncated 'truncated)))
(defun completion--insert-horizontal (strings group-fun
length wwidth
- colwidth _columns)
+ colwidth _columns lines)
(let ((column 0)
(first t)
(last-title nil)
@@ -2286,7 +2330,7 @@ completion--insert-horizontal
(apply #'+ (mapcar #'string-width str))
(string-width str)))))
;; No space for `str' at point, move to next line.
- (progn (insert "\n") (setq column 0))
+ (progn (completion--newline lines) (setq column 0))
(insert " \t")
;; Leave the space unpropertized so that in the case we're
;; already past the goal column, there is still
@@ -2307,7 +2351,7 @@ completion--insert-horizontal
(defun completion--insert-vertical (strings group-fun
_length _wwidth
- colwidth columns)
+ colwidth columns _lines)
(while strings
(let ((group nil)
(column 0)
@@ -2357,7 +2401,7 @@ completion--insert-vertical
(insert "\n"))
(setq row (1+ row)))))))
-(defun completion--insert-one-column (strings group-fun &rest _)
+(defun completion--insert-one-column (strings group-fun _length _wwidth _colwidth _columns lines)
(let ((last-title nil) (last-string nil))
(dolist (str strings)
(unless (equal last-string str) ; Remove (consecutive) duplicates.
@@ -2369,14 +2413,14 @@ completion--insert-one-column
(when title
(insert (format completions-group-format title) "\n")))))
(completion--insert str group-fun)
- (insert "\n")))
+ (completion--newline lines)))
(delete-char -1)))
(defun completion--insert (str group-fun)
(if (not (consp str))
(add-text-properties
(point)
- (progn
+ (let ((str (completion-lazy-hilit str)))
(insert
(if group-fun
(funcall group-fun str 'transform)
@@ -2644,6 +2688,7 @@ minibuffer-completion-help
(end (or end (point-max)))
(string (buffer-substring start end))
(md (completion--field-metadata start))
+ (completion-lazy-hilit t)
(completions (completion-all-completions
string
minibuffer-completion-table
@@ -4986,6 +5031,7 @@ with-minibuffer-completions-window
(get-buffer-window "*Completions*" 0)))))
(when window
(with-selected-window window
+ (completion--lazy-insert-strings)
,@body))))
(defcustom minibuffer-completion-auto-choose t
diff --git a/lisp/simple.el b/lisp/simple.el
index e1c0dd4a092..c7742689897 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -10455,10 +10455,12 @@ completion-setup-function
(buffer-substring (minibuffer-prompt-end) (point)))))))
(with-current-buffer standard-output
(let ((base-position completion-base-position)
- (insert-fun completion-list-insert-choice-function))
+ (insert-fun completion-list-insert-choice-function)
+ (lazy-button completions--lazy-insert-button))
(completion-list-mode)
(when completions-highlight-face
(setq-local cursor-face-highlight-nonselected-window t))
+ (setq-local completions--lazy-insert-button lazy-button)
(setq-local completion-base-position base-position)
(setq-local completion-list-insert-choice-function insert-fun))
(setq-local completion-reference-buffer mainbuf)
@@ -10504,6 +10506,7 @@ switch-to-completions
(progn (minibuffer-completion-help)
(get-buffer-window "*Completions*" 0)))))
(select-window window)
+ (completion--lazy-insert-strings)
(when (bobp)
(cond
((and (memq this-command '(completion-at-point minibuffer-complete))
--
2.39.3
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Fri, 14 Feb 2025 22:06:02 GMT)
Full text and
rfc822 format available.
Message #52 received at 74561 <at> debbugs.gnu.org (full text, mbox):
> In the process, I realized that this optimization doesn't help
> completions-format=vertical without a lot of effort, since that fills
> the entire first column before continuing to the second column.
FWIW, maybe we should change this behavior so instead of filling
column-by-column we fill line-by-line.
When the result completely fits in the *Completions* window, the current
behavior is arguably superior (assuming alphabetical sorting: it tends
to make it easier to see the common prefixes), but for longer
completion lists I don't like it very much.
Stefan
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Sat, 15 Feb 2025 14:38:02 GMT)
Full text and
rfc822 format available.
Message #55 received at 74561 <at> debbugs.gnu.org (full text, mbox):
Stefan Monnier <monnier <at> iro.umontreal.ca> writes:
>> In the process, I realized that this optimization doesn't help
>> completions-format=vertical without a lot of effort, since that fills
>> the entire first column before continuing to the second column.
>
> FWIW, maybe we should change this behavior so instead of filling
> column-by-column we fill line-by-line.
>
> When the result completely fits in the *Completions* window, the current
> behavior is arguably superior (assuming alphabetical sorting: it tends
> to make it easier to see the common prefixes), but for longer
> completion lists I don't like it very much.
I'm not sure what you mean.
completions-format=vertical looks like:
A D G
B E H
C F I
completions-format=horizontal looks like:
A B C
D E F
G H I
Is your suggestion that completions-format=vertical should look like the
second one? But then what would be the point of it as a separate category?
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Sat, 15 Feb 2025 15:07:02 GMT)
Full text and
rfc822 format available.
Message #58 received at 74561 <at> debbugs.gnu.org (full text, mbox):
> I'm not sure what you mean.
That's because I was confused.
> completions-format=vertical looks like:
> A D G
> B E H
> C F I
>
> completions-format=horizontal looks like:
> A B C
> D E F
> G H I
Ah, right I forgot we have both settings.
> Is your suggestion that completions-format=vertical should look like the
> second one?
No, I was just noticing that I like `vertical` when it all fits on
screen, but that I prefer `horizontal` when the list is long.
Stefan
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Sat, 15 Feb 2025 17:51:03 GMT)
Full text and
rfc822 format available.
Message #61 received at 74561 <at> debbugs.gnu.org (full text, mbox):
>> In the process, I realized that this optimization doesn't help
>> completions-format=vertical without a lot of effort, since that fills
>> the entire first column before continuing to the second column.
>
> FWIW, maybe we should change this behavior so instead of filling
> column-by-column we fill line-by-line.
I think this is exactly how we should improve completion--insert-vertical.
Instead of filling the first column before continuing to the second column
the algorithm could be changed to:
1. partition the list of completions (e.g. by `seq-partition`)
into N groups corresponding to columns;
2. then take the first elements from each sublist
while laying them out line-by-line.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Sun, 16 Feb 2025 16:36:01 GMT)
Full text and
rfc822 format available.
Message #64 received at 74561 <at> debbugs.gnu.org (full text, mbox):
Juri Linkov <juri <at> linkov.net> writes:
>>> In the process, I realized that this optimization doesn't help
>>> completions-format=vertical without a lot of effort, since that fills
>>> the entire first column before continuing to the second column.
>>
>> FWIW, maybe we should change this behavior so instead of filling
>> column-by-column we fill line-by-line.
>
> I think this is exactly how we should improve completion--insert-vertical.
> Instead of filling the first column before continuing to the second column
> the algorithm could be changed to:
>
> 1. partition the list of completions (e.g. by `seq-partition`)
> into N groups corresponding to columns;
> 2. then take the first elements from each sublist
> while laying them out line-by-line.
I can do this, but just to warn you, this will require essentiallya
complete rewrite of completion--insert-vertical. So the patch is going
to become a lot larger. I suggest we should land the optimization for
completion--insert-horizontal and completion--insert-one-column first,
since those require smaller changes.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Sun, 16 Feb 2025 17:01:01 GMT)
Full text and
rfc822 format available.
Message #67 received at 74561 <at> debbugs.gnu.org (full text, mbox):
>> 1. partition the list of completions (e.g. by `seq-partition`)
>> into N groups corresponding to columns;
>> 2. then take the first elements from each sublist
>> while laying them out line-by-line.
>
> I can do this, but just to warn you, this will require essentiallya
> complete rewrite of completion--insert-vertical. So the patch is going
> to become a lot larger. I suggest we should land the optimization for
> completion--insert-horizontal and completion--insert-one-column first,
> since those require smaller changes.
+1
Stefan "not convinced such a rewrite is even worth the trouble,
because of the increase of the costs that are
proportional to the total completions"
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Sun, 16 Feb 2025 17:41:02 GMT)
Full text and
rfc822 format available.
Message #70 received at 74561 <at> debbugs.gnu.org (full text, mbox):
>>> 1. partition the list of completions (e.g. by `seq-partition`)
>>> into N groups corresponding to columns;
>>> 2. then take the first elements from each sublist
>>> while laying them out line-by-line.
>>
>> I can do this, but just to warn you, this will require essentiallya
>> complete rewrite of completion--insert-vertical. So the patch is going
>> to become a lot larger. I suggest we should land the optimization for
>> completion--insert-horizontal and completion--insert-one-column first,
>> since those require smaller changes.
>
> +1
>
> Stefan "not convinced such a rewrite is even worth the trouble,
> because of the increase of the costs that are
> proportional to the total completions"
Maybe indeed not worth the trouble, this needs more measurements
that could be postponed to later after landing the optimization
for horizontal/one-column.
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Tue, 04 Mar 2025 17:34:02 GMT)
Full text and
rfc822 format available.
Message #73 received at 74561 <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
Any further thoughts about this patch?
I have an updated version which is even more efficient: we now throw a
continuation instead of a symbol, and call that continuation to insert
the remaining completions. That means the initial completions which are
already inserted in the buffer don't need to be deleted and reinserted.
Happily, this is also less code.
[0001-Lazily-highlight-and-insert-candidates-in-Completion.patch (text/x-diff, inline)]
From ccc9687291c29e6fe9829502c560d04d1644428c Mon Sep 17 00:00:00 2001
From: Spencer Baugh <sbaugh <at> catern.com>
Date: Sun, 16 Feb 2025 20:16:19 -0500
Subject: [PATCH] Lazily highlight and insert candidates in *Completions*
From profiling, the main bottleneck in completion over large
completion sets is display-completion-list, when there are many
available candidates. For example, in my large monorepo, when
completing over the 589196 files or the 73897 branches, even
with the candidates narrowed down by typing some prefix to
complete, TAB (when it shows *Completions*) or ? is slow, mostly
in display-completion-list.
However, rendering all the completion candidates is unnecessary if
the *Completions* window is never scrolled to see those candiates. By
eagerly inserting only some candidates and lazily highlighting and
inserting the remaining candidates only when necessary, performance is
much improved.
* lisp/minibuffer.el (completion--insert-strings): Insert
completions lazily. (bug#74561)
(completions--lazy-insert-button): Add.
(completion--insert-horizontal, completion--insert-one-column):
Throw a continuation when enough lines of completions are
inserted.
(completion--insert-vertical): Add ignored lines argument.
(minibuffer-completion-help): Set completion-lazy-hilit.
(with-minibuffer-completions-window): Call
completion--lazy-insert-strings.
(with-minibuffer-completions-window):
* lisp/simple.el (completion-setup-function): Preserve
buffer-locals required for lazy completion insertion.
(switch-to-completions): Call completion--lazy-insert-strings.
* etc/NEWS: Announce.
---
etc/NEWS | 10 ++++++
lisp/minibuffer.el | 77 +++++++++++++++++++++++++++++++++++++---------
lisp/simple.el | 5 ++-
3 files changed, 77 insertions(+), 15 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS
index 39c62756085..6b030c9fdb6 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -189,6 +189,16 @@ will still be on that candidate after "*Completions*" is updated with a
new list of completions. The candidate is automatically deselected when
the "*Completions*" buffer is hidden.
+---
+*** "*Completions*" is displayed faster with many completion candidates.
+As always, if there are more completion candidates than can be displayed
+in the current frame, only a subset of the candidates is displayed.
+This process is now faster: only that subset of the candidates is
+actually inserted into "*Completions*" until you run a command which
+interacts with the text of the "*Completions*" buffer. This
+optimization only applies when 'completions-format' is 'horizontal' or
+'one-column'.
+
---
*** New user option 'crm-prompt' for 'completing-read-multiple'.
This option configures the prompt format of 'completing-read-multiple'.
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index b0ca0a2ece8..5cc9f6221e6 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -2232,6 +2232,8 @@ completions-header-format
(string :tag "Format string for heading line"))
:version "29.1")
+(defvar-local completions--lazy-insert-button nil)
+
(defun completion--insert-strings (strings &optional group-fun)
"Insert a list of STRINGS into the current buffer.
The candidate strings are inserted into the buffer depending on the
@@ -2253,23 +2255,50 @@ completion--insert-strings
;; Don't allocate more columns than we can fill.
;; Windows can't show less than 3 lines anyway.
(max 1 (/ (length strings) 2))))
- (colwidth (/ wwidth columns)))
+ (colwidth (/ wwidth columns))
+ (lines (if completions-max-height completions-max-height (frame-height))))
(unless (or tab-stop-list (null completion-tab-width)
(zerop (mod colwidth completion-tab-width)))
;; Align to tab positions for the case
;; when the caller uses tabs inside prefix.
(setq colwidth (- colwidth (mod colwidth completion-tab-width))))
- (funcall (intern (format "completion--insert-%s" completions-format))
- strings group-fun length wwidth colwidth columns))))
+ (let ((completions-continuation
+ (catch 'completions-truncated
+ (funcall (intern (format "completion--insert-%s" completions-format))
+ strings group-fun length wwidth colwidth columns lines)
+ nil)))
+ (when completions-continuation
+ ;; If there's a bug which causes us to not insert the remaining
+ ;; completions automatically, the user can at least press this button.
+ (setq-local completions--lazy-insert-button
+ (insert-button "[Completions truncated, click here to insert the rest.]"
+ 'action #'completion--lazy-insert-strings))
+ (button-put completions--lazy-insert-button
+ 'completions-continuation completions-continuation))))))
+
+(defun completion--lazy-insert-strings (&optional button)
+ (setq button (or button completions--lazy-insert-button))
+ (when button
+ (let ((completion-lazy-hilit t)
+ (standard-output (current-buffer))
+ (inhibit-read-only t)
+ (completions-continuation (button-get button 'completions-continuation)))
+ (save-excursion
+ (goto-char (button-start button))
+ (delete-region (point) (button-end button))
+ (setq-local completions--lazy-insert-button nil)
+ (funcall completions-continuation)))))
(defun completion--insert-horizontal (strings group-fun
length wwidth
- colwidth _columns)
+ colwidth _columns lines
+ &optional last-title)
(let ((column 0)
(first t)
- (last-title nil)
- (last-string nil))
- (dolist (str strings)
+ (last-string nil)
+ str)
+ (while strings
+ (setq str (pop strings))
(unless (equal last-string str) ; Remove (consecutive) duplicates.
(setq last-string str)
(when group-fun
@@ -2288,7 +2317,16 @@ completion--insert-horizontal
(apply #'+ (mapcar #'string-width str))
(string-width str)))))
;; No space for `str' at point, move to next line.
- (progn (insert "\n") (setq column 0))
+ (progn
+ (insert "\n")
+ (when (and lines (> (line-number-at-pos) lines))
+ (throw 'completions-truncated
+ (apply-partially
+ #'completion--insert-horizontal
+ ;; Add str back, since we haven't inserted it yet.
+ (cons str strings) group-fun length wwidth colwidth _columns nil
+ last-title)))
+ (setq column 0))
(insert " \t")
;; Leave the space unpropertized so that in the case we're
;; already past the goal column, there is still
@@ -2309,7 +2347,7 @@ completion--insert-horizontal
(defun completion--insert-vertical (strings group-fun
_length _wwidth
- colwidth columns)
+ colwidth columns _lines)
(while strings
(let ((group nil)
(column 0)
@@ -2359,9 +2397,12 @@ completion--insert-vertical
(insert "\n"))
(setq row (1+ row)))))))
-(defun completion--insert-one-column (strings group-fun &rest _)
- (let ((last-title nil) (last-string nil))
- (dolist (str strings)
+(defun completion--insert-one-column (strings group-fun _length _wwidth _colwidth _columns lines
+ &optional last-title)
+ (let ((last-string nil)
+ str)
+ (while strings
+ (setq str (pop strings))
(unless (equal last-string str) ; Remove (consecutive) duplicates.
(setq last-string str)
(when group-fun
@@ -2371,14 +2412,20 @@ completion--insert-one-column
(when title
(insert (format completions-group-format title) "\n")))))
(completion--insert str group-fun)
- (insert "\n")))
+ (insert "\n")
+ (when (and lines (> (line-number-at-pos) lines))
+ (throw 'completions-truncated
+ (apply-partially
+ #'completion--insert-one-column
+ strings group-fun _length _wwidth _colwidth _columns nil
+ last-title)))))
(delete-char -1)))
(defun completion--insert (str group-fun)
(if (not (consp str))
(add-text-properties
(point)
- (progn
+ (let ((str (completion-lazy-hilit str)))
(insert
(if group-fun
(funcall group-fun str 'transform)
@@ -2622,6 +2669,7 @@ minibuffer-completion-help
(end (or end (point-max)))
(string (buffer-substring start end))
(md (completion--field-metadata start))
+ (completion-lazy-hilit t)
(completions (completion-all-completions
string
minibuffer-completion-table
@@ -4965,6 +5013,7 @@ with-minibuffer-completions-window
(get-buffer-window "*Completions*" 0)))))
(when window
(with-selected-window window
+ (completion--lazy-insert-strings)
,@body))))
(defcustom minibuffer-completion-auto-choose t
diff --git a/lisp/simple.el b/lisp/simple.el
index 2d36062e9c2..12dac272129 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -10456,10 +10456,12 @@ completion-setup-function
(buffer-substring (minibuffer-prompt-end) (point)))))))
(with-current-buffer standard-output
(let ((base-position completion-base-position)
- (insert-fun completion-list-insert-choice-function))
+ (insert-fun completion-list-insert-choice-function)
+ (lazy-button completions--lazy-insert-button))
(completion-list-mode)
(when completions-highlight-face
(setq-local cursor-face-highlight-nonselected-window t))
+ (setq-local completions--lazy-insert-button lazy-button)
(setq-local completion-base-position base-position)
(setq-local completion-list-insert-choice-function insert-fun))
(setq-local completion-reference-buffer mainbuf)
@@ -10505,6 +10507,7 @@ switch-to-completions
(progn (minibuffer-completion-help)
(get-buffer-window "*Completions*" 0)))))
(select-window window)
+ (completion--lazy-insert-strings)
(when (bobp)
(cond
((and (memq this-command '(completion-at-point minibuffer-complete))
--
2.44.0
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Sun, 09 Mar 2025 15:09:02 GMT)
Full text and
rfc822 format available.
Message #76 received at 74561 <at> debbugs.gnu.org (full text, mbox):
> Any further thoughts about this patch?
LGTM,
Stefan
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Thu, 20 Mar 2025 14:55:01 GMT)
Full text and
rfc822 format available.
Message #79 received at 74561 <at> debbugs.gnu.org (full text, mbox):
Stefan Monnier <monnier <at> iro.umontreal.ca> writes:
>> Any further thoughts about this patch?
> LGTM,
Could someone please install this change, then? (I can't do it myself)
Reply sent
to
Stefan Monnier <monnier <at> iro.umontreal.ca>
:
You have taken responsibility.
(Fri, 21 Mar 2025 21:27:02 GMT)
Full text and
rfc822 format available.
Notification sent
to
Spencer Baugh <sbaugh <at> janestreet.com>
:
bug acknowledged by developer.
(Fri, 21 Mar 2025 21:27:02 GMT)
Full text and
rfc822 format available.
Message #84 received at 74561-done <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
>>> Any further thoughts about this patch?
>> LGTM,
> Could someone please install this change, then? (I can't do it myself)
Pushed with the additional patch below.
Stefan
[minibuffer.patch (text/x-diff, inline)]
commit b21636580bed822bd9fb8bb84014311fa9b4c071
Author: Spencer Baugh <sbaugh <at> catern.com>
Date: Fri Mar 21 17:24:50 2025 -0400
minibuffer.el: Fix warnings and coding style in last change
* lisp/minibuffer.el: Cut lines to fit into 80 columns.
(completion--insert-strings): Simplify `if` to `or`.
(completion--insert-horizontal, completion--insert-one-column):
Fix warning about used var starting with `_`. Avoid `apply-partially`.
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 47b152ed35d..8fba0b88b20 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -2256,7 +2256,7 @@ completion--insert-strings
;; Windows can't show less than 3 lines anyway.
(max 1 (/ (length strings) 2))))
(colwidth (/ wwidth columns))
- (lines (if completions-max-height completions-max-height (frame-height))))
+ (lines (or completions-max-height (frame-height))))
(unless (or tab-stop-list (null completion-tab-width)
(zerop (mod colwidth completion-tab-width)))
;; Align to tab positions for the case
@@ -2264,15 +2264,17 @@ completion--insert-strings
(setq colwidth (- colwidth (mod colwidth completion-tab-width))))
(let ((completions-continuation
(catch 'completions-truncated
- (funcall (intern (format "completion--insert-%s" completions-format))
+ (funcall (intern (format "completion--insert-%s"
+ completions-format))
strings group-fun length wwidth colwidth columns lines)
nil)))
(when completions-continuation
;; If there's a bug which causes us to not insert the remaining
;; completions automatically, the user can at least press this button.
(setq-local completions--lazy-insert-button
- (insert-button "[Completions truncated, click here to insert the rest.]"
- 'action #'completion--lazy-insert-strings))
+ (insert-button
+ "[Completions truncated, click here to insert the rest.]"
+ 'action #'completion--lazy-insert-strings))
(button-put completions--lazy-insert-button
'completions-continuation completions-continuation))))))
@@ -2282,7 +2284,8 @@ completion--lazy-insert-strings
(let ((completion-lazy-hilit t)
(standard-output (current-buffer))
(inhibit-read-only t)
- (completions-continuation (button-get button 'completions-continuation)))
+ (completions-continuation
+ (button-get button 'completions-continuation)))
(save-excursion
(goto-char (button-start button))
(delete-region (point) (button-end button))
@@ -2291,7 +2294,7 @@ completion--lazy-insert-strings
(defun completion--insert-horizontal (strings group-fun
length wwidth
- colwidth _columns lines
+ colwidth columns lines
&optional last-title)
(let ((column 0)
(first t)
@@ -2306,26 +2309,28 @@ completion--insert-horizontal
(unless (equal title last-title)
(setq last-title title)
(when title
- (insert (if first "" "\n") (format completions-group-format title) "\n")
+ (insert (if first "" "\n")
+ (format completions-group-format title) "\n")
(setq column 0
first t)))))
(unless first
;; FIXME: `string-width' doesn't pay attention to
;; `display' properties.
- (if (< wwidth (+ column (max colwidth
- (if (consp str)
- (apply #'+ (mapcar #'string-width str))
- (string-width str)))))
+ (if (< wwidth (+ column
+ (max colwidth
+ (if (consp str)
+ (apply #'+ (mapcar #'string-width str))
+ (string-width str)))))
;; No space for `str' at point, move to next line.
(progn
(insert "\n")
(when (and lines (> (line-number-at-pos) lines))
(throw 'completions-truncated
- (apply-partially
- #'completion--insert-horizontal
- ;; Add str back, since we haven't inserted it yet.
- (cons str strings) group-fun length wwidth colwidth _columns nil
- last-title)))
+ (lambda ()
+ (completion--insert-horizontal
+ ;; Add str back, since we haven't inserted it yet.
+ (cons str strings) group-fun length wwidth colwidth
+ columns nil last-title))))
(setq column 0))
(insert " \t")
;; Leave the space unpropertized so that in the case we're
@@ -2397,8 +2402,8 @@ completion--insert-vertical
(insert "\n"))
(setq row (1+ row)))))))
-(defun completion--insert-one-column (strings group-fun _length _wwidth _colwidth _columns lines
- &optional last-title)
+(defun completion--insert-one-column ( strings group-fun length wwidth colwidth
+ columns lines &optional last-title)
(let ((last-string nil)
str)
(while strings
@@ -2415,10 +2420,10 @@ completion--insert-one-column
(insert "\n")
(when (and lines (> (line-number-at-pos) lines))
(throw 'completions-truncated
- (apply-partially
- #'completion--insert-one-column
- strings group-fun _length _wwidth _colwidth _columns nil
- last-title)))))
+ (lambda ()
+ (completion--insert-one-column
+ strings group-fun length wwidth colwidth columns nil
+ last-title))))))
(delete-char -1)))
(defun completion--insert (str group-fun)
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Mon, 24 Mar 2025 15:42:02 GMT)
Full text and
rfc822 format available.
Message #87 received at 74561 <at> debbugs.gnu.org (full text, mbox):
Hi,
Stefan Monnier writes:
>>>> Any further thoughts about this patch?
>>> LGTM,
>> Could someone please install this change, then? (I can't do it myself)
>
> Pushed with the additional patch below.
FYI this caused a small regression wherein it doesn't populate the
*Completions* buffer when you try to scroll through it by repeatedly
hitting TAB in the minibuffer. It seemed like a simple oversight, so
I installed a fix in commit 119931a9cee.
Regards,
Eshel
Information forwarded
to
bug-gnu-emacs <at> gnu.org
:
bug#74561
; Package
emacs
.
(Mon, 24 Mar 2025 15:54:02 GMT)
Full text and
rfc822 format available.
Message #90 received at 74561 <at> debbugs.gnu.org (full text, mbox):
Eshel Yaron <me <at> eshelyaron.com> writes:
> Hi,
>
> Stefan Monnier writes:
>
>>>>> Any further thoughts about this patch?
>>>> LGTM,
>>> Could someone please install this change, then? (I can't do it myself)
>>
>> Pushed with the additional patch below.
>
> FYI this caused a small regression wherein it doesn't populate the
> *Completions* buffer when you try to scroll through it by repeatedly
> hitting TAB in the minibuffer. It seemed like a simple oversight, so
> I installed a fix in commit 119931a9cee.
Yes, thank you, your change seems like the right fix.
bug archived.
Request was from
Debbugs Internal Request <help-debbugs <at> gnu.org>
to
internal_control <at> debbugs.gnu.org
.
(Tue, 22 Apr 2025 11:24:14 GMT)
Full text and
rfc822 format available.
This bug report was last modified 57 days ago.
Previous Next
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.