Subject: Re: bug#19031: 24.4; find-file in icomplete-mode shows completions with no input

From: Juri Linkov <[email-masked]>
Date: Wed, 09 Dec 2020 21:08:53 +0200

>> To make icomplete to not show completions until user starts typing filename,
>> icomplete could remember the initial minibuffer content immediately after its
>> activation, then after the user edits the minibuffer, compare the new content
>> with the stored initial one.  So this doesn't require any changes
>> outside of icomplete.
>
> This may lead to unexpected behavior:
>
>   (read-file-name "? " "~/" nil nil ".em")
>
> Completions will be shown for minibuffer content like "~/.e" and
> "~/.ema" but not for "~/.em".

Right, this behavior is described by the name of the option
icomplete-show-matches-on-no-input, i.e. with its default nil:
no input - no matches shown.  So the patch below implements this.

> Please read "until user starts typing filename" in my previous message
> as "until input doesn't contains part of filename" ;)

This is some new feature that can be implemented by a new option,
maybe something similar to icomplete-tidy-shadowed-file-names.

>> I'm not sure if such special casing for directory separators is needed.
>> The option icomplete-show-matches-on-no-input is quite simple and it
>> should check if the user changed the initial content.
>
> Anyway the 'minibuffer-default' variable is not the right place to do
> such thing.  It is used to provide default values which can be inserted
> into the minibuffer with 'M-n':
>
> 1. emacs -Q
> 2. M-: (setq enable-recursive-minibuffers t)
> 3. C-x C-f
> 4. M-: (setq minibuffer-default "foo")
> 5. M-n

I agree.

And here is the patch that implements what the name of
icomplete-show-matches-on-no-input suggests:

diff --git a/lisp/icomplete.el b/lisp/icomplete.el
index 0fdacd0a3c..84a5f88234 100644
--- a/lisp/icomplete.el
+++ b/lisp/icomplete.el
@@ -146,6 +146,9 @@ icomplete-minibuffer-setup-hook
 (defvar icomplete-overlay (make-overlay (point-min) (point-min) nil t t)
   "Overlay used to display the list of completions.")
 
+(defvar icomplete-initial nil
+  "Initial input in the minibuffer.")
+
 (defun icomplete-pre-command-hook ()
  (let ((non-essential t))
    (icomplete-tidy)))
@@ -441,6 +444,7 @@ icomplete-minibuffer-setup
   "Run in minibuffer on activation to establish incremental completion.
 Usually run by inclusion in `minibuffer-setup-hook'."
   (when (and icomplete-mode (icomplete-simple-completing-p))
+    (setq-local icomplete-initial (minibuffer-contents))
     (setq-local completion-show-inline-help nil)
     (use-local-map (make-composed-keymap icomplete-minibuffer-map
     					 (current-local-map)))
@@ -579,7 +583,9 @@ icomplete-exhibit
         (goto-char (point-max))
                                         ; Insert the match-status information:
         (when (and (or icomplete-show-matches-on-no-input
-                       (> (icomplete--field-end) (icomplete--field-beg)))
+                       (if (stringp icomplete-initial)
+                           (not (equal icomplete-initial (minibuffer-contents)))
+                         (> (icomplete--field-end) (icomplete--field-beg))))
                    (or
                     ;; Don't bother with delay after certain number of chars:
                     (> (- (point) (icomplete--field-beg))