bug-auctex
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

bug#31132: AUCTeX, RefTeX and biblatex's multicite commands


From: Arash Esbati
Subject: bug#31132: AUCTeX, RefTeX and biblatex's multicite commands
Date: Thu, 19 Apr 2018 21:45:51 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/25.3

address@hidden writes:

> From TeX.SX: https://tex.stackexchange.com/q/425883/105447
>
>
> RefTeX's relations biblatex's multicite commands (such as \parencites,
> \footcites, \textcites and so on) leave some things to be desired
> for. Namely, they do not seem to be recognized as citation
> commands. This results in RefTeX's `reftex-view-crossref` (C-c &)
> returning "Not on a crossref macro argument". Also, in adding an extra
> bibkey to an already existing multicite command, it is not possible to
> access RefTeX's facilities.
>
> Arash Esbati generously provided patches at TeX.SX that go a long way
> in dealing with these problems. However, support for the presence of
> global optional arguments (between parentheses) is still missing. At
> their request, I write to report this issue.

Hi Gusbrs,

thanks for the report.  I fiddled a little around and I think I have a
working setup for this issue.  I think with this patch, RefTeX will also
recognize referencing commands provided by cleveref.sty.

--8<---------------cut here---------------start------------->8---
diff --git a/lisp/textmodes/reftex-cite.el b/lisp/textmodes/reftex-cite.el
index fd229a6..3f4965a 100644
--- a/lisp/textmodes/reftex-cite.el
+++ b/lisp/textmodes/reftex-cite.el
@@ -763,7 +763,9 @@ in order to only add another reference in the same cite 
command."
       (setq format "%l"))

      ((and (stringp macro)
-           (string-match "\\`\\\\cite\\|cite\\'" macro))
+           (string-match "\\`\\\\cite\\|cite\\([s*]\\|texts?\\)?\\'" macro))
+      ;; Match also commands from biblatex ending with `s' (\cites) or
+      ;; `*' (\parencite*) and `texts?' (\footcitetext and \footcitetexts).
       ;; We are already inside a cite macro
       (if (or (not arg) (not (listp arg)))
           (setq format
diff --git a/lisp/textmodes/reftex-dcr.el b/lisp/textmodes/reftex-dcr.el
index 74a5e63..b93d05d 100644
--- a/lisp/textmodes/reftex-dcr.el
+++ b/lisp/textmodes/reftex-dcr.el
@@ -60,10 +60,13 @@ to the functions `reftex-view-cr-cite' and 
`reftex-view-cr-ref'."
       (setq reftex-call-back-to-this-buffer (current-buffer))

       (cond
-       ((string-match "\\`\\\\cite\\|cite\\*?\\'\\|bibentry" macro)
+       ((string-match "\\`\\\\cite\\|cite\\([s*]\\|texts?\\)?\\'\\|bibentry" 
macro)
+        ;; Match also commands from biblatex ending with `s' (\cites) or
+        ;; `*' (\parencite*) and `texts?' (\footcitetext and \footcitetexts).
        ;; A citation macro: search for bibitems or BibTeX entries
        (setq dw (reftex-view-cr-cite arg key auto-how)))
-       ((string-match "\\`\\\\ref\\|ref\\(range\\)?\\*?\\'" macro)
+       ((string-match "\\`\\\\ref\\|ref\\(s\\|range\\)?\\*?\\'" macro)
+        ;; Match also commands from cleveref ending with `s' (\namecrefs).
        ;; A reference macro: search for labels
        (setq dw (reftex-view-cr-ref arg key auto-how)))
        (auto-how nil)  ;; No further action for automatic display (speed)
diff --git a/lisp/textmodes/reftex-parse.el b/lisp/textmodes/reftex-parse.el
index 492f546..31e108c 100644
--- a/lisp/textmodes/reftex-parse.el
+++ b/lisp/textmodes/reftex-parse.el
@@ -788,12 +788,13 @@ move backward to the closing parenthesis of the previous 
argument.
 This function understands the splitting of macros over several lines
 in TeX."
   (cond
-   ;; Just to be quick:
-   ((memq (preceding-char) '(?\] ?\})))
+   ;; Just to be quick: biblatex uses () as delimiters for optional
+   ;; arguments in qualified citation lists.
+   ((memq (preceding-char) '(?\] ?\) ?\})))
    ;; Do a search
    ((and reftex-allow-detached-macro-args
          (re-search-backward
-          "[]}][ \t]*[\n\r]?\\([ \t]*%[^\n\r]*[\n\r]\\)*[ \t]*\\=" bound t))
+          "[])}][ \t]*[\n\r]?\\([ \t]*%[^\n\r]*[\n\r]\\)*[ \t]*\\=" bound t))
     (goto-char (1+ (match-beginning 0)))
     t)
    (t nil)))
@@ -842,12 +843,22 @@ considered an argument of macro \\macro."
             (while (and (reftex-move-to-previous-arg bound)
                         (condition-case nil
                             (let ((forward-sexp-function nil))
-                              (backward-sexp) t)
+                              (if (= (preceding-char) ?\))
+                                  (let ((temp-table (make-syntax-table)))
+                                    (modify-syntax-entry ?\( "()" temp-table)
+                                    (modify-syntax-entry ?\) ")(" temp-table)
+                                    (with-syntax-table temp-table
+                                      (backward-sexp)))
+                                (backward-sexp))
+                              t)
                           (error nil)))
-              (if (eq (following-char) ?\[) (cl-incf cnt-opt))
+              (if (or (eq (following-char) ?\[)
+                      (eq (following-char) ?\())
+                  (cl-incf cnt-opt))
               (cl-incf cnt))
             (setq pos (point))
             (when (and (or (= (following-char) ?\[)
+                           (= (following-char) ?\()
                            (= (following-char) ?\{))
                        (re-search-backward "\\\\[*a-zA-Z]+\\=" nil t))
               (setq cmd (reftex-match-string 0))
--8<---------------cut here---------------end--------------->8---

One thing I don't understand is why (backward-sexp) doesn't work on
(Global Pre) arguments.  Maybe somebody can enlighten me.  For now, I
added a trickery with `with-syntax-table'.

Further, I think the functions `reftex-move-over-touching-args' and
`reftex-move-to-next-arg' must also be patched in order to support
optional arguments in parentheses.

My test file for this looks like this.  In case somebody wants to try
this out, the complete functions are inserted below.  Any comments
welcome.


--8<---------------cut here---------------start------------->8---
\documentclass{article}

\begin{filecontents}{biblatex-bib.bib}
@book{lamp:94,
  author    = {Leslie Lamport},
  title     = {LaTeX - {A} Document Preparation System: User's Guide
               and Reference Manual, Second Edition},
  publisher = {Pearson / Prentice Hall},
  year      = {1994},
  isbn      = {978-0-201-52983-8},
  timestamp = {Fri, 08 Apr 2011 18:21:00 +0200},
}

@book{mitt:97,
  author    = {Michel Goossens and
               Sebastian Rahtz and
               Frank Mittelbach},
  title     = {The LaTeX Graphics Companion - Illustrating documents
               with TeX and PostScript},
  series    = {Addison-Wesley series on tools and techniques
               for computer typesetting},
  publisher = {Addison-Wesley},
  year      = {1997},
  isbn      = {978-0-201-85469-5},
}
\end{filecontents}

\usepackage[style=authoryear]{biblatex}

\addbibresource{biblatex-bib.bib}

\begin{document}

%% =============== RefTeX standard

\cite{mitt:97}
==> \verb|C-c &| Ok, \verb|C-c [| Ok

\cites[Pre][Post]{mitt:97}[Pre][Post]{lamp:94}
==> \verb|C-c &| Ok, \verb|C-c [| Ok

\footcitetext[Pre][Post]{mitt:97}
==> \verb|C-c &| NOk, \verb|C-c [| NOk

\parencite*[Pre][Post]{mitt:97}
==> \verb|C-c &| Ok, \verb|C-c [| NOk

\parencites{mitt:97}
\parencites(GPre)()[Pre][]{mitt:97}[Pre][]{lamp:94}
\parencites(pre)()[pre][]{mitt:97}
\parencites(post)[post]{mitt:97}
==> \verb|C-c &| NOk, \verb|C-c [| NOk

%% ============== RefTeX patched

\parencites[Pre][Post]{mitt:97}[Pre][Post]{lamp:94}
==> \verb|C-c &| Ok, \verb|C-c [| Ok

\parencites(Global Pre)(Global Post)[Pre][Post]{mitt:97}[Pre][Post]{lamp:94}
==> \verb|C-c &| Ok, \verb|C-c [| Ok


\begin{verbatim}
(defun reftex-view-crossref (&optional arg auto-how fail-quietly)
  "View cross reference of macro at point.  Point must be on the KEY
argument.  When at a `\\ref' macro, show corresponding `\\label'
definition, also in external documents (`xr').  When on a label, show
a locations where KEY is referenced.  Subsequent calls find additional
locations.  When on a `\\cite', show the associated `\\bibitem' macro or
the BibTeX database entry.  When on a `\\bibitem', show a `\\cite' macro
which uses this KEY. When on an `\\index', show other locations marked
by the same index entry.
To define additional cross referencing items, use the option
`reftex-view-crossref-extra'.  See also `reftex-view-crossref-from-bibtex'.
With one or two C-u prefixes, enforce rescanning of the document.
With argument 2, select the window showing the cross reference.
AUTO-HOW is only for the automatic crossref display and is handed through
to the functions `reftex-view-cr-cite' and `reftex-view-cr-ref'."

  (interactive "P")
  ;; See where we are.
  (let* ((macro (car (reftex-what-macro-safe 1)))
         (key (reftex-this-word "^{}%\n\r, \t"))
         dw)

    (if (or (null macro) (reftex-in-comment))
        (or fail-quietly
            (error "Not on a crossref macro argument"))

      (setq reftex-call-back-to-this-buffer (current-buffer))

      (cond
       ((string-match "\\`\\\\cite\\|cite\\([s*]\\|texts?\\)?\\'\\|bibentry" 
macro)
        ;; Match also commands from biblatex ending with `s' (\cites) or
        ;; `*' (\parencite*) and `texts?' (\footcitetext and \footcitetexts).
        ;; A citation macro: search for bibitems or BibTeX entries
        (setq dw (reftex-view-cr-cite arg key auto-how)))
       ((string-match "\\`\\\\ref\\|ref\\(s\\|range\\)?\\*?\\'" macro)
        ;; Match also commands from cleveref ending with `s' (\namecrefs).
        ;; A reference macro: search for labels
        (setq dw (reftex-view-cr-ref arg key auto-how)))
       (auto-how nil)  ;; No further action for automatic display (speed)
       ((or (equal macro "\\label")
            (member macro reftex-macros-with-labels))
        ;; A label macro: search for reference macros
        (reftex-access-scan-info arg)
        (setq dw (reftex-view-regexp-match
                  (format reftex-find-reference-format (regexp-quote key))
                  4 nil nil)))
       ((equal macro "\\bibitem")
        ;; A bibitem macro: search for citations
        (reftex-access-scan-info arg)
        (setq dw (reftex-view-regexp-match
                  (format reftex-find-citation-regexp-format (regexp-quote key))
                  4 nil nil)))
       ((member macro reftex-macros-with-index)
        (reftex-access-scan-info arg)
        (setq dw (reftex-view-regexp-match
                  (format reftex-find-index-entry-regexp-format
                          (regexp-quote key))
                  3 nil nil)))
       (t
        (reftex-access-scan-info arg)
        (catch 'exit
          (let ((list reftex-view-crossref-extra)
                entry mre action group)
            (while (setq entry (pop list))
              (setq mre (car entry)
                    action (nth 1 entry)
                    group (nth 2 entry))
              (when (string-match mre macro)
                (setq dw (reftex-view-regexp-match
                          (format action key) group nil nil))
                (throw 'exit t))))
          (error "Not on a crossref macro argument"))))
      (if (and (eq arg 2) (windowp dw)) (select-window dw)))))

(defun reftex-figure-out-cite-format (arg &optional no-insert format-key)
  "Check if there is already a cite command at point and change cite format
in order to only add another reference in the same cite command."
  (let ((macro (car (reftex-what-macro 1)))
        (cite-format-value (reftex-get-cite-format))
        key format)
    (cond
     (no-insert
      ;; Format does not really matter because nothing will be inserted.
      (setq format "%l"))

     ((and (stringp macro)
           (string-match "\\`\\\\cite\\|cite\\([s*]\\|texts?\\)?\\'" macro))
      ;; Match also commands from biblatex ending with `s' (\cites) or
      ;; `*' (\parencite*) and `texts?' (\footcitetext and \footcitetexts).
      ;; We are already inside a cite macro
      (if (or (not arg) (not (listp arg)))
          (setq format
                (concat
                 (if (member (preceding-char) '(?\{ ?,))
                     ""
                   reftex-cite-key-separator)
                 "%l"
                 (if (member (following-char) '(?\} ?,))
                     ""
                   reftex-cite-key-separator)))
        (setq format "%l")))
     (t
      ;; Figure out the correct format
      (setq format
            (if (and (symbolp cite-format-value)
                     (assq cite-format-value reftex-cite-format-builtin))
                (nth 2 (assq cite-format-value reftex-cite-format-builtin))
              cite-format-value))
      (when (listp format)
        (setq key
              (or format-key
                  (reftex-select-with-char
                   "" (concat "SELECT A CITATION FORMAT\n\n"
                              (mapconcat
                               (lambda (x)
                                 (format "[%c] %s  %s" (car x)
                                         (if (> (car x) 31) " " "")
                                         (cdr x)))
                               format "\n")))))
        (if (assq key format)
            (setq format (cdr (assq key format)))
          (error "No citation format associated with key `%c'" key)))))
    format))

(defun reftex-move-to-previous-arg (&optional bound)
  "Assuming that we are in front of a macro argument,
move backward to the closing parenthesis of the previous argument.
This function understands the splitting of macros over several lines
in TeX."
  (cond
   ;; Just to be quick: biblatex uses () as delimiters for optional
   ;; arguments in qualified citation lists.
   ((memq (preceding-char) '(?\] ?\) ?\})))
   ;; Do a search
   ((and reftex-allow-detached-macro-args
         (re-search-backward
          "[])}][ \t]*[\n\r]?\\([ \t]*%[^\n\r]*[\n\r]\\)*[ \t]*\\=" bound t))
    (goto-char (1+ (match-beginning 0)))
    t)
   (t nil)))


(defun reftex-what-macro (which &optional bound)
  "Find out if point is within the arguments of any TeX-macro.
The return value is either (\"\\macro\" . (point)) or a list of them.

If WHICH is nil, immediately return nil.
If WHICH is 1, return innermost enclosing macro.
If WHICH is t, return list of all macros enclosing point.
If WHICH is a list of macros, look only for those macros and return the
  name of the first macro in this list found to enclose point.
If the optional BOUND is an integer, bound backwards directed
  searches to this point.  If it is nil, limit to nearest \\section -
  like statement.

This function is pretty stable, but can be fooled if the text contains
things like \\macro{aa}{bb} where \\macro is defined to take only one
argument.  As RefTeX cannot know this, the string \"bb\" would still be
considered an argument of macro \\macro."
  (unless reftex-section-regexp (reftex-compile-variables))
  (catch 'exit
    (if (null which) (throw 'exit nil))
    (let ((bound (or bound (save-excursion (re-search-backward
                                            reftex-section-regexp nil 1)
                                           (point))))
          pos cmd-list cmd cnt cnt-opt entry)
      (save-restriction
        (save-excursion
          (narrow-to-region (max (point-min) bound) (point-max))
          ;; move back out of the current parenthesis
          (while (condition-case nil
                     (let ((forward-sexp-function nil))
                       (up-list -1) t)
                   (error nil))
            (setq cnt 1 cnt-opt 0)
            ;; move back over any touching sexps
            (while (and (reftex-move-to-previous-arg bound)
                        (condition-case nil
                            (let ((forward-sexp-function nil))
                              (if (= (preceding-char) ?\))
                                  (let ((temp-table (make-syntax-table)))
                                    (modify-syntax-entry ?\( "()" temp-table)
                                    (modify-syntax-entry ?\) ")(" temp-table)
                                    (with-syntax-table temp-table
                                      (backward-sexp)))
                                (backward-sexp))
                              t)
                          (error nil)))
              (if (or (eq (following-char) ?\[)
                      (eq (following-char) ?\())
                  (cl-incf cnt-opt))
              (cl-incf cnt))
            (setq pos (point))
            (when (and (or (= (following-char) ?\[)
                           (= (following-char) ?\()
                           (= (following-char) ?\{))
                       (re-search-backward "\\\\[*a-zA-Z]+\\=" nil t))
              (setq cmd (reftex-match-string 0))
              (when (looking-at "\\\\begin{[^}]*}")
                (setq cmd (reftex-match-string 0)
                      cnt (1- cnt)))
              ;; This does ignore optional arguments.  Very hard to fix.
              (when (setq entry (assoc cmd reftex-env-or-mac-alist))
                (if (> cnt (or (nth 4 entry) 100))
                    (setq cmd nil)))
              (cond
               ((null cmd))
               ((eq t which)
                (push (cons cmd (point)) cmd-list))
               ((or (eq 1 which) (member cmd which))
                (throw 'exit (cons cmd (point))))))
            (goto-char pos)))
        (nreverse cmd-list)))))

\end{verbatim}

\end{document}

--8<---------------cut here---------------end--------------->8---

Best, Arash





reply via email to

[Prev in Thread] Current Thread [Next in Thread]