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