unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: Juri Linkov <juri@linkov.net>
To: Stefan Monnier <monnier@IRO.UMontreal.CA>
Cc: 49931@debbugs.gnu.org
Subject: bug#49931: 28.0.50; `choose-completion' submits incorrect string when minibuffer content changes after creation of the *Completions* buffer
Date: Sat, 12 Mar 2022 20:49:58 +0200	[thread overview]
Message-ID: <86ilsj574h.fsf@mail.linkov.net> (raw)
In-Reply-To: <874kbydr7m.fsf@mail.linkov.net> (Juri Linkov's message of "Tue,  10 Aug 2021 10:07:25 +0300")

[-- Attachment #1: Type: text/plain, Size: 2521 bytes --]

[This is continuation of the unfinished fix from bug#34517]

>> Stefan, please advise shouldn't selecting a completion from the
>> *Completions* buffer clear the minibuffer's content before
>> inserting the selected completion?
>
> No, for example when you complete file name "C-x C-f ~/.e TAB"  the
> *Completions* buffer will only show ".emacs" so we should clear the
> minibuffer before inserting ".emacs" because that would lose the leading
> "~/".  There are other circumstances where trailing text needs to
> be preserved.

Another example that occurred to me is shell-command M-! minibuffer
where TAB can complete on commands and file names.

> The completion code handles this with `completion-base-position` which
> holds the beginning and end of the text that should be replaced when you
> choose an item in *Completions*.
>
>>> 0. emacs -Q
>>> 1. ‘C-h f TAB’ displays a list of completions
>>> 2. type a nonexistent function name, i.e. some random text
>>>    in the minibuffer, e.g. “blabla”
>
> The *Completions* content is now "out of date" compared to the minibuffer.
>
>>> 3. click on an existing valid completion in the *Completions* buffer,
>>>    e.g. on “append”
>
> completion-base-position was set at step (1) to cover the empty text
> after the prompt, so this empty text (which is now right in front of
> "blabla") is replaced with "append" resulting in "appendblabla".
>
> Obviously, the result is not what we want.
> Now sure how to change which part, tho.  Maybe instead of
> completion-base-position we should store the prefix and suffix strings,
> so when you select an entry from *Completions* we just clear the
> minibuffer and replace it with (concat prefix selection suffix)?

Now I tried this, and it works correctly.  But not sure how to make
this change as backward-compatible as possible.  One variant
would be to save '("prefix" "suffix") instead of '(10 11)
in 'completion-base-position' but this might fail in some existing code.

So maybe better to add a new variable 'completion-base-affixes'.
Then whether to use 'completion-base-position'
or 'completion-base-affixes' could be defined by the new user option
'completion-use-base-affixes'.

Then it can be used in the new command from emacs-devel:

```
(defun minibuffer-completion-choose (&optional arg)
  "Run `choose-completion' from the minibuffer in its scrolling window."
  (interactive "P")
  (with-minibuffer-scroll-window
    (let ((completion-use-base-affixes t))
      (choose-completion nil arg))))
```


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: completion-base-affixes.patch --]
[-- Type: text/x-diff, Size: 4985 bytes --]

diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 0262424b87..4cc050ff86 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -2290,6 +2290,8 @@ minibuffer-completion-help
   (let* ((start (or start (minibuffer--completion-prompt-end)))
          (end (or end (point-max)))
          (string (buffer-substring start end))
+         (base-prefix (buffer-substring (minibuffer--completion-prompt-end) start))
+         (base-suffix (buffer-substring end (point-max)))
          (md (completion--field-metadata start))
          (completions (completion-all-completions
                        string
@@ -2405,11 +2407,16 @@ minibuffer-completion-help
                                    ;; completion-all-completions does not give us the
                                    ;; necessary information.
                                    end))
+                        (setq-local completion-base-affixes (list base-prefix base-suffix))
                         (setq-local completion-list-insert-choice-function
                              (let ((ctable minibuffer-completion-table)
                                    (cpred minibuffer-completion-predicate)
                                    (cprops completion-extra-properties))
                                (lambda (start end choice)
+                                 (if (and (stringp start) (stringp end))
+                                     (progn
+                                       (delete-region (minibuffer-prompt-end) (point-max))
+                                       (insert start choice end))
                                  (unless (or (zerop (length prefix))
                                              (equal prefix
                                                     (buffer-substring-no-properties
@@ -2418,7 +2425,7 @@ minibuffer-completion-help
                                                      start)))
                                    (message "*Completions* out of date"))
                                  ;; FIXME: Use `md' to do quoting&terminator here.
-                                 (completion--replace start end choice)
+                                 (completion--replace start end choice))
                                  (let* ((minibuffer-completion-table ctable)
                                         (minibuffer-completion-predicate cpred)
                                         (completion-extra-properties cprops)
diff --git a/lisp/simple.el b/lisp/simple.el
index 9601e6cd76..bf9d8c8830 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -9072,6 +9072,19 @@ completion-base-position
 where the completion should be inserted and END (if non-nil) is the end
 of the text to replace.  If END is nil, point is used instead.")
 
+(defvar completion-base-affixes nil
+  "Base context of the text corresponding to the shown completions.
+This variable is used in the *Completions* buffers.
+Its value is a list of the form (PREFIX SUFFIX) where PREFIX is the text
+before the place where completion should be inserted and SUFFIX is the text
+after the completion.")
+
+(defcustom completion-use-base-affixes nil
+  "Non-nil means to restore original prefix and suffix in the minibuffer."
+  :type 'boolean
+  :version "29.1"
+  :group 'completion)
+
 (defvar completion-list-insert-choice-function #'completion--replace
   "Function to use to insert the text chosen in *Completions*.
 Called with three arguments (BEG END TEXT), it should replace the text
@@ -9164,6 +9181,7 @@ choose-completion
   (with-current-buffer (window-buffer (posn-window (event-start event)))
     (let ((buffer completion-reference-buffer)
           (base-position completion-base-position)
+          (base-affixes completion-base-affixes)
           (insert-function completion-list-insert-choice-function)
          (completion-no-auto-exit (if arg t completion-no-auto-exit))
           (choice
@@ -9184,7 +9202,8 @@ choose-completion
       (with-current-buffer buffer
         (choose-completion-string
          choice buffer
-         (or base-position
+         (or (and completion-use-base-affixes base-affixes)
+             base-position
              ;; If all else fails, just guess.
              (list (choose-completion-guess-base-position choice)))
          insert-function)))))
@@ -9357,9 +9377,11 @@ completion-setup-function
                 (buffer-substring (minibuffer-prompt-end) (point)))))))
     (with-current-buffer standard-output
       (let ((base-position completion-base-position)
+            (base-affixes completion-base-affixes)
             (insert-fun completion-list-insert-choice-function))
         (completion-list-mode)
         (setq-local completion-base-position base-position)
+        (setq-local completion-base-affixes base-affixes)
         (setq-local completion-list-insert-choice-function insert-fun))
       (setq-local completion-reference-buffer mainbuf)
       (if base-dir (setq default-directory base-dir))

  parent reply	other threads:[~2022-03-12 18:49 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-08-07 17:20 bug#49931: 28.0.50; `choose-completion' submits incorrect string when minibuffer content changes after creation of the *Completions* buffer Visuwesh via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-08-08  5:14 ` Eli Zaretskii
2021-08-08  7:02   ` Visuwesh via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-08-08  7:14     ` Eli Zaretskii
2021-08-08  7:31       ` Visuwesh via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-08-08  8:19         ` Eli Zaretskii
2021-08-10  7:07 ` Juri Linkov
2021-08-10  9:00   ` Visuwesh via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-03-12 18:49   ` Juri Linkov [this message]
2022-04-05 18:54     ` Juri Linkov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=86ilsj574h.fsf@mail.linkov.net \
    --to=juri@linkov.net \
    --cc=49931@debbugs.gnu.org \
    --cc=monnier@IRO.UMontreal.CA \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).