On Tue, Nov 4, 2025 at 11:20 PM Hongyi Zhao <[email protected]> wrote:
>
> 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))
> ```

Here is the updated version of this file, just FYI. In this version, I
enhanced the cape package's configuration to solve the problem
discussed here.

```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))

(use-package cape
  :bind ("C-c p" . cape-prefix-map)
  :init
   (add-hook 'completion-at-point-functions #'cape-dabbrev)
   (add-hook 'completion-at-point-functions #'cape-file)
   ;; https://lists.gnu.org/archive/html/auctex/2025-11/msg00008.html
  :custom
  (cape-file-prefix '("file:" "./" "../" "~/"))
  )

;; 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