On Mon, Nov 3, 2025 at 3:21 PM Hongyi Zhao <[email protected]> wrote:
>
> On Mon, Nov 3, 2025 at 4:06 AM Arash Esbati <[email protected]> wrote:
> >
> > Hongyi Zhao <[email protected]> writes:
> >
> > > First, I'd like to express my immense appreciation for your incredible
> > > work on AUCTeX. It is an indispensable tool.
> >
> > Thanks for your kind words.
> >
> > > **The Problem:**
> > > In my setup, the standard automatic completion chain
> > > (`completion-at-point-functions`) is unreliable for file paths inside
> > > commands like `\include{./}` or `\addbibresource{./}`.
> >
> > What is in this case unreliable?
> >
> > > My extensive debugging suggests this is due to a "short-circuiting" 
> > > effect:
> > > 1.  Higher-priority completion backends (like `lsp-mode` or
> > > `cape-dabbrev`) are triggered first.
> > > 2.  Even if they find no relevant candidates for a prefix like `./`,
> > > they seem to return a "successful but empty" result.
> > > 3.  This "successful" result prevents the completion chain from
> > > proceeding to lower-priority, more specialized backends like
> > > `completion-file-name-table`. The file completion backend is never
> > > given a chance to run.
> >
> > You should try to assemble a minimal recipe how to reproduce this,
> > starting with "emacs -Q", and then report that as a bug to the relevant
> > developers.
> >
> > > **My Questions:**
> > >
> > > While my solution is effective, I am keen to know if it is the optimal
> > > or most idiomatic approach.
> > >
> > > 1.  Is my analysis of the "short-circuit" problem in the automatic
> > > completion chain correct?
> >
> > How can others tell without knowing your setup?  Again, the only way to
> > track this down is a reproduceable recipe, starting with "emacs -Q".
> >
> > > 2.  Is this manual, key-bound command the recommended way to solve
> > > this problem within the AUCTeX ecosystem?
> > > 3.  Does AUCTeX perhaps have a built-in command, variable, or
> > > mechanism to handle this exact scenario, which I may have missed in
> > > the documentation?
> >
> > No, AUCTeX simply adds 2 functions to `completion-at-point-functions',
> > so you're basically free to add or remove any function from it.
> >
> > > 4.  Is there a more elegant way to configure
> > > `completion-at-point-functions` itself to grant file completion higher
> > > priority in these specific command-argument contexts?
> >
> > This is my setup for the cape package:
> >
> > (use-package cape
> >   :defer t
> >   :init
> >   (add-hook 'completion-at-point-functions #'cape-file 95)
> >
> >   :custom
> >   (cape-file-prefix '("file:" "./" "../" "~/"))
> >
> >   :bind (:map corfu-map
> >               ("C-c p f" . cape-file)))
> >
> > Not sure if it is an elegant way, but it works for me.
> >
> > Best, Arash
>
> Dear Arash,
>
> Thank you once again for your invaluable guidance. Following your
> advice, I conducted the `emacs -Q` test with a minimal recipe. The
> results were both surprising and incredibly illuminating, and they
> have led me to a definitive conclusion.
>
> The test rigorously demonstrates the "short-circuiting" problem I
> described. I present the complete, step-by-step recipe below for your
> review.
>
> ---
>
> ### **Minimal Reproducible Recipe**
>
> #### **1. Prerequisites**
>
> *   A recent version of Emacs (e.g., Emacs 29+).
> *   The `texlab` LSP server installed and available in the system's `PATH`.
>
> #### **2. Minimal Init File (`~/mini-init.el`)**
>
> Save the following code as `~/mini-init.el`. This configuration sets
> up a completion chain where `lsp-mode` and `cape-dabbrev` are grouped
> in a high-priority `cape-super-capf`, with `cape-file` as a
> lower-priority fallback.
>
> ```emacs-lisp
> ;; -*- lexical-binding: t; -*-
>
> ;; Step 1: Bootstrap straight.el
> (defvar bootstrap-version)
> (let ((bootstrap-file
>        (expand-file-name "straight/repos/straight.el/bootstrap.el"
>                          (or (bound-and-true-p straight-base-dir)
>                              user-emacs-directory)))
>       (bootstrap-version 7))
>   (unless (file-exists-p bootstrap-file)
>     (with-current-buffer
>         (url-retrieve-synchronously
>          
> "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el";
>          'silent 'inhibit-cookies)
>       (goto-char (point-max))
>       (eval-print-last-sexp)))
>   (load bootstrap-file nil 'nomessage))
>
> (setq straight-use-package-by-default t
>       package-enable-at-startup nil)
>
> ;; Step 2: Configure core completion packages
> (use-package corfu
>   :init (global-corfu-mode)
>   :custom (corfu-auto t))
>
> ;; ===================================================================
> ;; Core Fix: Add :demand t to ensure cape functions are available
> ;; ===================================================================
> (use-package cape
>   :demand t)
>
> ;; Step 3: Configure AUCTeX
> (use-package auctex
>   :mode ("\\.tex\\'" . LaTeX-mode))
>
> ;; Step 4: Configure lsp-mode with the exact setup causing the issue
> (use-package lsp-mode
>   :hook (LaTeX-mode . lsp-deferred)
>   :commands (lsp lsp-deferred)
>   :config
>   ;; Helper functions
>   (defun my/completing-latex-command-p ()
>     (when-let* ((bounds (bounds-of-thing-at-point 'symbol))
>                 (start (car bounds)))
>       (and (> start (point-min))
>            (eq (char-before start) ?\\))))
>
>   (defun my/capf-unless (predicate capf)
>     (lambda ()
>       (when-let* ((fn (symbol-function capf)))
>         (unless (funcall predicate)
>           (funcall fn)))))
>
>   ;; The core advice function that creates the complex completion chain
>   (defun my/lsp-add-cape-advice (&rest _)
>     "Set up a completion chain with lsp-mode, dabbrev, and cape-file."
>     (when (and (bound-and-true-p lsp-mode)
>                (boundp 'completion-at-point-functions))
>       ;; This `(require 'cape)` is good practice, but :demand t is more robust
>       (require 'cape nil t)
>       (let ((lsp-capf (car completion-at-point-functions)))
>         (setq-local completion-at-point-functions
>                     (list
>                      (cape-capf-buster
>                       (cape-super-capf

Here, `cape-super-capf` should be written as `cape-capf-super`.

>                        lsp-capf
>                        (my/capf-unless #'my/completing-latex-command-p
> #'cape-dabbrev)))
>                      ;; Fallback for file completion
>                      #'cape-file

This one should be put at the beginning of the list to solve the race
condition with cape-dabbrev when triggering completion with possible
path prefixes.

>                      t)))))
>   (advice-add 'lsp-completion--enable :after #'my/lsp-add-cape-advice))
> ```

So, the correct version of this file is as follows, just FYI:

```emacs-lisp
;; -*- lexical-binding: t; -*-

;; Step 1: Bootstrap straight.el
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el"
                         (or (bound-and-true-p straight-base-dir)
                             user-emacs-directory)))
      (bootstrap-version 7))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el";
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(setq straight-use-package-by-default t
      package-enable-at-startup nil)

;; Step 2: Configure core completion packages
(use-package corfu
  :init (global-corfu-mode)
  :custom (corfu-auto t))

;; ===================================================================
;; Core Fix: Add :demand t to ensure cape functions are available
;; ===================================================================
(use-package cape
  :demand t)

;; Step 3: Configure AUCTeX
(use-package auctex
  :mode ("\\.tex\\'" . LaTeX-mode))

;; Step 4: Configure lsp-mode with the exact setup causing the issue
(use-package lsp-mode
  :hook (LaTeX-mode . lsp-deferred)
  :commands (lsp lsp-deferred)
  :config
  ;; Helper functions
  (defun my/completing-latex-command-p ()
    (when-let* ((bounds (bounds-of-thing-at-point 'symbol))
                (start (car bounds)))
      (and (> start (point-min))
           (eq (char-before start) ?\\))))

  (defun my/capf-unless (predicate capf)
    (lambda ()
      (when-let* ((fn (symbol-function capf)))
        (unless (funcall predicate)
          (funcall fn)))))

  ;; The core advice function that creates the complex completion chain
  (defun my/lsp-add-cape-advice (&rest _)
    "Set up a completion chain with lsp-mode, dabbrev, and cape-file."
    (when (and (bound-and-true-p lsp-mode)
               (boundp 'completion-at-point-functions))
      ;; This `(require 'cape)` is good practice, but :demand t is more robust
      (require 'cape nil t)
      (let ((lsp-capf (car completion-at-point-functions)))
        (setq-local completion-at-point-functions
                    (list
             ;; Must put in the first place to solve the race
condition problem with cape-dabbrev when triggering the completion
with possible path prefixes,
             ;; say, ./, ../, ~/, or /.
             #'cape-file
                     (cape-capf-buster
                      (cape-capf-super
                       lsp-capf
                       (my/capf-unless #'my/completing-latex-command-p
#'cape-dabbrev)))
                     t)))))
  (advice-add 'lsp-completion--enable :after #'my/lsp-add-cape-advice))
```

Regards,
Zhao

Reply via email to