Subject: [PATCH] Treat a completion boundary change as completion

From: Spencer Baugh <[email-masked]>
Date: Thu, 14 Aug 2025 15:23:10 -0400

Tags: patch

In completion--do-completion, check if completion-try-completion
moved point out of the old completion boundaries.  If that
happened, then we did non-trivial completion even if the string
is otherwise unchanged.

For example,
~/src/emacs/trunk/lisp|/progmodes/project.el
hitting TAB moves us to:
~/src/emacs/trunk/lisp/|progmodes/project.el
then hitting TAB again moves us to
~/src/emacs/trunk/lisp/progmodes/|project.el

Both of these completions are successful, but we previously ran
code for completion failure (the t branch of the cond in
completion--do-completion) in the second case.  In particular,
we would always run minibuffer-completion-help, ignoring the
specific value of completion-auto-help which controls whether or
not to run minibuffer-completion-help.  Now we correctly run
code for successful completion for both cases.

We also always have checked that we're in the same boundaries
before doing completion cycling; that check is now more
accurate.

In GNU Emacs 30.1.90 (build 4, x86_64-pc-linux-gnu, X toolkit, cairo
 version 1.15.12, Xaw scroll bars) of 2025-08-14 built on
 igm-qws-u22796a
Repository revision: c7ee7707212c0b7c2892d2d9da039375f624799c
Repository branch: HEAD
Windowing system distributor 'The X.Org Foundation', version 11.0.12011000
System Description: Rocky Linux 8.10 (Green Obsidian)

Configured using:
 'configure --with-x-toolkit=lucid --without-gpm --without-gconf
 --without-selinux --without-imagemagick --with-modules --with-gif=no
 --with-cairo --with-rsvg --without-compress-install --with-tree-sitter
 --with-native-compilation=aot
 PKG_CONFIG_PATH=/usr/local/home/garnish/libtree-sitter/0.22.6-1/lib/pkgconfig/'

>From 79c0a93a8a03a7a8d8bcda4c5441731a2b02eccf Mon Sep 17 00:00:00 2001
From: Spencer Baugh <[email-masked]>
Date: Thu, 14 Aug 2025 15:15:29 -0400
Subject: [PATCH] Treat a completion boundary change as completion

In completion--do-completion, check if completion-try-completion
moved point out of the old completion boundaries.  If that
happened, then we did non-trivial completion even if the string
is otherwise unchanged.

For example,
~/src/emacs/trunk/lisp|/progmodes/project.el
hitting TAB moves us to:
~/src/emacs/trunk/lisp/|progmodes/project.el
then hitting TAB again moves us to
~/src/emacs/trunk/lisp/progmodes/|project.el

Both of these completions are successful, but we previously ran
code for completion failure (the t branch of the cond in
completion--do-completion) in the second case.  In particular,
we would always run minibuffer-completion-help, ignoring the
specific value of completion-auto-help which controls whether or
not to run minibuffer-completion-help.  Now we correctly run
code for successful completion for both cases.

We also always have checked that we're in the same boundaries
before doing completion cycling; that check is now more
accurate.

* lisp/minibuffer.el (completion--in-boundaries-p): Add.
(completion--do-completion): Check completion--in-boundaries-p.
---
 lisp/minibuffer.el | 40 ++++++++++++++++++++++++++--------------
 1 file changed, 26 insertions(+), 14 deletions(-)

diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 956b7e6235d..689cda7edab 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -1519,6 +1519,15 @@ completion--message
   (if completion-show-inline-help
       (minibuffer-message msg)))
 
+(defun completion--in-boundaries-p (string collection pred suffix pos)
+  "Return non-nil if POS is in the current completion boundaries.
+
+Calls `completion-boundaries' with STRING, COLLECTION, PRED, SUFFIX."
+  (let* ((boundaries (completion-boundaries string collection pred suffix))
+         (start (car boundaries))
+         (end (+ (length string) (cdr boundaries))))
+    (>= start pos end)))
+
 (defun completion--do-completion (beg end &optional
                                       try-completion-function expect-exact)
   "Do the completion and return a summary of what happened.
@@ -1540,13 +1549,14 @@ completion--do-completion
 EXPECT-EXACT, if non-nil, means that there is no need to tell the user
 when the buffer's text is already an exact match."
   (let* ((string (buffer-substring beg end))
+         (pos (- (point) beg))
          (md (completion--field-metadata beg))
          (comp (funcall (or try-completion-function
                             #'completion-try-completion)
                         string
                         minibuffer-completion-table
                         minibuffer-completion-predicate
-                        (- (point) beg)
+                        pos
                         md)))
     (cond
      ((null comp)
@@ -1568,7 +1578,16 @@ completion--do-completion
       (let* ((comp-pos (cdr comp))
              (completion (car comp))
              (completed (not (string-equal-ignore-case completion string)))
-             (unchanged (string-equal completion string)))
+             (unchanged (string-equal completion string))
+             (only-changed-boundaries
+              (and (not completed)
+                   (/= comp-pos pos)
+                   (not (completion--in-boundaries-p
+                         (substring string 0 pos)
+                         minibuffer-completion-table
+                         minibuffer-completion-predicate
+                         (substring string pos)
+                         comp-pos)))))
         (if unchanged
 	    (goto-char end)
           ;; Insert in minibuffer the chars we got.
@@ -1597,15 +1616,8 @@ completion--do-completion
                   ;; try-completion and all-completions, for things
                   ;; like completion-ignored-extensions.
                   (when (and threshold
-                             ;; Check that the completion didn't make
-                             ;; us jump to a different boundary.
-                             (or (not completed)
-                                 (< (car (completion-boundaries
-                                          (substring completion 0 comp-pos)
-                                          minibuffer-completion-table
-                                          minibuffer-completion-predicate
-                                         ""))
-                                   comp-pos)))
+                             (not completed)
+                             (not only-changed-boundaries))
                    (completion-all-sorted-completions beg end))))
             (completion--flush-all-sorted-completions)
             (cond
@@ -1619,7 +1631,7 @@ completion--do-completion
               (setq completed t exact t)
               (completion--cache-all-sorted-completions beg end comps)
               (minibuffer-force-complete beg end))
-             (completed
+             ((or completed only-changed-boundaries)
               (cond
                ((pcase completion-auto-help
                   ('visible (get-buffer-window "*Completions*" 0))
@@ -1643,8 +1655,8 @@ completion--do-completion
              ;; means we've already given a "Complete, but not unique" message
              ;; and the user's hit TAB again, so now we give him help.
              (t
-              (if (and (eq this-command last-command) completion-auto-help)
-                  (minibuffer-completion-help beg end))
+              (when (and (eq this-command last-command) completion-auto-help)
+                (minibuffer-completion-help beg end))
               (completion--done completion 'exact
                                 (unless (or expect-exact
                                             (and completion-auto-select
-- 
2.39.3