Subject: bug#48356: 28.0.50; choose-completion discards the suffix after the completion boundary

From: Dmitry Gutov <[email-masked]>
Date: Wed, 10 Apr 2024 04:33:44 +0300

Hi Stefan,

On 09/04/2024 02:50, Stefan Monnier wrote:
>> ...which translates to "/" because of the double slash -- the filesystem
>>   root directory (*). But that's the same data which would be used by any
>>   other proposed solution, too.
> 
> More or less, tho the "ideal" solution is to do that in the
> completion-style code, which has a bit more knowledge about it.

Doing it in completion-style, though, would either require a relatively 
awkward change in most/all styles (e.g. the "new dynamic variable" 
route), or a more straightforward change in styles together with an 
incompatible change in completion-all-completions.

So on balance, would you say it's a good idea to a) use this approach in 
minibuffer-completion-help, b) create a named function for it, for other 
callers to take advantage of it as well?

IIUC Vertico (and other minibuffer completion UIs) might be interested.

>>   So maybe it should be either be fixed in the
>>   completion table (avoid adding trailing slash when the last boundary is
>>   already followed by slash?), or the insertion code should do some
>>   additional post-processing of the completion string.
> 
> I think you can fix it in the same ad-hoc way we use elsewhere: compare
> the first char after the boundary with the last char of the completion
> and drop one of the two if they're the same.

Looks like completion--merge-suffix is the helper to use.

>> +             (base-suffix (let ((suffix (buffer-substring (point) end)))
>> +                            (substring
>> +                             suffix
>> +                             (cdr (completion-boundaries string
>> +                                                         minibuffer-completion-table
>> +                                                         minibuffer-completion-predicate
>> +                                                         suffix)))))
> 
> I think you want to be careful to pass (buffer-substring start (point))
> rather than `string` to `completion-boundaries`.

Thanks, see the update attached.

> In theory this approach can "do the wrong thing" with some completion
> styles, but AFAIK they haven't been written yet.  🙂

So you figure that such theoretical style would return adjusted 
base-suffix in -all-completions method, not just in -try-completion?

diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 41b20174be1..0cccc46448b 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -2586,16 +2586,13 @@ minibuffer-completion-help
              (minibuffer-completion-base (substring string 0 base-size))
              (base-prefix (buffer-substring (minibuffer--completion-prompt-end)
                                             (+ start base-size)))
-             (base-suffix
-              (if (or (eq (alist-get 'category (cdr md)) 'file)
-                      completion-in-region-mode-predicate)
-                  (buffer-substring
-                   (save-excursion
-                     (if completion-in-region-mode-predicate
-                         (point)
-                       (or (search-forward "/" nil t) (point-max))))
-                   (point-max))
-                ""))
+             (base-suffix (let ((suffix (buffer-substring (point) end)))
+                            (substring
+                             suffix
+                             (cdr (completion-boundaries (buffer-substring start (point))
+                                                         minibuffer-completion-table
+                                                         minibuffer-completion-predicate
+                                                         suffix)))))
              (all-md (completion--metadata (buffer-substring-no-properties
                                             start (point))
                                            base-size md
@@ -2697,7 +2694,11 @@ minibuffer-completion-help
                                        (delete-minibuffer-contents)
                                        (insert start choice)
                                        ;; Keep point after completion before suffix
-                                       (save-excursion (insert end)))
+                                       (save-excursion (insert
+                                                        (completion--merge-suffix
+                                                         choice
+                                                         (1- (length choice))
+                                                         end))))
                                    (unless (or (zerop (length prefix))
                                                (equal prefix
                                                       (buffer-substring-no-properties