;;; mathtools.el --- Style hook for the LaTeX package `mathtools'.  -*- lexical-binding: t; -*-

;; Copyright (C) 2011-2020, 2022 Free Software Foundation, Inc.

;; Author: Mads Jensen <mje@inducks.org>
;; Created: 2011-02-13
;; Keywords: tex

;; This file is part of AUCTeX.

;; AUCTeX 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, or (at your option)
;; any later version.

;; AUCTeX 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 AUCTeX; see the file COPYING.  If not, write to the Free
;; Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
;; 02110-1301, USA.

;;; Commentary:

;;  This file adds support for `mathtools.sty'

;;; Comments:

;;; This package serves as a wrapper for amsmath, adding more features
;;; and fixing a few bugs in amsmath.  The mathstyle argument for many
;;; of the macros is discussed at
;;; <https://www.tug.org/TUGboat/Articles/tb22-4/tb72perlS.pdf>

;;; Code:

;; Needed for auto-parsing:
(require 'tex)
(require 'latex)

;; Silence the compiler:
(declare-function font-latex-add-keywords
                  "font-latex"
                  (keywords class))

(defvar LaTeX-amsmath-package-options)

;; amsmath options which can be passed directly to mathtools are
;; appended in the style hook below
(defvar LaTeX-mathtools-package-options
  '("fixamsmath" "donotfixamsmathbugs" "allowspaces" "disallowspaces"
    ;; Update 2013: We now make \(\) and \[\] robust (can be disabled
    ;; via nonrobust package option)
    "nonrobust")
  "Package options for the mathtools package.")
(TeX-load-style "amsmath")
(dolist (elt LaTeX-amsmath-package-options)
  (add-to-list 'LaTeX-mathtools-package-options elt))

(defvar LaTeX-mathtools-key-val-options
  '(("showonlyrefs")
    ("mathic" ("true" "false"))
    ("showmanualtags" ("true" "false"))
    ;; 3.4.1 Matrices
    ("smallmatrix-align" ("c" "l" "r"))
    ("smallmatrix-inner-space")
    ;; 3.4.2 The multlined environment
    ("firstline-afterskip")
    ("lastline-preskip")
    ("multlined-pos" ("c" "b" "t"))
    ("multlined-width")
    ;; 3.4.7 Centered \vdots
    ("shortvdotsadjustabove")
    ("shortvdotsadjustbelow")
    ;; 3.5 Intertext and short intertext
    ("original-intertext" ("true" "false"))
    ("original-shortintertext" ("true" "false"))
    ("above-intertext-sep")
    ("below-intertext-sep")
    ("above-shortintertext-sep")
    ("below-shortintertext-sep")
    ;; 3.7.2 Vertically centered colon
    ("centercolon" ("true" "false"))
    ;; 4.2 Left sub/superscripts
    ("prescript-sub-format")
    ("prescript-sup-format")
    ("prescript-arg-format"))
  "Options for the \\mathtoolsset command.")

;; Setup for \newtagform
(TeX-auto-add-type "mathtools-newtagform" "LaTeX")

(defvar LaTeX-mathtools-newtagform-regexp
  '("\\\\newtagform{\\([^}]+\\)}"
    1 LaTeX-auto-mathtools-newtagform)
  "Matches the first argument of \\newtagform from mathtools package.")

;; Setup for \DeclarePairedDelimiter(X)?:
(TeX-auto-add-type "mathtools-DeclarePairedDelimiter" "LaTeX")

(defvar LaTeX-mathtools-DeclarePairedDelimiter-regexp
  `(,(concat "\\\\DeclarePairedDelimiter\\(?:X\\|XPP\\)?"
             "{?"
             "\\\\\\([a-zA-Z]+\\)"
             "}?"
             "\\(?:\\[\\([0-9]+\\)\\]\\)?")
    (1 2) LaTeX-auto-mathtools-DeclarePairedDelimiter)
  "Match the arguments of \\DeclarePairedDelimiterX? from mathtools package.")

;; Setup for \newgathered
(TeX-auto-add-type "mathtools-newgathered" "LaTeX")

(defvar LaTeX-mathtools-newgathered-regexp
  '("\\\\newgathered{\\([^}]+\\)}"
    1 LaTeX-auto-mathtools-newgathered)
  "Matches the first argument of \\newgathered from mathtools package.")

(defun LaTeX-mathtools-auto-prepare ()
  "Clear various variables for mathtools package before parsing."
  (setq LaTeX-auto-mathtools-newtagform             nil
        LaTeX-auto-mathtools-DeclarePairedDelimiter nil
        LaTeX-auto-mathtools-newgathered            nil))

(defun LaTeX-mathtools-auto-cleanup ()
  "Process the parsed elements for mathtools package."
  (when (LaTeX-mathtools-DeclarePairedDelimiter-list)
    (dolist (delim (LaTeX-mathtools-DeclarePairedDelimiter-list))
      (let ((cmd (car delim))
            (arg (cadr delim)))
        (TeX-add-symbols
         `(,cmd
           [TeX-arg-completing-read ("\\big" "\\Big" "\\bigg" "\\Bigg")
                                    "Command" nil nil nil nil nil nil "\\"]
           ,(if (string= arg "")
                1
              (string-to-number arg)))
         `(,(concat cmd "*")
           ,(if (string= arg "")
                1
              (string-to-number arg)))))))
  (when (LaTeX-mathtools-newgathered-list)
    (dolist (env (mapcar #'car (LaTeX-mathtools-newgathered-list)))
      (LaTeX-add-environments env)
      (add-to-list 'LaTeX-item-list
                   `(,env . LaTeX-item-equation) t)
      (add-to-list 'LaTeX-label-alist
                   `(,env . LaTeX-amsmath-label) t)
      (when (fboundp 'reftex-add-label-environments)
        (reftex-add-label-environments `((,env ?e nil nil t)))))))

(add-hook 'TeX-auto-prepare-hook #'LaTeX-mathtools-auto-prepare t)
(add-hook 'TeX-auto-cleanup-hook #'LaTeX-mathtools-auto-cleanup t)
(add-hook 'TeX-update-style-hook #'TeX-auto-parse t)

(defvar LaTeX-mathtools-mathstyle-list
  '("\\displaystyle" "\\textstyle"
    "\\scriptstyle"  "\\scriptscriptstyle")
  "List of math styles supported by the mathtools package.")

(defun LaTeX-mathtools-arg-declarepaireddelimiter (optional &optional X)
  "Query and insert various \\DeclarePairedDelimiter macros from mathtools package.
If X is non-nil, be aware of the macros \\DeclarePairedDelimiterX
and \\DeclarePairedDelimiterXPP."
  (let ((cmd (TeX-read-string (concat "Command: " TeX-esc)))
        (arg (when X (TeX-read-string
                      (TeX-argument-prompt t nil "Number of arguments")))))
    (TeX-add-symbols
     `(,cmd
       [TeX-arg-completing-read ("\\big" "\\Big" "\\bigg" "\\Bigg")
                                "Command" nil nil nil nil nil nil "\\"]
       ,(if X
            ;; This is no precaution, arg has to be > 0
            (string-to-number arg)
          1))
     `(,(concat cmd "*")
       ,(if X
            (string-to-number arg)
          1)))
    (LaTeX-add-mathtools-DeclarePairedDelimiters
     `(,cmd ,(if X arg "")))
    (TeX-argument-insert cmd optional TeX-esc)
    (when arg
      (insert (concat LaTeX-optop arg LaTeX-optcl)))))

(defun LaTeX-mathtools-env-multlined (env)
  "Query and insert two optional arguments for ENV multlined.
If both arguments are given, insert them in brackets.  If only a
width is given, insert it prefixed with a pair of empty
brackets."
  (let ((pos (TeX-read-string
              (TeX-argument-prompt t nil "Position (t, b or c (default))")))
        (width (completing-read
                (TeX-argument-prompt t nil "Width")
                (mapcar
                 (lambda (x) (concat TeX-esc (car x)))
                 (LaTeX-length-list)))))
    (LaTeX-insert-environment
     env
     (cond (;; both arguments
            (and pos   (not (string= pos ""))
                 width (not (string= width "")))
            (format "[%s][%s]" pos width))
           (;; pos not empty, width empty
            (and pos (not (string= pos ""))
                 (string= width ""))
            (format "[%s]" pos))
           (;; pos empty, width not
            (and (string= pos "")
                 width (not (string= width "")))
            (format "[][%s]" width))
           (t nil)))))

(defun LaTeX-mathtools-env-cases (env)
  "Insert various cases ENVs incl. an ampersand from mathtools package."
  (LaTeX-insert-environment env)
  (save-excursion
    (insert ?&)))

(defun LaTeX-mathtools-item-cases ()
  "Insert contents to terminate a line in multi-line cases environment.
Put line break macro on the last line.  Next, insert an ampersand."
  (end-of-line 0)
  (just-one-space)
  (TeX-insert-macro "\\")
  (forward-line 1)
  (save-excursion
    (insert ?&)))

(TeX-add-style-hook
 "mathtools"
 (lambda ()

   ;; Add mathtools to parser
   (TeX-auto-add-regexp LaTeX-mathtools-newtagform-regexp)
   (TeX-auto-add-regexp LaTeX-mathtools-DeclarePairedDelimiter-regexp)
   (TeX-auto-add-regexp LaTeX-mathtools-newgathered-regexp)

   ;; "default" is pre-defined
   (LaTeX-add-mathtools-newtagforms "default")

   ;; mathtools requires amsmath, as some bugs in amsmath are fixed
   (TeX-run-style-hooks "amsmath")

   (LaTeX-add-environments
    ;; 3.4.1 Matrices
    '("matrix*"  [ "Vertical alignment (l, r or c (default))" ])
    '("pmatrix*" [ "Vertical alignment (l, r or c (default))" ])
    '("bmatrix*" [ "Vertical alignment (l, r or c (default))" ])
    '("Bmatrix*" [ "Vertical alignment (l, r or c (default))" ])
    '("vmatrix*" [ "Vertical alignment (l, r or c (default))" ])
    '("Vmatrix*" [ "Vertical alignment (l, r or c (default))" ])
    '("smallmatrix*" [ "Vertical alignment (l, r or c (default))" ])
    '("psmallmatrix")
    '("psmallmatrix*" [ "Vertical alignment (l, r or c (default))" ])
    '("bsmallmatrix")
    '("bsmallmatrix*" [ "Vertical alignment (l, r or c (default))" ])
    '("Bsmallmatrix")
    '("Bsmallmatrix*" [ "Vertical alignment (l, r or c (default))" ])
    '("vsmallmatrix")
    '("vsmallmatrix*" [ "Vertical alignment (l, r or c (default))" ])
    '("Vsmallmatrix")
    '("Vsmallmatrix*" [ "Vertical alignment (l, r or c (default))" ])
    ;; 3.4.2 The multlined environment
    '("multlined" LaTeX-mathtools-env-multlined)
    ;; 3.4.3 More cases -like environments
    '("dcases"   LaTeX-mathtools-env-cases)
    '("dcases*"  LaTeX-mathtools-env-cases)
    '("rcases"   LaTeX-mathtools-env-cases)
    '("rcases*"  LaTeX-mathtools-env-cases)
    '("drcases"  LaTeX-mathtools-env-cases)
    '("drcases*" LaTeX-mathtools-env-cases)
    '("cases*"   LaTeX-mathtools-env-cases)
    ;; 4.4 Spreading equations
    '("spreadlines" "Spacing between lines")
    ;; 4.5 Gathered environments
    '("lgathered" ["Vertical position (t or b)"])
    '("rgathered" ["Vertical position (t or b)"]))

   (TeX-add-symbols
    '("mathtoolsset" (TeX-arg-key-val LaTeX-mathtools-key-val-options))
    ;; 3.1.1 A complement to \smash, \llap, and \rlap
    '("mathllap"
      [TeX-arg-completing-read LaTeX-mathtools-mathstyle-list
                               "Math style" nil nil nil nil nil nil "\\"]
      t)
    '("mathrlap"
      [TeX-arg-completing-read LaTeX-mathtools-mathstyle-list
                               "Math style" nil nil nil nil nil nil "\\"]
      t)
    '("mathclap"
      [TeX-arg-completing-read LaTeX-mathtools-mathstyle-list
                               "Math style" nil nil nil nil nil nil "\\"]
      t)
    '("mathmakebox" [TeX-arg-length "Width"] [ "Position" ] 1)
    '("clap" 1)
    '("mathmbox" 1)
    ;; 3.1.2 Forcing a cramped style
    '("cramped"
      [TeX-arg-completing-read LaTeX-mathtools-mathstyle-list
                               "Math style" nil nil nil nil nil nil "\\"]
      1)
    '("crampedllap"
      [TeX-arg-completing-read LaTeX-mathtools-mathstyle-list
                               "Math style" nil nil nil nil nil nil "\\"]
      t)
    '("crampedrlap"
      [TeX-arg-completing-read LaTeX-mathtools-mathstyle-list
                               "Math style" nil nil nil nil nil nil "\\"]
      t)
    '("crampedclap"
      [TeX-arg-completing-read LaTeX-mathtools-mathstyle-list
                               "Math style" nil nil nil nil nil nil "\\"]
      t)
    ;; 3.1.3 Smashing an operator
    '("smashoperator" [ "Position (l, r or lr (default)" ] 1)
    ;; 3.1.4 Adjusting the limits of operators
    '("adjustlimits" t (TeX-arg-literal "_") nil nil (TeX-arg-literal "_") nil)
    ;; 3.1.5 Swapping space above AMS display math environments
    '("SwapAboveDisplaySkip" 0)
    ;; 3.2.1 The appearance of tags
    `("newtagform"
      ,(lambda (optional)
         (let ((newtag (TeX-read-string
                        (TeX-argument-prompt optional nil "Name"))))
           (LaTeX-add-mathtools-newtagforms newtag)
           (TeX-argument-insert newtag optional)))
      [ "Inner format" ] "Left" "Right")
    '("renewtagform"
      (TeX-arg-completing-read (LaTeX-mathtools-newtagform-list) "Name")
      [ "Inner format" ] "Left" "Right")
    '("usetagform"
      (TeX-arg-completing-read (LaTeX-mathtools-newtagform-list) "Name"))
    ;; 3.2.2 Showing only referenced tags
    '("refeq" TeX-arg-ref)
    '("noeqref" TeX-arg-ref)
    ;; 3.3.1 Arrow-like symbols
    '("xleftrightarrow" ["Below"] "Above")
    '("xLeftarrow" ["Below"] "Above")
    '("xRightarrow" ["Below"] "Above")
    '("xLeftrightarrow" ["Below"] "Above")
    '("xhookleftarrow" ["Below"] "Above")
    '("xhookrightarrow" ["Below"] "Above")
    '("xmapsto" ["Below"] "Above")
    '("xrightharpoondown" ["Below"] "Above")
    '("xrightharpoonup" ["Below"] "Above")
    '("xleftharpoondown" ["Below"] "Above")
    '("xleftharpoonup" ["Below"] "Above")
    '("xrightleftharpoons" ["Below"] "Above")
    '("xleftrightharpoons" ["Below"] "Above")
    ;; 3.3.2 Braces and brackets
    '("underbracket" [TeX-arg-length "Rule thickness"]
      [ (TeX-arg-length "Bracket height") ] t)
    '("overbracket"  [TeX-arg-length "Rule thickness"]
      [ (TeX-arg-length "Bracket height") ] t)
    '("underbrace" 1)
    '("overbrace" 1)
    '("LaTeXunderbrace" 1)
    '("LaTeXoverbrace" 1)
    ;; 3.4.2
    '("shoveleft"  [TeX-arg-length "Dimension"] 1)
    '("shoveright" [TeX-arg-length "Dimension"] 1)
    ;; 3.4.4
    '("MoveEqLeft" [ "Number" ])
    ;; 3.4.5 Boxing a single line in an alignment
    '("Aboxed" 1)
    ;; 3.4.6 Adding arrows between lines in an alignment
    '("ArrowBetweenLines" [ TeX-arg-macro ] )
    '("ArrowBetweenLines*" [ TeX-arg-macro ] )
    ;; 3.4.7 Centered \vdots
    '("vdotswithin" "Symbol")
    '("shortvdotswithin" "Symbol")
    '("shortvdotswithin*" "Symbol")
    '("MTFlushSpaceAbove")
    '("MTFlushSpaceBelow")
    ;; 3.5 Intertext and short intertext
    ;; don't understand t, but intertext in amsmath.el uses it
    '("shortintertext" t)
    ;; 3.6 Paired delimiters
    '("DeclarePairedDelimiter"
      LaTeX-mathtools-arg-declarepaireddelimiter
      "Left delimiter" "Right delimiter")
    '("DeclarePairedDelimiterX"
      (LaTeX-mathtools-arg-declarepaireddelimiter t)
      "Left delimiter" "Right delimiter" t)
    '("DeclarePairedDelimiterXPP"
      (LaTeX-mathtools-arg-declarepaireddelimiter t)
      "Pre-code" "Left delimiter" "Right delimiter" 2)
    '("delimsize" 0)
    ;; 3.6.1 Expert use
    `("reDeclarePairedDelimiterInnerWrapper"
      (TeX-arg-completing-read
       ,(lambda ()
          (mapcar (lambda (x) (concat TeX-esc (car x)))
                  (LaTeX-mathtools-DeclarePairedDelimiter-list)))
       "Command" nil nil nil nil nil nil "\\")
      (TeX-arg-completing-read ("star" "nostar") "star or nostar")
      t)
    ;; 3.7.1 Left and right parentheses
    '("lparen" TeX-arg-insert-right-brace-maybe)
    '("rparen")
    ;; 3.7.2 Vertically centered colon
    "vcentcolon" "ordinarycolon" "coloneqq" "Coloneqq"
    "coloneq" "Coloneq" "eqqcolon" "Eqqcolon" "eqcolon"
    "Eqcolon" "colonapprox" "Colonapprox" "colonsim" "Colonsim"
    "dblcolon"
    ;; 3.7.3 A few missing symbols
    "nuparrow" "ndownarrow" "bigtimes"
    ;; 4.2 Left sub/superscripts
    '("prescript" "Below" "Above" t)
    ;; 4.3 Declaring math sizes
    '("DeclareMathSizes" 4)
    ;; 4.5 Gathered environments
    `("newgathered"
      ,(lambda (optional)
         (let ((env (TeX-read-string
                     (TeX-argument-prompt optional nil "Name"))))
           (LaTeX-add-environments env)
           (LaTeX-add-mathtools-newgathereds env)
           (add-to-list 'LaTeX-item-list
                        `(,env . LaTeX-item-equation) t)
           (add-to-list 'LaTeX-label-alist
                        `(,env . LaTeX-amsmath-label) t)
           (TeX-argument-insert env optional)))
      3)
    '("renewgathered"
      (TeX-arg-completing-read (LaTeX-mathtools-newgathered-list) "Name")
      3)
    ;; 4.6 Split fractions
    '("splitfrac" 2)
    '("splitdfrac" 2))

   ;; Append delimiters to `TeX-braces-association'
   ;; 3.7.1 Left and right parentheses
   (make-local-variable 'TeX-braces-association)
   (add-to-list 'TeX-braces-association '("\\lparen" . "\\rparen") t)

   (setq LaTeX-item-list
         (append '(("multlined"   . LaTeX-item-equation)
                   ("lgathered"   . LaTeX-item-equation)
                   ("rgathered"   . LaTeX-item-equation)
                   ;; FIXME: The entry for spreadlines seems dubious.
                   ("spreadlines" . LaTeX-item-equation)
                   ("matrix*"     . LaTeX-item-equation)
                   ("pmatrix*"    . LaTeX-item-equation)
                   ("bmatrix*"    . LaTeX-item-equation)
                   ("Bmatrix*"    . LaTeX-item-equation)
                   ("vmatrix*"    . LaTeX-item-equation)
                   ("Vmatrix*"    . LaTeX-item-equation)
                   ("smallmatrix*"  . LaTeX-item-equation)
                   ("psmallmatrix"  . LaTeX-item-equation)
                   ("psmallmatrix*" . LaTeX-item-equation)
                   ("bsmallmatrix"  . LaTeX-item-equation)
                   ("bsmallmatrix*" . LaTeX-item-equation)
                   ("Bsmallmatrix"  . LaTeX-item-equation)
                   ("Bsmallmatrix*" . LaTeX-item-equation)
                   ("vsmallmatrix"  . LaTeX-item-equation)
                   ("vsmallmatrix*" . LaTeX-item-equation)
                   ("Vsmallmatrix"  . LaTeX-item-equation)
                   ("Vsmallmatrix*" . LaTeX-item-equation)
                   ("dcases"      . LaTeX-mathtools-item-cases)
                   ("dcases*"     . LaTeX-mathtools-item-cases)
                   ("rcases"      . LaTeX-mathtools-item-cases)
                   ("rcases*"     . LaTeX-mathtools-item-cases)
                   ("drcases"     . LaTeX-mathtools-item-cases)
                   ("drcases*"    . LaTeX-mathtools-item-cases)
                   ("cases*"      . LaTeX-mathtools-item-cases))
                 LaTeX-item-list))

   (setq LaTeX-label-alist
         (append '(("lgathered" . LaTeX-amsmath-label)
                   ("rgathered" . LaTeX-amsmath-label)
                   ("multlined" . LaTeX-amsmath-label))
                 LaTeX-label-alist))

   ;; RefTeX support: Add env's with `reftex-add-label-environments'
   (when (fboundp 'reftex-add-label-environments)
     (let ((envs '(("lgathered"  ?e nil nil t)
                   ("rgathered"  ?e nil nil t)
                   ("multlined"  ?e nil nil t))))
       (dolist (env envs)
         (reftex-add-label-environments `(,env)))))

   ;; Fontification
   (when (and (featurep 'font-latex)
              (eq TeX-install-font-lock 'font-latex-setup))
     (font-latex-add-keywords '(("mathtoolsset"  "{")
                                ("newtagform"    "{[{{")
                                ("renewtagform"  "{[{{")
                                ("DeclarePairedDelimiter"     "|{\\{{")
                                ("DeclarePairedDelimiterX"    "|{\\[{{{")
                                ("DeclarePairedDelimiterXPP"  "|{\\[{{{{{")
                                ("reDeclarePairedDelimiterInnerWrapper" "|{\\{{")
                                ("DeclareMathSizes"           "{{{{")
                                ("newgathered"                "{{{{")
                                ("renewgathered"              "{{{{"))
                              'function)
     (font-latex-add-keywords '(("usetagform" "{"))
                              'variable)
     (font-latex-add-keywords '(("refeq"   "{")
                                ("noeqref" "{"))
                              'reference)))
 TeX-dialect)

;;; mathtools.el ends here
