Subject: bug#79026: 30.1; TAB in Minibuffer when using a quail input-method should complete the longest common suffix of candidates

From: Visuwesh <[email-masked]>
Date: Wed, 16 Jul 2025 20:39:41 +0530

[செவ்வாய் ஜூலை 15, 2025] Eli Zaretskii wrote:

> It doesn't look like quail-completion (which is the command bound to
> TAB in that input method) is supposed to work as you expect.  Instead,
> it is a fancy visual feedback for typing long sequences, fancier than
> what Emacs usually shows in the echo-area (which is just the list of
> candidates for the next character).
>
> IOW, after pressing TAB, you are supposed to:
>
>   . realize that there's just one candidate "\above"
>   . type its characters one by one, guided by the moving highlight in
>     the *Quail Completions* buffer as you go
>
> Stefan and Arash, am I right?

Looking at quail-completion, it only seems to list the possible
translations and their keys.  There is quail-choose-completion but it
seems to be broken (it inserts the same translation twice when trying it
with tamil-phonetic), and does not work at all for TeX (maybe because of
non-nil TRANSLATION-KEYS (why BTW?)).  If it works, then you can click
on the translation shown in *Quail Completions* to insert it.

I gave a shot implementing a CAPF for Quail but it does not "recontinue"
the translation process if one "rejects" the completion completely i.e.,
using C-g.

To try it out, evaluate the following in emacs -Q

    (defun vz/quail--string (def key)
      "Return the translation of KEY as specified by DEF."
      (let ((translation (quail-get-translation (car def) key (length key))))
        (if (characterp translation)
            (string translation)
          ;; When there are multiple candidates, then a vector is
          ;; returned.
          (and (consp translation)
               (append (cdr translation) nil)))))

    (defun vz/quail--candidates (key &optional map candidates)
      "Return an alist of translations and corresponding keys leading from KEY.
    The optional argument MAP is the `quail-map' for KEY, and CANDIDATES is
    an internal variable."
      (let* ((map (or map (quail-lookup-key key nil t)))
             (map (if (and (symbolp map) (functionp map))
                      (funcall map key (length key))
                    map)))
        ;; If car of MAP is non-nil, then KEY translates to car of MAP.
        (when (car map)
          (let ((translation (vz/quail--string map key)))
            (if (stringp translation)
                (push (cons translation key) candidates)
              (dolist (tr translation)
                (push (cons tr key) candidates)))))
        ;; If cdr of MAP is non-nil, then the translation continues on
        ;; forward.
        (when (cdr map)
          ;; See `quail-completion-1'.
          (dolist (rest (if (functionp (cdr map))
                            (funcall (cdr map))
                          (cdr map)))
            (setq candidates
                  (append (vz/quail--candidates (concat key (string (car rest)))
                                                (cdr rest))
                          candidates))))
        candidates))

    (defun vz/quail--capf ()
      (let ((candidates (vz/quail--candidates quail-current-key)))
        ;; For `quail-overlay', see `quail-delete-region'.
        (when (and candidates (overlay-start quail-overlay)))
        (quail-delete-region)
        (list (overlay-start quail-overlay)
              (overlay-end quail-overlay)
              (completion-table-with-metadata
               candidates
               `((annotation-function
                  . ,(lambda (string)
                       (concat "\t← " (cdr (assoc string candidates)))))))
              :exit-function
              (lambda (string status)
                (when (memq status '(finished sole))
                  ;; Setting to nil is required for sole candidate.
                  (setq quail-current-str nil)
                  (quail-terminate-translation))))))

    (defun vz/quail-complete-at-point ()
      (interactive)
      (let ((completion-at-point-functions (list #'vz/quail--capf)))
        (completion-at-point)))

    (with-temp-buffer
      ;; Load the TeX IM.
      (let (message-log-max) (activate-input-method "TeX"))
      (let ((quail-current-package (assoc "TeX" quail-package-alist)))
        (define-key (nth 5 quail-current-package)
                    (kbd "TAB")
                    #'vz/quail-complete-at-point)))

    (define-key quail-translation-keymap (kbd "TAB") #'vz/quail-complete-at-point)

and say C-u C-\ TeX RET to activate the TeX IM.  Then say \al TAB which
should pop up the *Completions* buffer from which you can select the
desired translation.  For reasons I have not explored, M-<up> and
M-<down> does not work and kills the completion.