Subject: bug#42101:

From: Andy Schwartzmeyer <[email-masked]>
Date: Sat, 27 Jun 2020 23:36:53 -0700

Okay, with some message style debugging I think I narrowed it down:

(defun icomplete-force-complete-and-exit ()
  "Complete the minibuffer with the longest possible match and exit.
Use the first of the matches if there are any displayed, and use
the default otherwise."
  (interactive)
  ;; This function is tricky.  The mandate is to "force", meaning we
  ;; should take the first possible valid completion for the input.
  ;; However, if there is no input and we can prove that that
  ;; coincides with the default, it is much faster to just call
  ;; `minibuffer-complete-and-exit'.  Otherwise, we have to call
  ;; `minibuffer-force-complete-and-exit', which needs the full
  ;; completion set and is potentially slow and blocking.  Do the
  ;; latter if:
  (if (or
       ;; there's some input, meaning the default in off the table by
       ;; definition; OR
       (> (icomplete--field-end) (icomplete--field-beg))
       ;; there's no input, but there's also no minibuffer default
       ;; (and the user really wants to see completions on no input,
       ;; meaning he expects a "force" to be at least attempted); OR
       (and (not minibuffer-default)
            icomplete-show-matches-on-no-input)
       ;; there's no input but the full completion set has been
       ;; calculated, This causes the first cached completion to
       ;; be taken (i.e. the one that the user sees highlighted)
       completion-all-sorted-completions)
      (progn
        (message (if completion-all-sorted-completions (message "completions t") (message "completions nil")))
        (message (if minibuffer-default "default t" "default nil"))
        (message "FORCING")
        (minibuffer-force-complete-and-exit))
    ;; Otherwise take the faster route...
    (minibuffer-complete-and-exit)))

With fido-mode, the final test in the or clause, namely 'completion-all-sorted-completions’, is t, whereas in icomplete-mode it is nil. This is causing the first cached completion to be taken (i.e. the ‘completion-styles-alist’ which probably shouldn’t be the first completion anyway). Moreover, it means that this function’s existence is being overriden: it’s never shortcutting to 'minibuffer-complete-and-exit’ for fido-mode users.

Now to find out two more things:

1. Why is the completion list not bubbling minibuffer-default to the top? (I’m using helpful-variable, but this also happens with describe-variable, and I’ve noticed it with other functions that will use a default from point.)
2. Why is 'completion-all-sorted-completions’ always t when using fido-mode?

At least for #1:

In this bit of code the minibuffer-default is compared with equal and with string-prefix-p:

(defun icomplete--sorted-completions ()
 (or completion-all-sorted-completions
     (cl-loop
      with beg = (icomplete--field-beg)
      with end = (icomplete--field-end)
      with all = (completion-all-sorted-completions beg end)
      for fn in (cond ((and minibuffer-default
                            (stringp minibuffer-default) ; bug#38992
                            (= (icomplete--field-end) (icomplete--field-beg)))
                       ;; When we have a non-nil string default and
                       ;; no input whatsoever: we want to make sure
                       ;; that default is bubbled to the top so that
                       ;; `icomplete-force-complete-and-exit' will
                       ;; select it (do that even if the match
                       ;; doesn't match the completion perfectly.
                       `(,(lambda (comp)
                            (equal minibuffer-default comp))
                         ,(lambda (comp)
                            (string-prefix-p minibuffer-default comp))))
                      ((and fido-mode
                            (not minibuffer-default)
                            (eq (icomplete--category) 'file))
                       ;; `fido-mode' has some extra file-sorting
                       ;; semantics even if there isn't a default,
                       ;; which is to bubble "./" to the top if it
                       ;; exists.  This makes M-x dired RET RET go to
                       ;; the directory of current file, which is
                       ;; what vanilla Emacs and `ido-mode' both do.
                       `(,(lambda (comp)
                            (string= "./" comp)))))
      thereis (cl-loop
               for l on all
               while (consp (cdr l))
               for comp = (cadr l)
               when (funcall fn comp)
               do (setf (cdr l) (cddr l))
               and return
               (completion--cache-all-sorted-completions beg end (cons comp all)))
      finally return all)))

I’m not sure how to get it to do so, but when there are candidates satisfying both ‘equals’ and ‘string-prefix-p’, we need to get this to prefer the former over the latter.

Cheers,

Andy