;;; auto-capitalize-tex.el --- TeX plugin for auto-capitalize.el  -*- lexical-binding: t; -*-

;; Copyright   2026 Abdulnafé Toulaïmat

;; Author: Abdulnafé Toulaïmat <abdulnafe.toulaimat@gmail.com>
;; Assisted-by: OpenCode:Big_Pickle

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; This plugin adds TeX support to `auto-capitalize'. It requires `AUCTeX', and
;; it will not work with the builtin `tex-mode'.

;;; Code:

(require 'auto-capitalize)

(declare-function texmathp "ext:texmathp")
(declare-function TeX-current-macro "ext:tex")
(declare-function TeX-escaped-p "ext:tex")
(declare-function TeX-find-macro-start "ext:tex")

(defgroup auto-capitalize-tex nil
  "TeX support for auto-capitalize."
  :group 'auto-capitalize)

(defvar auto-capitalize-tex--lighter "/TeX"
  "Appended to `auto-capitalize--lighter' by `auto-capitalize-tex-mode'.")

(defcustom auto-capitalize-tex-macro-whitelist
  '("intertext" "text" "textbf" "textit" "textsl" "textsc" "textrm" "textsf" "texttt"
    "textup" "textmd" "emph" "underline" "textnormal"
    "title" "author" "date" "thanks" "caption"
    "textsuperscript" "textsubscript"
    ;; beamer
    "frametitle" "framesubtitle" "institute" "subtitle"
    ;; soul
    "ul" "st" "hl" "caps" "so"
    ;; ulem
    "uline" "uuline" "uwave" "sout" "xout" "dashuline" "dotuline"
    ;; xcolor
    "textcolor" "colorbox" "fcolorbox")

  "List of TeX macros whose first argument should have its first word capitalized.
Only macros taking plain text as an argument should be included. Macros
matching `outline-regexp' (like \\section) need not be listed, as they
are already handled by the `outline-regexp' check in
`auto-capitalize-default-trigger-function'."
  :type '(repeat (string :tag "Macro name")))

(defun auto-capitalize-tex-blocking-function (_text-start word-start)
  "Block capitalization in TeX when appropriate.

WORD-START is the position of the start of the current word; TEXT-START
is ignored.

This predicate blocks capitalization in `TeX-mode' buffers inside of
math envs. It also prevents capitalization of TeX macros.

This predicate is added to `auto-capitalize-blocking-functions' when
`auto-capitalize-tex-mode' is enabled."
  (save-excursion
    (goto-char word-start)
    (or
     (TeX-escaped-p)   ; Macros themselves should never be capitalized
     (texmathp)
     (equal (TeX-current-macro) "documentclass"))))

(defun auto-capitalize-tex-trigger-function (_text-start word-start)
  "Return non-nil if capitalization should occur at WORD-START.

TEXT-START is ignored; the check uses WORD-START and the buffer content
before it. Specifically, this function returns non-nil if WORD-START
follows the opening brace of a whitelisted TeX macro, i.e. one that's a
member of `auto-capitalize-tex-macro-whitelist', AND the macro itself
sits at a standard capitalization boundary (paragraph start, sentence
start, etc.).

That last check is performed by moving point to the `\\' before the
macro, then calling `auto-capitalize-default-trigger-function'.

This function is added to `auto-capitalize-trigger-functions' when
`auto-capitalize-tex-mode' is enabled."
  (when-let* ((macro (TeX-current-macro))
              (whitelisted-p (member macro auto-capitalize-tex-macro-whitelist))
              (macro-start
               (save-excursion
                 (goto-char word-start)
                 (skip-syntax-backward " ")
                 (when (and (eq (char-before) ?{)
                            (not (TeX-escaped-p (1- (point)))))
                   (TeX-find-macro-start)))))
    (save-excursion
      (goto-char macro-start)
      (auto-capitalize-default-trigger-function
       (point) (1+ (point))))))

;;;###autoload
(define-minor-mode auto-capitalize-tex-mode
  "Toggle TeX-specific capitalization support in this buffer.

When enabled, this mode adds TeX-specific blocking and trigger functions
to `auto-capitalize-blocking-functions' and
`auto-capitalize-trigger-functions' buffer-locally, namely
`auto-capitalize-tex-blocking-function' and
`auto-capitalize-tex-trigger-function'.

Note that this mode requires `AUCTeX'.

If `auto-capitalize-mode' is not yet enabled in this buffer, it
will be enabled automatically."
  :lighter nil
  (cond
   ((not auto-capitalize-tex-mode)
    (remove-hook 'auto-capitalize-blocking-functions
                 #'auto-capitalize-tex-blocking-function t)
    (remove-hook 'auto-capitalize-trigger-functions
                 #'auto-capitalize-tex-trigger-function t)
    (setq-local auto-capitalize--lighter
                (string-replace
                 auto-capitalize-tex--lighter
                 ""
                 auto-capitalize--lighter)))

   (t
    (unless (bound-and-true-p TeX-mode-p)
      (auto-capitalize-tex-mode -1)
      (user-error "Auto-capitalize-tex-mode requires AUCTeX's `TeX-mode'"))

    (unless auto-capitalize-mode
      (auto-capitalize-mode 1)
      (message "auto-capitalize-mode enabled for TeX support."))

    (add-hook 'auto-capitalize-blocking-functions
              #'auto-capitalize-tex-blocking-function nil t)
    (add-hook 'auto-capitalize-trigger-functions
              #'auto-capitalize-tex-trigger-function nil t)

    (setq-local auto-capitalize--lighter
                (concat auto-capitalize--lighter
                        auto-capitalize-tex--lighter)))))

(provide 'auto-capitalize-tex)
;;; auto-capitalize-tex.el ends here
