Subject: bug#70217: [PATCH] Add substring-partial-completion style

From: Spencer Baugh <[email-masked]>
Date: Thu, 16 May 2024 16:26:32 -0400

Stefan Monnier <[email-masked]> writes:

>> * lisp/minibuffer.el (completion-emacs22-use-pcm)
>> (completion-substring-use-pcm): Add. (bug#70217)
>> (completion-emacs22-try-completion)
>> (completion-emacs22-all-completions): Check completion-emacs22-use-pcm.
>> (completion-pcm--string->pattern, completion-pcm--find-all-completions)
>> (completion-pcm-all-completions, completion-pcm--merge-try)
>> (completion-pcm-try-completion): Add "startglob" optional argument and
>> pass through.
>> (completion-substring-try-completion)
>> (completion-substring-all-completions): Check
>> completion-substring-use-pcm and pass startglob=t.
>
> I'm not super happy about this `startglob` everywhere.
> Two things bother me about it:
>
> - Its name and doc (in my view of what "glob" means, there's no such
>   thing as a "leading glob"; I think you're talking about a leading
>   wildcard (which is one of the things that can appear in a glob
>   pattern)).

Yes, very true, changed to "anchored".

> - Its spreading all over the place.

In the attached diff it's a dynamic variable completion-pcm-anchored
checked in one place.  How's that?

> I don't have the time to dig into the second problem (which might be
> fixable by combining this info into the `string` argument (which
> wouldn't be just a string as more, so it could have too-far reaching
> consequences)), but for the first I suspect a "standard" name for this
> idea is `anchored`.
>
> Other than that, I don't have a strong opinion on whether to introduce
> the feature via new styles or as custom vars that affect
> existing styles.  Either way works for me.

As part of making it a dynamic variable, I also changed the usage.  In
the attached diff, an element in completion-styles can contain
additional dynamic variable bindings:

(setq completion-styles '(basic (partial-completion (completion-pcm-anchored nil)) emacs22))

This is powerful and elegant in some ways - for example now a user could
use completion-ignore-case only for an individual style.  Also, the
overall diff is now much shorter.

But it may be a bit too powerful.

Also, now completion uses cl-progv, which internally uses eval, which
might be undesirable.  Maybe that could be solved with a C
implementation of cl-progv?  I think a C implementation of cl-progv
might also be useful for my native modules, so I'd be happy to implement
that if it seems like a good idea.

Also, it's somewhat hard to expose this new power via customize (I
haven't bothered to update the customize type for it yet in this diff)

What do you think?

diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 5ce5aab5c7e..7110e7f573d 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -1218,11 +1218,18 @@ completion--nth-completion
          (result-and-style
           (completion--some
            (lambda (style)
-             (let ((probe (funcall
-                           (or (nth n (assq style completion-styles-alist))
-                               (error "Invalid completion style %s" style))
-                           string table pred point)))
-               (and probe (cons probe style))))
+             (let (symbols values)
+               (when (consp style)
+                 (dolist (binding (cdr style))
+                   (push (car binding) symbols)
+                   (push (cadr binding) values))
+                 (setq style (car style)))
+               (cl-progv symbols values
+                 (let ((probe (funcall
+                               (or (nth n (assq style completion-styles-alist))
+                                   (error "Invalid completion style %s" style))
+                               string table pred point)))
+                   (and probe (cons probe style))))))
            (completion--styles md)))
          (adjust-fn (get (cdr result-and-style) 'completion--adjust-metadata)))
     (when (and adjust-fn metadata)
@@ -3780,6 +3787,17 @@ completion-pcm--pattern-trivial-p
 	     (setq trivial nil)))
 	 trivial)))
 
+(defcustom completion-pcm-anchored t
+  "If nil, the partial-completion style expands at the start of a string.
+
+If non-nil, then expansion at the start of a string only happens
+if the string begins with a wildcard.
+
+For example, if this is nil then \"b/c\" will match
+\"aaa/bbb/ccc\"."
+  :version "30.1"
+  :type 'boolean)
+
 (defun completion-pcm--string->pattern (string &optional point)
   "Split STRING into a pattern.
 A pattern is a list where each element is either a string
@@ -3830,7 +3848,12 @@ completion-pcm--string->pattern
       (when (> (length string) p0)
         (if pending (push pending pattern))
         (push (substring string p0) pattern))
-      (nreverse pattern))))
+      (setq pattern (nreverse pattern))
+      (unless completion-pcm-anchored
+        (when (stringp (car pattern))
+          (push 'prefix pattern)))
+      pattern)))
 
 (defun completion-pcm--optimize-pattern (p)
   ;; Remove empty strings in a separate phase since otherwise a ""