Subject: Re: bug#14175: 24.3.50; Bad completion behavior with read-file-name-completion-ignore-case t

From: Michael Heerdegen <[email-masked]>
Date: Fri, 12 Apr 2013 16:53:47 +0200

Michael Heerdegen <[email-masked]> writes:

> Hello,
>
> 1.  I tried to debug a bit.
>
> When you hit TAB the second time, this call in
> `completion--nth-completion' changes the result from ("download/"
> "Downloads/") to ("Download/" "Downloads/"):
>
>    (if requote
>         (funcall requote result n)
>       result)

Some more information: this call at the very last lines of
`completion-table-with-quoting':

(when last
 (setcdr last nil)
 (completion--twq-all string ustring
                      unquoted-result base
                      unquote requote))

returns the changed case of the first candidate (from "~/download" to
"~/Download"), i.e., introduces the error.

Here is a backtrace of that point of time:

,----------------------------------------------------------------------
|   completion--twq-all("~/Download" "~/Download" (#("download/" 0 8 (face (completions-common-part)) 8 9 (face (completions-first-difference))) #("Downloads/" 0 8 (face (completions-common-part)) 8 9 (face (completions-first-difference)))) 2 substitute-in-file-name completion--sifn-requote)
|   (progn (setcdr last nil) (completion--twq-all string ustring unquoted-result base unquote requote))
|   (if last (progn (setcdr last nil) (completion--twq-all string ustring unquoted-result base unquote requote)))
|   (let* ((last (last unquoted-result)) (base (or (cdr last) 0))) (if last (progn (setcdr last nil) (completion--twq-all string ustring unquoted-result base unquote requote))))
|   (let nil (debug) (let* ((last (last unquoted-result)) (base (or (cdr last) 0))) (if last (progn (setcdr last nil) (completion--twq-all string ustring unquoted-result base unquote requote)))))
|   (cond ((eq op (quote 1)) (let nil (if (not (stringp (car-safe unquoted-result))) unquoted-result (completion--twq-try string ustring (car unquoted-result) (cdr unquoted-result) unquote requote)))) ((eq op (quote 2)) (let nil (debug) (let* ((last (last unquoted-result)) (base (or (cdr last) 0))) (if last (progn (setcdr last nil) (completion--twq-all string ustring unquoted-result base unquote requote)))))) (t nil))
|   (closure ((uprefix . "~/Download") (ustring . #1="~/Download") (action . completion--unquote) (pred . 10) (string . #1#) (requote . completion--sifn-requote) (unquote . substitute-in-file-name) (table . completion-file-name-table) t) (unquoted-result op) (cond ((eq op (quote 1)) (let nil (if (not (stringp (car-safe unquoted-result))) unquoted-result (completion--twq-try string ustring (car unquoted-result) (cdr unquoted-result) unquote requote)))) ((eq op (quote 2)) (let nil (debug) (let* ((last (last unquoted-result)) (base (or ... 0))) (if last (progn (setcdr last nil) (completion--twq-all string ustring unquoted-result base unquote requote)))))) (t nil)))((#("download/" 0 8 (face (completions-common-part)) 8 9 (face (completions-first-difference))) #("Downloads/" 0 8 (face (completions-common-part)) 8 9 (face (completions-first-difference)))) 2)
|   funcall((closure ((uprefix . "~/Download") (ustring . #1="~/Download") (action . completion--unquote) (pred . 10) (string . #1#) (requote . completion--sifn-requote) (unquote . substitute-in-file-name) (table . completion-file-name-table) t) (unquoted-result op) (cond ((eq op (quote 1)) (let nil (if (not (stringp (car-safe unquoted-result))) unquoted-result (completion--twq-try string ustring (car unquoted-result) (cdr unquoted-result) unquote requote)))) ((eq op (quote 2)) (let nil (debug) (let* ((last (last unquoted-result)) (base (or ... 0))) (if last (progn (setcdr last nil) (completion--twq-all string ustring unquoted-result base unquote requote)))))) (t nil))) (#("download/" 0 8 (face (completions-common-part)) 8 9 (face (completions-first-difference))) #("Downloads/" 0 8 (face (completions-common-part)) 8 9 (face (completions-first-difference)))) 2)
|   (if requote (funcall requote result n) result)
|   (let ((requote (if (completion-metadata-get metadata (quote completion--unquote-requote)) (progn (let ((new ...)) (setq string (car ...)) (setq table (car ...)) (setq point (car ...)) (car (prog1 new ...)))))) (result (progn (completion--some (function (lambda (style) (funcall ... string table pred point))) (completion--styles metadata))))) (if requote (funcall requote result n) result))
|   completion--nth-completion(2 "~/Download" read-file-name-internal file-exists-p 10 (metadata (category . file) (completion--unquote-requote . t)))
|   completion-all-completions("~/Download" read-file-name-internal file-exists-p 10 (metadata (category . file) (completion--unquote-requote . t)))
|   (let* ((start (field-beginning)) (end (field-end)) (string (field-string)) (md (completion--field-metadata start)) (completions (completion-all-completions string minibuffer-completion-table minibuffer-completion-predicate (- (point) (field-beginning)) md))) (message nil) (if (or (null completions) (and (not (consp (cdr completions))) (equal (car completions) string))) (progn (minibuffer-hide-completions) (ding) (minibuffer-message (if completions "Sole completion" "No completions"))) (let* ((last (last completions)) (base-size (cdr last)) (prefix (if (zerop base-size) nil (substring string 0 base-size))) (all-md (completion--metadata (buffer-substring-no-properties start (point)) base-size md minibuffer-completion-table minibuffer-completion-predicate)) (afun (or (completion-metadata-get all-md (quote annotation-function)) (plist-get completion-extra-properties :annotation-function) completion-annotate-function)) (display-buffer-mark-dedicated (quote soft))) (let* ((#1=#:old-dir default-directory) (#4=#:buf (save-current-buffer (set-buffer ...) (prog1 ... ... ... ... ... ... ...))) (standard-output #4#)) (prog1 (progn (if last (progn ...)) (setq completions (let ... ...)) (if afun (progn ...)) (save-current-buffer (set-buffer standard-output) (set ... ...) (set ... ...)) (display-completion-list completions)) (internal-temp-output-buffer-show #4#))))) nil)
|   minibuffer-completion-help()
|   (if (if (eq completion-auto-help (quote lazy)) (let nil (eq this-command last-command)) (let nil completion-auto-help)) (minibuffer-completion-help) (completion--message "Next char not unique"))
|   (cond ((and (consp (cdr comps)) (not (condition-case nil (progn (consp (nthcdr threshold comps))) (error nil)))) (setq completed t exact t) (completion--cache-all-sorted-completions comps) (minibuffer-force-complete)) (completed (minibuffer-hide-completions) (if exact (completion--done completion (if (< comp-pos (length completion)) (quote exact) (quote unknown))))) ((not exact) (if (if (eq completion-auto-help (quote lazy)) (let nil (eq this-command last-command)) (let nil completion-auto-help)) (minibuffer-completion-help) (completion--message "Next char not unique"))) (t (if (and (eq this-command last-command) completion-auto-help) (minibuffer-completion-help)) (completion--done completion (quote exact) (if expect-exact nil "Complete, but not unique"))))
|   (let* ((exact (test-completion completion minibuffer-completion-table minibuffer-completion-predicate)) (threshold (completion--cycle-threshold md)) (comps (if (and threshold (or (not completed) (< (car ...) comp-pos))) (progn (completion-all-sorted-completions))))) (completion--flush-all-sorted-completions) (cond ((and (consp (cdr comps)) (not (condition-case nil (progn (consp ...)) (error nil)))) (setq completed t exact t) (completion--cache-all-sorted-completions comps) (minibuffer-force-complete)) (completed (minibuffer-hide-completions) (if exact (completion--done completion (if (< comp-pos (length completion)) (quote exact) (quote unknown))))) ((not exact) (if (if (eq completion-auto-help (quote lazy)) (let nil (eq this-command last-command)) (let nil completion-auto-help)) (minibuffer-completion-help) (completion--message "Next char not unique"))) (t (if (and (eq this-command last-command) completion-auto-help) (minibuffer-completion-help)) (completion--done completion (quote exact) (if expect-exact nil "Complete, but not unique")))) (minibuffer--bitset completed t exact))
|   (if (not (or unchanged completed)) (completion--do-completion try-completion-function expect-exact) (let* ((exact (test-completion completion minibuffer-completion-table minibuffer-completion-predicate)) (threshold (completion--cycle-threshold md)) (comps (if (and threshold (or (not completed) (< ... comp-pos))) (progn (completion-all-sorted-completions))))) (completion--flush-all-sorted-completions) (cond ((and (consp (cdr comps)) (not (condition-case nil (progn ...) (error nil)))) (setq completed t exact t) (completion--cache-all-sorted-completions comps) (minibuffer-force-complete)) (completed (minibuffer-hide-completions) (if exact (completion--done completion (if (< comp-pos ...) (quote exact) (quote unknown))))) ((not exact) (if (if (eq completion-auto-help (quote lazy)) (let nil (eq this-command last-command)) (let nil completion-auto-help)) (minibuffer-completion-help) (completion--message "Next char not unique"))) (t (if (and (eq this-command last-command) completion-auto-help) (minibuffer-completion-help)) (completion--done completion (quote exact) (if expect-exact nil "Complete, but not unique")))) (minibuffer--bitset completed t exact)))
|   (let* ((comp-pos (cdr comp)) (completion (car comp)) (completed (not (eq t (compare-strings completion nil nil string nil nil t)))) (unchanged (eq t (compare-strings completion nil nil string nil nil nil)))) (if unchanged (goto-char end) (completion--replace beg end completion)) (forward-char (- comp-pos (length completion))) (if (not (or unchanged completed)) (completion--do-completion try-completion-function expect-exact) (let* ((exact (test-completion completion minibuffer-completion-table minibuffer-completion-predicate)) (threshold (completion--cycle-threshold md)) (comps (if (and threshold (or ... ...)) (progn (completion-all-sorted-completions))))) (completion--flush-all-sorted-completions) (cond ((and (consp (cdr comps)) (not (condition-case nil ... ...))) (setq completed t exact t) (completion--cache-all-sorted-completions comps) (minibuffer-force-complete)) (completed (minibuffer-hide-completions) (if exact (completion--done completion (if ... ... ...)))) ((not exact) (if (if (eq completion-auto-help ...) (let nil ...) (let nil completion-auto-help)) (minibuffer-completion-help) (completion--message "Next char not unique"))) (t (if (and (eq this-command last-command) completion-auto-help) (minibuffer-completion-help)) (completion--done completion (quote exact) (if expect-exact nil "Complete, but not unique")))) (minibuffer--bitset completed t exact))))
|   (cond ((null comp) (minibuffer-hide-completions) (if completion-fail-discreetly nil (ding) (completion--message "No match")) (minibuffer--bitset nil nil nil)) ((eq t comp) (minibuffer-hide-completions) (goto-char end) (completion--done string (quote finished) (if expect-exact nil "Sole completion")) (minibuffer--bitset nil nil t)) (t (let* ((comp-pos (cdr comp)) (completion (car comp)) (completed (not (eq t (compare-strings completion nil nil string nil nil t)))) (unchanged (eq t (compare-strings completion nil nil string nil nil nil)))) (if unchanged (goto-char end) (completion--replace beg end completion)) (forward-char (- comp-pos (length completion))) (if (not (or unchanged completed)) (completion--do-completion try-completion-function expect-exact) (let* ((exact (test-completion completion minibuffer-completion-table minibuffer-completion-predicate)) (threshold (completion--cycle-threshold md)) (comps (if ... ...))) (completion--flush-all-sorted-completions) (cond ((and ... ...) (setq completed t exact t) (completion--cache-all-sorted-completions comps) (minibuffer-force-complete)) (completed (minibuffer-hide-completions) (if exact ...)) ((not exact) (if ... ... ...)) (t (if ... ...) (completion--done completion ... ...))) (minibuffer--bitset completed t exact))))))
|   (let* ((beg (field-beginning)) (end (field-end)) (string (buffer-substring beg end)) (md (completion--field-metadata beg)) (comp (funcall (or try-completion-function (quote completion-try-completion)) string minibuffer-completion-table minibuffer-completion-predicate (- (point) beg) md))) (cond ((null comp) (minibuffer-hide-completions) (if completion-fail-discreetly nil (ding) (completion--message "No match")) (minibuffer--bitset nil nil nil)) ((eq t comp) (minibuffer-hide-completions) (goto-char end) (completion--done string (quote finished) (if expect-exact nil "Sole completion")) (minibuffer--bitset nil nil t)) (t (let* ((comp-pos (cdr comp)) (completion (car comp)) (completed (not (eq t ...))) (unchanged (eq t (compare-strings completion nil nil string nil nil nil)))) (if unchanged (goto-char end) (completion--replace beg end completion)) (forward-char (- comp-pos (length completion))) (if (not (or unchanged completed)) (completion--do-completion try-completion-function expect-exact) (let* ((exact ...) (threshold ...) (comps ...)) (completion--flush-all-sorted-completions) (cond (... ... ... ...) (completed ... ...) (... ...) (t ... ...)) (minibuffer--bitset completed t exact)))))))
|   completion--do-completion()
|   (let* ((#1=#:val (completion--do-completion))) (if (eq #1# (quote 0)) (let nil nil) (let nil t)))
|   (cond ((window-live-p minibuffer-scroll-window) (let ((window minibuffer-scroll-window)) (save-current-buffer (set-buffer (window-buffer window)) (if (pos-visible-in-window-p (point-max) window) (set-window-start window (point-min) nil) (scroll-other-window)) nil))) ((and completion-cycling completion-all-sorted-completions) (minibuffer-force-complete) t) (t (let* ((#1=#:val (completion--do-completion))) (if (eq #1# (quote 0)) (let nil nil) (let nil t)))))
|   minibuffer-complete()
|   call-interactively(minibuffer-complete nil nil)
|   command-execute(minibuffer-complete)
|   read-from-minibuffer("Dired (directory): " "~/today/" (keymap (keymap (32)) keymap (7 . minibuffer-keyboard-quit) (menu-bar keymap (minibuf #1="Minibuf" keymap (tab menu-item "Complete" minibuffer-complete :help "Complete as far as possible") (space menu-item "Complete Word" minibuffer-complete-word :help "Complete at most one word") (63 menu-item "List Completions" minibuffer-completion-help :help "Display all possible completions") #1#)) (27 keymap (21) (23) (118 . switch-to-completions)) (prior . switch-to-completions) (63 . minibuffer-completion-help) (32 . minibuffer-complete-word) (9 . minibuffer-complete) keymap (f9 . my-enlarge-mb) (menu-bar keymap (minibuf #1# keymap (previous menu-item "Previous History Item" previous-history-element :help "Put previous minibuffer history element in the minibuffer") (next menu-item "Next History Item" next-history-element :help "Put next minibuffer history element in the minibuffer") (isearch-backward menu-item "Isearch History Backward" isearch-backward :help "Incrementally search minibuffer history backward") (isearch-forward menu-item "Isearch History Forward" isearch-forward :help "Incrementally search minibuffer history forward") (return menu-item "Enter" exit-minibuffer :key-sequence "" :help "Terminate input and exit minibuffer") (quit menu-item "Quit" abort-recursive-edit :help "Abort input and exit minibuffer") #1#)) (10 . exit-minibuffer) (13 . exit-minibuffer) (7 . abort-recursive-edit) (C-tab . file-cache-minibuffer-complete) (9 . self-insert-command) (XF86Back . previous-history-element) (up . previous-history-element) (prior . previous-history-element) (XF86Forward . next-history-element) (down . next-history-element) (next . next-history-element) (27 keymap (114 . previous-matching-history-element) (115 . next-matching-history-element) (112 . previous-history-element) (110 . next-history-element))) nil file-name-history "~/today/" nil)
|   (let* ((minibuffer-completion-table collection) (minibuffer-completion-predicate predicate) (minibuffer-completion-confirm (if (eq require-match t) nil require-match)) (base-keymap (if require-match minibuffer-local-must-match-map minibuffer-local-completion-map)) (keymap (if (memq minibuffer-completing-file-name (quote (nil lambda))) base-keymap (make-composed-keymap minibuffer-local-filename-completion-map base-keymap))) (result (read-from-minibuffer prompt initial-input keymap nil hist def inherit-input-method))) (if (and (equal result "") def) (progn (setq result (if (consp def) (car def) def)))) result)
|   completing-read-default("Dired (directory): " read-file-name-internal file-exists-p nil "~/today/" file-name-history "~/today/" nil)
|   completing-read("Dired (directory): " read-file-name-internal file-exists-p nil "~/today/" file-name-history "~/today/")
|   (progn (add-hook (quote minibuffer-setup-hook) #:setup-hook) (completing-read prompt (quote read-file-name-internal) pred mustmatch insdef (quote file-name-history) default-filename))
|   (unwind-protect (progn (add-hook #1=(quote minibuffer-setup-hook) #2=#:setup-hook) (completing-read prompt (quote read-file-name-internal) pred mustmatch insdef (quote file-name-history) default-filename)) (remove-hook #1# #2#))
|   (let (#1=#:setup-hook) (setq #1# (function (lambda nil (remove-hook #2=(quote minibuffer-setup-hook) #1#) (funcall (function (lambda nil (setq default-directory dir) (if ... ...) (set ... ...) (set-syntax-table minibuffer-local-filename-syntax))))))) (unwind-protect (progn (add-hook #2# #1#) (completing-read prompt (quote read-file-name-internal) pred mustmatch insdef (quote file-name-history) default-filename)) (remove-hook #2# #1#)))
|   (let ((dir (file-name-as-directory (expand-file-name dir)))) (let (#1=#:setup-hook) (setq #1# (function (lambda nil (remove-hook #2=(quote minibuffer-setup-hook) #1#) (funcall (function (lambda nil ... ... ... ...)))))) (unwind-protect (progn (add-hook #2# #1#) (completing-read prompt (quote read-file-name-internal) pred mustmatch insdef (quote file-name-history) default-filename)) (remove-hook #2# #1#))))
|   (if (or (not (next-read-file-uses-dialog-p)) (file-remote-p dir)) (let ((dir (file-name-as-directory (expand-file-name dir)))) (let (#1=#:setup-hook) (setq #1# (function (lambda nil (remove-hook #2=(quote minibuffer-setup-hook) #1#) (funcall (function ...))))) (unwind-protect (progn (add-hook #2# #1#) (completing-read prompt (quote read-file-name-internal) pred mustmatch insdef (quote file-name-history) default-filename)) (remove-hook #2# #1#)))) (let ((file (file-name-nondirectory dir)) (dialog-mustmatch (not (memq mustmatch (quote (nil confirm confirm-after-completion)))))) (if (and (not default-filename) (not (zerop (length file)))) (progn (setq default-filename file) (setq dir (file-name-directory dir)))) (if default-filename (progn (setq default-filename (expand-file-name (if (consp default-filename) (car default-filename) default-filename) dir)))) (setq add-to-history t) (x-file-dialog prompt dir default-filename dialog-mustmatch (eq predicate (quote file-directory-p)))))
|   (let* ((val (if (or (not (next-read-file-uses-dialog-p)) (file-remote-p dir)) (let ((dir (file-name-as-directory ...))) (let (#1=#:setup-hook) (setq #1# (function ...)) (unwind-protect (progn ... ...) (remove-hook #2=... #1#)))) (let ((file (file-name-nondirectory dir)) (dialog-mustmatch (not ...))) (if (and (not default-filename) (not ...)) (progn (setq default-filename file) (setq dir ...))) (if default-filename (progn (setq default-filename ...))) (setq add-to-history t) (x-file-dialog prompt dir default-filename dialog-mustmatch (eq predicate (quote file-directory-p)))))) (replace-in-history (eq (car-safe file-name-history) val))) (if (consp default-filename) (progn (setq default-filename (car default-filename)))) (if (eq val default-filename) (progn (if (not replace-in-history) (setq add-to-history t)) (setq val ""))) (if val nil (error "No file name specified")) (if (and default-filename (string-equal val (if (consp insdef) (car insdef) insdef))) (setq val default-filename)) (setq val (substitute-in-file-name val)) (if replace-in-history (let ((val1 (minibuffer--double-dollars val))) (if history-delete-duplicates (setcdr file-name-history (delete val1 (cdr file-name-history)))) (if (string= val1 (cadr file-name-history)) (car (prog1 file-name-history (setq file-name-history (cdr file-name-history)))) (setcar file-name-history val1))) (if add-to-history (let ((val1 (minibuffer--double-dollars val))) (if (and (consp file-name-history) (equal (car file-name-history) val1)) nil (setq file-name-history (cons val1 (if history-delete-duplicates ... file-name-history))))))) val)
|   (let ((completion-ignore-case read-file-name-completion-ignore-case) (minibuffer-completing-file-name t) (pred (or predicate (quote file-exists-p))) (add-to-history nil)) (let* ((val (if (or (not (next-read-file-uses-dialog-p)) (file-remote-p dir)) (let ((dir ...)) (let (#1=#:setup-hook) (setq #1# ...) (unwind-protect ... ...))) (let ((file ...) (dialog-mustmatch ...)) (if (and ... ...) (progn ... ...)) (if default-filename (progn ...)) (setq add-to-history t) (x-file-dialog prompt dir default-filename dialog-mustmatch (eq predicate ...))))) (replace-in-history (eq (car-safe file-name-history) val))) (if (consp default-filename) (progn (setq default-filename (car default-filename)))) (if (eq val default-filename) (progn (if (not replace-in-history) (setq add-to-history t)) (setq val ""))) (if val nil (error "No file name specified")) (if (and default-filename (string-equal val (if (consp insdef) (car insdef) insdef))) (setq val default-filename)) (setq val (substitute-in-file-name val)) (if replace-in-history (let ((val1 (minibuffer--double-dollars val))) (if history-delete-duplicates (setcdr file-name-history (delete val1 (cdr file-name-history)))) (if (string= val1 (cadr file-name-history)) (car (prog1 file-name-history (setq file-name-history ...))) (setcar file-name-history val1))) (if add-to-history (let ((val1 (minibuffer--double-dollars val))) (if (and (consp file-name-history) (equal ... val1)) nil (setq file-name-history (cons val1 ...)))))) val))
|   (let ((insdef (cond ((and insert-default-directory (stringp dir)) (if initial (cons (minibuffer--double-dollars ...) (length ...)) (minibuffer--double-dollars dir))) (initial (cons (minibuffer--double-dollars initial) 0))))) (let ((completion-ignore-case read-file-name-completion-ignore-case) (minibuffer-completing-file-name t) (pred (or predicate (quote file-exists-p))) (add-to-history nil)) (let* ((val (if (or (not ...) (file-remote-p dir)) (let (...) (let ... ... ...)) (let (... ...) (if ... ...) (if default-filename ...) (setq add-to-history t) (x-file-dialog prompt dir default-filename dialog-mustmatch ...)))) (replace-in-history (eq (car-safe file-name-history) val))) (if (consp default-filename) (progn (setq default-filename (car default-filename)))) (if (eq val default-filename) (progn (if (not replace-in-history) (setq add-to-history t)) (setq val ""))) (if val nil (error "No file name specified")) (if (and default-filename (string-equal val (if (consp insdef) (car insdef) insdef))) (setq val default-filename)) (setq val (substitute-in-file-name val)) (if replace-in-history (let ((val1 (minibuffer--double-dollars val))) (if history-delete-duplicates (setcdr file-name-history (delete val1 ...))) (if (string= val1 (cadr file-name-history)) (car (prog1 file-name-history ...)) (setcar file-name-history val1))) (if add-to-history (let ((val1 ...)) (if (and ... ...) nil (setq file-name-history ...))))) val)))
|   read-file-name-default("Dired (directory): " nil "/home/micha/today/" nil nil nil)
|   funcall(read-file-name-default "Dired (directory): " nil "/home/micha/today/" nil nil nil)
|   read-file-name("Dired (directory): " nil "/home/micha/today/" nil)
|   dired-read-dir-and-switches("")
|   call-interactively(dired nil nil)
|   command-execute(dired)
`----------------------------------------------------------------------

I can reproduce this behavior manually:

(completion--twq-all
 #1="~/Download"
 #1# '(#("download/" 0 8 (face (completions-common-part)) 8 9 (face (completions-first-difference)))
       #("Downloads/" 0 8 (face (completions-common-part)) 8 9 (face (completions-first-difference))))
 2 'substitute-in-file-name 'completion--sifn-requote)

==>

(#("Download/" 0 8
   (face completions-common-part)
   8 9
   (face
    (completions-first-difference)))
 #("Downloads/" 0 8
   (face completions-common-part)
   8 9
   (face
    (completions-first-difference)))
 . 2)

I can't dig further 'cause I don't know what `completion--twq-all' is or
should do.

HTH,

Michael.