Subject: Re: bug#68815: Unexpected behavior with read-file-name and functional REQUIRE-MATCH argument

From: Joseph Turner <[email-masked]>
Date: Tue, 30 Jan 2024 22:11:37 -0800

Hi Stefan!

Stefan Monnier <[email-masked]> writes:

>> emacs -Q on the tip of the emacs-29 branch (after commit 77f5d4d523a), run...
>>
>> (let ((default-directory "~/"))
>>   (read-directory-name "Clone into new or empty directory: " nil nil
>>                        (lambda (dir) (or (not (file-exists-p dir))
>>                                     (directory-empty-p dir)))))
>>
>> ...then type "/tmp/" (the whole minibuffer now reads "~//tmp") then RET.
>>
>> Expected: Completion does not exit, instead saying "[No match]".
>
> Ah, that good old problem about whether PRED should apply to the
> quoted
> or to the unquoted string 🙁
>
> I think here the bug is in `read-directory-name` (and
> `read-file-name`)
> since their PRED arg is expected to apply to the (unquoted) file name
> itself (i.e. the thing that would be returned by the function), rather
> than to the quoted representation used in the minibuffer.
>
> They should wrap PRED so as to pass the arg through
> `substitute-in-file-name` (or otherwise arrange to make sure PRED is
> called with an unquoted file name).

Thank you for the clear instructions!  Does the attached patch do the
right thing?

If so, may it be applied to emacs-29?

Joseph

>From 78c7232593cc12bdf3e772df602d75a31bc4fd2a Mon Sep 17 00:00:00 2001
From: Joseph Turner <[email-masked]>
Date: Tue, 30 Jan 2024 22:08:50 -0800
Subject: [PATCH] Pass unquoted filename to user-supplied MUSTMATCH predicate

* lisp/minibuffer.el (read-file-name-default):

Resolves bug#68815.
---
 lisp/minibuffer.el | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index faa7f543ece..1f0f3bdade3 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -3353,7 +3353,13 @@ See `read-file-name' for the meaning of the arguments."
     (let ((ignore-case read-file-name-completion-ignore-case)
           (minibuffer-completing-file-name t)
           (pred (or predicate 'file-exists-p))
-          (add-to-history nil))
+          (add-to-history nil)
+          (require-match (if (functionp mustmatch)
+                             (lambda (input)
+                               (funcall mustmatch
+                                        ;; User-supplied MUSTMATCH expects an unquoted filename
+                                        (substitute-in-file-name input)))
+                           mustmatch)))
 
       (let* ((val
               (if (or (not (next-read-file-uses-dialog-p))
@@ -3389,7 +3395,7 @@ See `read-file-name' for the meaning of the arguments."
 				   (read-file-name--defaults dir initial))))
 			  (set-syntax-table minibuffer-local-filename-syntax))
                       (completing-read prompt 'read-file-name-internal
-                                       pred mustmatch insdef
+                                       pred require-match insdef
                                        'file-name-history default-filename)))
                 ;; If DEFAULT-FILENAME not supplied and DIR contains
                 ;; a file name, split it.
-- 
2.41.0