aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Fussner2024-06-10 14:16:04 +0100
committerStefan Kangas2024-09-14 17:05:33 +0200
commitb44c00669ace7b9e6a90aecb5f4e9f4edf6ed25a (patch)
tree7119d2ff1695271877a27a1a0cf0b0d648983d1c
parent98e582e74a2bbc2c7fdef02b8cd90036fa217712 (diff)
downloademacs-b44c00669ace7b9e6a90aecb5f4e9f4edf6ed25a.tar.gz
emacs-b44c00669ace7b9e6a90aecb5f4e9f4edf6ed25a.zip
Provide a modified xref backend for TeX buffers
In addition to providing a new `xref' backend, the patch also improves the general handling of expl3 syntax. Expl3 is the next-generation LaTeX specification, and has for some time been available by default in the LaTeX kernel. The new syntax co-exists in many files with the standard LaTeX2e syntax, so we try at least minimally to separate the way modes handle the two specifications, both to reduce visually-disturbing interference between them and also to improve the `xref' backend. (Bug#53749) * lib-src/etags.c (TeX_commands): Improve parsing of commands in TeX buffers. (TEX_defenv): Expand list of commands to tag by default in TeX buffers. (TeX_help): * doc/emacs/maintaining.texi (Tag Syntax): Document new tagged commands. (Identifier Search): Add note about semantic-symref-filepattern-alist, auto-mode-alist, and xref-find-references. * lisp/textmodes/tex-mode.el (tex-font-lock-suscript): Test for underscore in expl3 files and regions, disable subscript face there. (tex-common-initialization): Set up xref backend for in-tree TeX modes. Detect expl3 files, and in others set up a list of expl3 regions. (tex-expl-buffer-parse): New function called in previous. (tex-expl-buffer-p): New variable to hold the result of previous. (tex-expl-region-set): New function added to 'syntax-propertize-extend-region-functions' hook. (tex-expl-region-list): New variable to hold the result of previous. (tex--xref-backend): New function to identify the xref backend. (tex--thing-at-point, tex-thingatpt--beginning-of-symbol) (tex-thingatpt--end-of-symbol, tex--bounds-of-symbol-at-point): New functions to return 'thing-at-point' for xref backend. (tex-thingatpt-exclude-chars): New variable to do the same. (xref-backend-identifier-at-point): New TeX backend method to provide symbols for processing by xref. (xref-backend-identifier-completion-table) (xref-backend-identifier-completion-ignore-case) (xref-backend-definitions, xref-backend-apropos): Placeholders to call the standard 'etags' xref backend methods. (xref-backend-references): Wrapper to call the default xref backend method, finding as many relevant files as possible and using a bespoke syntax-propertize-function when required. (tex--collect-file-extensions, tex-xref-syntax-function): Helper functions for previous. (tex-find-references-syntax-table, tex--buffers-list) (tex--xref-syntax-fun, tex--old-syntax-function): New variables for the same.
-rw-r--r--doc/emacs/maintaining.texi39
-rw-r--r--etc/NEWS9
-rw-r--r--lib-src/etags.c186
-rw-r--r--lisp/textmodes/tex-mode.el358
4 files changed, 572 insertions, 20 deletions
diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi
index 3c34afbaa20..0ec2385860b 100644
--- a/doc/emacs/maintaining.texi
+++ b/doc/emacs/maintaining.texi
@@ -2549,6 +2549,15 @@ identifier, showing the file name and the line where the identifier is
2549referenced. The XREF mode commands are available in this buffer, see 2549referenced. The XREF mode commands are available in this buffer, see
2550@ref{Xref Commands}. 2550@ref{Xref Commands}.
2551 2551
2552When invoked in a buffer whose major mode uses the @code{etags} backend,
2553@kbd{M-?} searches files and buffers whose major mode matches that of
2554the original buffer. It guesses that mode from file extensions, so if
2555@kbd{M-?} seems to be skipping relevant buffers or files, try
2556customizing either the variable @code{semantic-symref-filepattern-alist}
2557(if your buffer's major mode already has an entry in it), or
2558@code{auto-mode-alist} (if not), thereby informing @code{xref} of the
2559missing extensions (@pxref{Choosing Modes}).
2560
2552@vindex xref-auto-jump-to-first-xref 2561@vindex xref-auto-jump-to-first-xref
2553 If the value of the variable @code{xref-auto-jump-to-first-xref} is 2562 If the value of the variable @code{xref-auto-jump-to-first-xref} is
2554@code{t}, @code{xref-find-references} automatically jumps to the first 2563@code{t}, @code{xref-find-references} automatically jumps to the first
@@ -2767,10 +2776,32 @@ Tags for variables and functions in classes are named
2767@item 2776@item
2768In @LaTeX{} documents, the arguments for @code{\chapter}, 2777In @LaTeX{} documents, the arguments for @code{\chapter},
2769@code{\section}, @code{\subsection}, @code{\subsubsection}, 2778@code{\section}, @code{\subsection}, @code{\subsubsection},
2770@code{\eqno}, @code{\label}, @code{\ref}, @code{\cite}, 2779@code{\eqno}, @code{\label}, @code{\ref}, @code{\Ref}, @code{\footref},
2771@code{\bibitem}, @code{\part}, @code{\appendix}, @code{\entry}, 2780@code{\cite}, @code{\bibitem}, @code{\part}, @code{\appendix},
2772@code{\index}, @code{\def}, @code{\newcommand}, @code{\renewcommand}, 2781@code{\entry}, @code{\index}, @code{\def}, @code{\edef}, @code{\gdef},
2773@code{\newenvironment} and @code{\renewenvironment} are tags. 2782@code{\xdef}, @code{\newcommand}, @code{\renewcommand},
2783@code{\newenvironment}, @code{\renewenvironment},
2784@code{\DeclareRobustCommand}, @code{\newrobustcmd},
2785@code{\renewrobustcmd}, @code{\providecommand},
2786@code{\providerobustcmd}, @code{\NewDocumentCommand},
2787@code{\RenewDocumentCommand}, @code{\ProvideDocumentCommand},
2788@code{\DeclareDocumentCommand}, @code{\NewExpandableDocumentCommand},
2789@code{\RenewExpandableDocumentCommand},
2790@code{\ProvideExpandableDocumentCommand},
2791@code{\DeclareExpandableDocumentCommand},
2792@code{\NewDocumentEnvironment}, @code{\RenewDocumentEnvironment},
2793@code{\ProvideDocumentEnvironment}, @code{\DeclareDocumentEnvironment},
2794@code{\csdef}, @code{\csedef}, @code{\csgdef}, @code{\csxdef},
2795@code{\csletcs}, @code{\cslet}, @code{\letcs}, @code{\let},
2796@code{\cs_new_protected_nopar}, @code{\cs_new_protected},
2797@code{\cs_new_nopar}, @code{\cs_new_eq}, @code{\cs_new},
2798@code{\cs_set_protected_nopar}, @code{\cs_set_protected},
2799@code{\cs_set_nopar}, @code{\cs_set_eq}, @code{\cs_set},
2800@code{\cs_gset_protected_nopar}, @code{\cs_gset_protected},
2801@code{\cs_gset_nopar}, @code{\cs_gset_eq}, @code{\cs_gset},
2802@code{\cs_generate_from_arg_count}, and @code{\cs_generate_variant} are
2803tags. So too are the arguments of any starred variants of these
2804commands.
2774 2805
2775Other commands can make tags as well, if you specify them in the 2806Other commands can make tags as well, if you specify them in the
2776environment variable @env{TEXTAGS} before invoking @command{etags}. The 2807environment variable @env{TEXTAGS} before invoking @command{etags}. The
diff --git a/etc/NEWS b/etc/NEWS
index d80e31ef7bb..4ccb158dd7d 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -343,6 +343,15 @@ the 'grep' results editable. The edits will be reflected in the buffer
343visiting the originating file. Typing 'C-c C-c' will leave the Grep 343visiting the originating file. Typing 'C-c C-c' will leave the Grep
344Edit mode. 344Edit mode.
345 345
346** TeX modes
347
348+++
349*** New xref backend for TeX modes.
350The new backend ('tex-etags') is on by default, and improves the
351functionality of the standard 'xref' commands in TeX buffers. You can
352restore the standard 'etags' backend with the 'M-x xref-etags-mode'
353toggle.
354
346 355
347* New Modes and Packages in Emacs 31.1 356* New Modes and Packages in Emacs 31.1
348 357
diff --git a/lib-src/etags.c b/lib-src/etags.c
index 556b7d701fc..7f652790261 100644
--- a/lib-src/etags.c
+++ b/lib-src/etags.c
@@ -793,11 +793,27 @@ variables set with 'set!' at top level in the file.";
793static const char *TeX_suffixes [] = 793static const char *TeX_suffixes [] =
794 { "bib", "clo", "cls", "ltx", "sty", "TeX", "tex", NULL }; 794 { "bib", "clo", "cls", "ltx", "sty", "TeX", "tex", NULL };
795static const char TeX_help [] = 795static const char TeX_help [] =
796"In LaTeX text, the argument of any of the commands '\\chapter',\n\ 796"In LaTeX text, the argument of the commands '\\chapter', '\\section',\n\
797'\\section', '\\subsection', '\\subsubsection', '\\eqno', '\\label',\n\ 797'\\subsection', '\\subsubsection', '\\eqno', '\\label', '\\ref',\n\
798'\\ref', '\\cite', '\\bibitem', '\\part', '\\appendix', '\\entry',\n\ 798'\\Ref', '\\footref', '\\cite', '\\bibitem', '\\part', '\\appendix',\n\
799'\\index', '\\def', '\\newcommand', '\\renewcommand',\n\ 799'\\entry', '\\index', '\\def', '\\edef', '\\gdef', '\\xdef',\n\
800'\\newenvironment' or '\\renewenvironment' is a tag.\n\ 800'\\newcommand', '\\renewcommand', '\\newrobustcmd', '\\renewrobustcmd',\n\
801'\\newenvironment', '\\renewenvironment', '\\DeclareRobustCommand',\n\
802'\\providecommand', '\\providerobustcmd', '\\NewDocumentCommand',\n\
803'\\RenewDocumentCommand', '\\ProvideDocumentCommand',\n\
804'\\DeclareDocumentCommand', '\\NewExpandableDocumentCommand',\n\
805'\\RenewExpandableDocumentCommand', '\\ProvideExpandableDocumentCommand',\n\
806'\\DeclareExpandableDocumentCommand', '\\NewDocumentEnvironment',\n\
807'\\RenewDocumentEnvironment', '\\ProvideDocumentEnvironment',\n\
808'\\DeclareDocumentEnvironment','\\csdef', '\\csedef', '\\csgdef',\n\
809'\\csxdef', '\\csletcs', '\\cslet', '\\letcs', '\\let',\n\
810'\\cs_new_protected_nopar', '\\cs_new_protected', '\\cs_new_nopar',\n\
811'\\cs_new_eq', '\\cs_new', '\\cs_set_protected_nopar',\n\
812'\\cs_set_protected', '\\cs_set_nopar', '\\cs_set_eq', '\\cs_set',\n\
813'\\cs_gset_protected_nopar', '\\cs_gset_protected', '\\cs_gset_nopar',\n\
814'\\cs_gset_eq', '\\cs_gset', '\\cs_generate_from_arg_count', or\n\
815'\\cs_generate_variant' is a tag. So is the argument of any starred\n\
816variant of these commands.\n\
801\n\ 817\n\
802Other commands can be specified by setting the environment variable\n\ 818Other commands can be specified by setting the environment variable\n\
803'TEXTAGS' to a colon-separated list like, for example,\n\ 819'TEXTAGS' to a colon-separated list like, for example,\n\
@@ -5746,9 +5762,20 @@ static linebuffer *TEX_toktab = NULL; /* Table with tag tokens */
5746/* Default set of control sequences to put into TEX_toktab. 5762/* Default set of control sequences to put into TEX_toktab.
5747 The value of environment var TEXTAGS is prepended to this. */ 5763 The value of environment var TEXTAGS is prepended to this. */
5748static const char *TEX_defenv = "\ 5764static const char *TEX_defenv = "\
5749:chapter:section:subsection:subsubsection:eqno:label:ref:cite:bibitem\ 5765:label:ref:Ref:footref:chapter:section:subsection:subsubsection:eqno:cite\
5750:part:appendix:entry:index:def\ 5766:bibitem:part:appendix:entry:index:def:edef:gdef:xdef:newcommand:renewcommand\
5751:newcommand:renewcommand:newenvironment:renewenvironment"; 5767:newenvironment:renewenvironment:DeclareRobustCommand:renewrobustcmd\
5768:newrobustcmd:providecommand:providerobustcmd:NewDocumentCommand\
5769:RenewDocumentCommand:ProvideDocumentCommand:DeclareDocumentCommand\
5770:NewExpandableDocumentCommand:RenewExpandableDocumentCommand\
5771:ProvideExpandableDocumentCommand:DeclareExpandableDocumentCommand\
5772:NewDocumentEnvironment:RenewDocumentEnvironment\
5773:ProvideDocumentEnvironment:DeclareDocumentEnvironment:csdef\
5774:csedef:csgdef:csxdef:csletcs:cslet:letcs:let:cs_new_protected_nopar\
5775:cs_new_protected:cs_new_nopar:cs_new_eq:cs_new:cs_set_protected_nopar\
5776:cs_set_protected:cs_set_nopar:cs_set_eq:cs_set:cs_gset_protected_nopar\
5777:cs_gset_protected:cs_gset_nopar:cs_gset_eq:cs_gset\
5778:cs_generate_from_arg_count:cs_generate_variant";
5752 5779
5753static void TEX_decode_env (const char *, const char *); 5780static void TEX_decode_env (const char *, const char *);
5754 5781
@@ -5807,19 +5834,139 @@ TeX_commands (FILE *inf)
5807 { 5834 {
5808 char *p; 5835 char *p;
5809 ptrdiff_t namelen, linelen; 5836 ptrdiff_t namelen, linelen;
5810 bool opgrp = false; 5837 bool opgrp = false, one_esc = false, is_explthree = false;
5811 5838
5812 cp = skip_spaces (cp + key->len); 5839 cp = skip_spaces (cp + key->len);
5840
5841 /* 1. The canonical expl3 syntax looks something like this:
5842 \cs_new:Npn \__hook_tl_gput:Nn { \ERROR }. First, if we
5843 want to tag any such commands, we include only the part
5844 before the colon (cs_new) in TEX_defenv or TEXTAGS. Second,
5845 etags skips the argument specifier (including the colon)
5846 after the tag token, so that it doesn't become the tag name.
5847 Third, we set the boolean 'is_explthree' to true so that we
5848 can remove the argument specifier from the actual tag name
5849 (__hook_tl_gput). This all allows us to include expl3
5850 constructs in TEX_defenv or in the environment variable
5851 TEXTAGS without requiring a change of separator, and it also
5852 allows us to find the definition of variant commands (with
5853 different argument specifiers) defined using, for example,
5854 \cs_generate_variant:Nn. Please note that the expl3 spec
5855 requires etags to pay more attention to whitespace in the
5856 code.
5857
5858 2. We also automatically remove the asterisk from starred
5859 variants of all commands, without the need to include the
5860 starred commands explicitly in TEX_defenv or TEXTAGS. */
5861 if (*cp == ':')
5862 {
5863 while (!c_isspace (*cp) && *cp != TEX_opgrp)
5864 cp++;
5865 cp = skip_spaces (cp);
5866 is_explthree = true;
5867 }
5868 else if (*cp == '*')
5869 cp++;
5870
5871 /* Skip the optional arguments to commands in the tags list so
5872 that these arguments don't end up as the name of the tag.
5873 The name will instead come from the argument in curly braces
5874 that follows the optional ones. The '\let' command gets
5875 special treatment. */
5876 while (*cp != '\0' && *cp != '%'
5877 && !streq (key->buffer, "let"))
5878 {
5879 if (*cp == '[')
5880 {
5881 while (*cp != ']' && *cp != '\0' && *cp != '%')
5882 cp++;
5883 }
5884 else if (*cp == '(')
5885 {
5886 while (*cp != ')' && *cp != '\0' && *cp != '%')
5887 cp++;
5888 }
5889 else if (*cp == ']' || *cp == ')')
5890 cp++;
5891 else
5892 break;
5893 }
5813 if (*cp == TEX_opgrp) 5894 if (*cp == TEX_opgrp)
5814 { 5895 {
5815 opgrp = true; 5896 opgrp = true;
5816 cp++; 5897 cp++;
5898 cp = skip_spaces (cp); /* For expl3 code. */
5817 } 5899 }
5900
5901 /* Removing the TeX escape character from tag names simplifies
5902 things for editors finding tagged commands in TeX buffers.
5903 This applies to Emacs but also to the tag-finding behavior
5904 of at least some of the editors that use ctags, though in
5905 the latter case this will remain suboptimal. The
5906 undocumented ctags option '--no-duplicates' may help. */
5907 if (*cp == TEX_esc)
5908 {
5909 cp++;
5910 one_esc = true;
5911 }
5912
5913 /* Testing !c_isspace && !c_ispunct is simpler, but halts
5914 processing at too many places. The list as it stands tries
5915 both to ensure that tag names will derive from macro names
5916 rather than from optional parameters to those macros, and
5917 also to return findable names while still allowing for
5918 unorthodox constructs. */
5818 for (p = cp; 5919 for (p = cp;
5819 (!c_isspace (*p) && *p != '#' && 5920 (!c_isspace (*p) && *p != '#' && *p != '=' &&
5820 *p != TEX_opgrp && *p != TEX_clgrp); 5921 *p != '[' && *p != '(' && *p != TEX_opgrp &&
5922 *p != TEX_clgrp && *p != '"' && *p != '\'' &&
5923 *p != '%' && *p != ',' && *p != '|' && *p != '$');
5821 p++) 5924 p++)
5822 continue; 5925 /* In expl3 code we remove the argument specification from
5926 the tag name. More generally we allow only one (deleted)
5927 escape char in a tag name, which (primarily) enables
5928 tagging a TeX command's different, possibly temporary,
5929 '\let' bindings. */
5930 if (is_explthree && *p == ':')
5931 break;
5932 else if (*p == TEX_esc)
5933 { /* Second part of test is for, e.g., \cslet. */
5934 if (!one_esc && !opgrp)
5935 {
5936 one_esc = true;
5937 continue;
5938 }
5939 else
5940 break;
5941 }
5942 else
5943 continue;
5944 /* For TeX files, tags without a name are basically cruft, and
5945 in some situations they can produce spurious and confusing
5946 matches. Try to catch as many cases as possible where a
5947 command name is of the form '\(', but avoid, as far as
5948 possible, the spurious matches. */
5949 if (p == cp)
5950 {
5951 switch (*p)
5952 { /* Include =? */
5953 case '(': case '[': case '"': case '\'':
5954 case '\\': case '!': case '=': case ',':
5955 case '|': case '$':
5956 p++;
5957 break;
5958 case '{': case '}': case '<': case '>':
5959 if (!opgrp)
5960 {
5961 p++;
5962 if (*p == '\0' || *p == '%')
5963 goto tex_next_line;
5964 }
5965 break;
5966 default:
5967 break;
5968 }
5969 }
5823 namelen = p - cp; 5970 namelen = p - cp;
5824 linelen = lb.len; 5971 linelen = lb.len;
5825 if (!opgrp || *p == TEX_clgrp) 5972 if (!opgrp || *p == TEX_clgrp)
@@ -5828,9 +5975,18 @@ TeX_commands (FILE *inf)
5828 p++; 5975 p++;
5829 linelen = p - lb.buffer + 1; 5976 linelen = p - lb.buffer + 1;
5830 } 5977 }
5831 make_tag (cp, namelen, true, 5978 if (namelen)
5832 lb.buffer, linelen, lineno, linecharno); 5979 make_tag (cp, namelen, true,
5833 goto tex_next_line; /* We only tag a line once */ 5980 lb.buffer, linelen, lineno, linecharno);
5981 /* Lines with more than one \def or \let are surprisingly
5982 common in TeX files, especially in the system files that
5983 form the basis of the various TeX formats. This tags them
5984 all. */
5985 /* goto tex_next_line; /\* We only tag a line once *\/ */
5986 while (*cp != '\0' && *cp != '%' && *cp != TEX_esc)
5987 cp++;
5988 if (*cp != TEX_esc)
5989 goto tex_next_line;
5834 } 5990 }
5835 } 5991 }
5836 tex_next_line: 5992 tex_next_line:
diff --git a/lisp/textmodes/tex-mode.el b/lisp/textmodes/tex-mode.el
index 4bc2c840cdb..ec0c0c47a2d 100644
--- a/lisp/textmodes/tex-mode.el
+++ b/lisp/textmodes/tex-mode.el
@@ -637,6 +637,14 @@ An alternative value is \" . \", if you use a font with a narrow period."
637 3 '(tex-font-lock-append-prop 'bold) 'append))))) 637 3 '(tex-font-lock-append-prop 'bold) 'append)))))
638 "Gaudy expressions to highlight in TeX modes.") 638 "Gaudy expressions to highlight in TeX modes.")
639 639
640(defvar-local tex-expl-region-list nil
641 "List of region boundaries where expl3 syntax is active.
642It will be nil in buffers visiting files which use expl3 syntax
643throughout, for example, expl3 classes or packages.")
644
645(defvar-local tex-expl-buffer-p nil
646 "Non-nil in buffers using expl3 syntax throughout.")
647
640(defun tex-font-lock-suscript (pos) 648(defun tex-font-lock-suscript (pos)
641 (unless (or (memq (get-text-property pos 'face) 649 (unless (or (memq (get-text-property pos 'face)
642 '(font-lock-constant-face font-lock-builtin-face 650 '(font-lock-constant-face font-lock-builtin-face
@@ -646,7 +654,17 @@ An alternative value is \" . \", if you use a font with a narrow period."
646 (pos pos)) 654 (pos pos))
647 (while (eq (char-before pos) ?\\) 655 (while (eq (char-before pos) ?\\)
648 (setq pos (1- pos) odd (not odd))) 656 (setq pos (1- pos) odd (not odd)))
649 odd)) 657 odd)
658 ;; Check if POS is in an expl3 syntax region or an expl3 buffer
659 (when (eq (char-after pos) ?_)
660 (or tex-expl-buffer-p
661 (and
662 tex-expl-region-list
663 (catch 'result
664 (dolist (range tex-expl-region-list)
665 (and (> pos (car range))
666 (< pos (cdr range))
667 (throw 'result t))))))))
650 (if (eq (char-after pos) ?_) 668 (if (eq (char-after pos) ?_)
651 `(face subscript display (raise ,(car tex-font-script-display))) 669 `(face subscript display (raise ,(car tex-font-script-display)))
652 `(face superscript display (raise ,(cadr tex-font-script-display)))))) 670 `(face superscript display (raise ,(cadr tex-font-script-display))))))
@@ -1290,8 +1308,16 @@ Entering SliTeX mode runs the hook `text-mode-hook', then the hook
1290 #'tex--prettify-symbols-compose-p) 1308 #'tex--prettify-symbols-compose-p)
1291 (setq-local syntax-propertize-function 1309 (setq-local syntax-propertize-function
1292 (syntax-propertize-rules latex-syntax-propertize-rules)) 1310 (syntax-propertize-rules latex-syntax-propertize-rules))
1311 ;; Don't add extra processing to `syntax-propertize' in files where
1312 ;; expl3 syntax is always active.
1313 :after-hook (progn (tex-expl-buffer-parse)
1314 (unless tex-expl-buffer-p
1315 (add-hook 'syntax-propertize-extend-region-functions
1316 #'tex-expl-region-set nil t)))
1293 ;; TABs in verbatim environments don't do what you think. 1317 ;; TABs in verbatim environments don't do what you think.
1294 (setq-local indent-tabs-mode nil) 1318 (setq-local indent-tabs-mode nil)
1319 ;; Set up xref backend in TeX buffers.
1320 (add-hook 'xref-backend-functions #'tex--xref-backend nil t)
1295 ;; Other vars that should be buffer-local. 1321 ;; Other vars that should be buffer-local.
1296 (make-local-variable 'tex-command) 1322 (make-local-variable 'tex-command)
1297 (make-local-variable 'tex-start-of-header) 1323 (make-local-variable 'tex-start-of-header)
@@ -1937,6 +1963,36 @@ Mark is left at original location."
1937 (forward-sexp 1)))))) 1963 (forward-sexp 1))))))
1938 (message "%s words" count)))) 1964 (message "%s words" count))))
1939 1965
1966(defun tex-expl-buffer-parse ()
1967 "Identify buffers using expl3 syntax throughout."
1968 (save-excursion
1969 (goto-char (point-min))
1970 (when (tex-search-noncomment
1971 (re-search-forward
1972 "\\\\\\(?:ExplFile\\|ProvidesExpl\\|__xparse_file\\)"
1973 nil t))
1974 (setq tex-expl-buffer-p t))))
1975
1976(defun tex-expl-region-set (_beg _end)
1977 "Create a list of regions where expl3 syntax is active.
1978This function updates the list whenever `syntax-propertize' runs, and
1979stores it in the buffer-local variable `tex-expl-region-list'. The list
1980will always be nil when the buffer visits an expl3 file, for example, an
1981expl3 class or package, where the entire file uses expl3 syntax."
1982 (unless syntax-ppss--updated-cache;; Stop forward search running twice.
1983 (setq tex-expl-region-list nil)
1984 ;; Leaving this test here allows users to set `tex-expl-buffer-p'
1985 ;; independently of the mode's automatic detection of an expl3 file.
1986 (unless tex-expl-buffer-p
1987 (goto-char (point-min))
1988 (let ((case-fold-search nil))
1989 (while (tex-search-noncomment
1990 (search-forward "\\ExplSyntaxOn" nil t))
1991 (let ((new-beg (point))
1992 (new-end (or (tex-search-noncomment
1993 (search-forward "\\ExplSyntaxOff" nil t))
1994 (point-max))))
1995 (push (cons new-beg new-end) tex-expl-region-list)))))))
1940 1996
1941 1997
1942;;; Invoking TeX in an inferior shell. 1998;;; Invoking TeX in an inferior shell.
@@ -3743,6 +3799,306 @@ There might be text before point."
3743 (process-send-region tex-chktex--process (point-min) (point-max)) 3799 (process-send-region tex-chktex--process (point-min) (point-max))
3744 (process-send-eof tex-chktex--process)))) 3800 (process-send-eof tex-chktex--process))))
3745 3801
3802
3803;;; Xref backend
3804
3805;; Here we lightly adapt the default etags backend for xref so that
3806;; the main xref user commands (including `xref-find-definitions',
3807;; `xref-find-apropos', and `xref-find-references' [on M-., C-M-., and
3808;; M-?, respectively]) work in TeX buffers. The only methods we
3809;; actually modify are `xref-backend-identifier-at-point' and
3810;; `xref-backend-references'. Many of the complications here, and in
3811;; `etags' itself, are due to the necessity of parsing both the old
3812;; TeX syntax and the new expl3 syntax, which will continue to appear
3813;; together in documents for the foreseeable future. Synchronizing
3814;; Emacs and `etags' this way aims to improve the user experience "out
3815;; of the box."
3816
3817(defvar tex-thingatpt-exclude-chars '(?\\ ?\{ ?\})
3818 "Exclude these chars by default from TeX thing-at-point.
3819
3820The TeX `xref-backend-identifier-at-point' method uses the characters
3821listed in this variable to decide on the default search string to
3822present to the user who calls an `xref' command. These characters
3823become part of a regexp which always excludes them from that default
3824string. For the `xref' commands to function properly in TeX buffers, at
3825least the TeX escape and the two TeX grouping characters should be
3826listed here. Should your TeX documents contain other characters which
3827you want to exclude by default, then you can add them to the list,
3828though you may wish to consult the functions
3829`tex-thingatpt--beginning-of-symbol' and `tex-thingatpt--end-of-symbol'
3830to see what the regexp already contains. If your documents contain
3831non-standard escape and grouping characters, then you can replace the
3832three listed here with your own, thereby allowing the three standard
3833characters to appear by default in search strings. Please be aware,
3834however, that the `etags' program only recognizes `\\' (92) and `!' (33)
3835as escape characters in TeX documents, and if it detects the latter it
3836also uses `<>' as the TeX grouping construct rather than `{}'. Setting
3837the escape and grouping chars to anything other than `\\=\\{}' or `!<>'
3838will not be useful without changes to `etags', at least for commands
3839that search tags tables, such as \\[xref-find-definitions] and \
3840\\[xref-find-apropos].
3841
3842Should you wish to change the defaults, please also be aware that,
3843without further modifications to tex-mode.el, the usual text-parsing
3844routines for `font-lock' and the like won't work correctly, as the
3845default escape and grouping characters are currently hard coded in many
3846places.")
3847
3848;; Populate `semantic-symref-filepattern-alist' for the in-tree modes;
3849;; AUCTeX is doing the same for its modes.
3850(with-eval-after-load 'semantic/symref/grep
3851 (defvar semantic-symref-filepattern-alist)
3852 (push '(latex-mode "*.[tT]e[xX]" "*.ltx" "*.sty" "*.cl[so]"
3853 "*.bbl" "*.drv" "*.hva")
3854 semantic-symref-filepattern-alist)
3855 (push '(plain-tex-mode "*.[tT]e[xX]" "*.ins")
3856 semantic-symref-filepattern-alist)
3857 (push '(doctex-mode "*.dtx") semantic-symref-filepattern-alist))
3858
3859(defun tex--xref-backend () 'tex-etags)
3860
3861(cl-defmethod xref-backend-identifier-at-point ((_backend (eql 'tex-etags)))
3862 (require 'etags)
3863 (tex--thing-at-point))
3864
3865;; The detection of `_' and `:' is a primitive method for determining
3866;; whether point is on an expl3 construct. It may fail in some
3867;; instances.
3868(defun tex--thing-at-point ()
3869 "Demarcate `thing-at-point' for the TeX `xref' backend."
3870 (let ((bounds (tex--bounds-of-symbol-at-point)))
3871 (when bounds
3872 (let ((texsym (buffer-substring-no-properties (car bounds) (cdr bounds))))
3873 (if (and (not (string-match-p "reference" (symbol-name this-command)))
3874 (seq-contains-p texsym ?_)
3875 (seq-contains-p texsym ?:))
3876 (seq-take texsym (seq-position texsym ?:))
3877 texsym)))))
3878
3879(defun tex-thingatpt--beginning-of-symbol ()
3880 (and
3881 (re-search-backward (concat "[]["
3882 (mapconcat #'regexp-quote
3883 (mapcar #'char-to-string
3884 tex-thingatpt-exclude-chars))
3885 "\"*`'#=&()%,|$[:cntrl:][:blank:]]"))
3886 (forward-char)))
3887
3888(defun tex-thingatpt--end-of-symbol ()
3889 (and
3890 (re-search-forward (concat "[]["
3891 (mapconcat #'regexp-quote
3892 (mapcar #'char-to-string
3893 tex-thingatpt-exclude-chars))
3894 "\"*`'#=&()%,|$[:cntrl:][:blank:]]"))
3895 (backward-char)))
3896
3897(defun tex--bounds-of-symbol-at-point ()
3898 "Simplify `bounds-of-thing-at-point' for TeX `xref' backend."
3899 (let ((orig (point)))
3900 (ignore-errors
3901 (save-excursion
3902 (tex-thingatpt--end-of-symbol)
3903 (tex-thingatpt--beginning-of-symbol)
3904 (let ((beg (point)))
3905 (if (<= beg orig)
3906 (let ((real-end
3907 (progn
3908 (tex-thingatpt--end-of-symbol)
3909 (point))))
3910 (cond ((and (<= orig real-end) (< beg real-end))
3911 (cons beg real-end))
3912 ((and (= orig real-end) (= beg real-end))
3913 (cons beg (1+ beg)))))))))));; For 1-char TeX commands.
3914
3915(cl-defmethod xref-backend-identifier-completion-table ((_backend
3916 (eql 'tex-etags)))
3917 (xref-backend-identifier-completion-table 'etags))
3918
3919(cl-defmethod xref-backend-identifier-completion-ignore-case ((_backend
3920 (eql
3921 'tex-etags)))
3922 (xref-backend-identifier-completion-ignore-case 'etags))
3923
3924(cl-defmethod xref-backend-definitions ((_backend (eql 'tex-etags)) symbol)
3925 (xref-backend-definitions 'etags symbol))
3926
3927(cl-defmethod xref-backend-apropos ((_backend (eql 'tex-etags)) pattern)
3928 (xref-backend-apropos 'etags pattern))
3929
3930;; The `xref-backend-references' method requires more code than the
3931;; others for at least two main reasons: TeX authors have typically been
3932;; free in their invention of new file types with new suffixes, and they
3933;; have also tended sometimes to include non-symbol characters in
3934;; command names. When combined with the default Semantic Symbol
3935;; Reference API, these two characteristics of TeX code mean that a
3936;; command like `xref-find-references' would often fail to find any hits
3937;; for a symbol at point, including the one under point in the current
3938;; buffer, or it would find only some instances and skip others.
3939
3940(defun tex-find-references-syntax-table ()
3941 (let ((st (if (boundp 'TeX-mode-syntax-table)
3942 (make-syntax-table TeX-mode-syntax-table)
3943 (make-syntax-table tex-mode-syntax-table))))
3944 st))
3945
3946(defvar tex--xref-syntax-fun nil)
3947
3948(defun tex-xref-syntax-function (str beg end)
3949 "Provide a bespoke `syntax-propertize-function' for \\[xref-find-references]."
3950 (let* (grpb tempstr
3951 (shrtstr (if end
3952 (progn
3953 (setq tempstr (seq-take str (1- (length str))))
3954 (if beg
3955 (setq tempstr (seq-drop tempstr 1))
3956 tempstr))
3957 (seq-drop str 1)))
3958 (grpa (if (and beg end)
3959 (prog1
3960 (list 1 "_")
3961 (setq grpb (list 2 "_")))
3962 (list 1 "_")))
3963 (re (concat beg (regexp-quote shrtstr) end))
3964 (temp-rule (if grpb
3965 (list re grpa grpb)
3966 (list re grpa))))
3967 ;; Simple benchmarks suggested that the speed-up from compiling this
3968 ;; function was nearly nil, so `eval' and its non-byte-compiled
3969 ;; function remain.
3970 (setq tex--xref-syntax-fun (eval
3971 `(syntax-propertize-rules ,temp-rule)))))
3972
3973(defun tex--collect-file-extensions ()
3974 "Gather TeX file extensions from `auto-mode-alist'."
3975 (let* ((mlist (when (rassq major-mode auto-mode-alist)
3976 (seq-filter
3977 (lambda (elt)
3978 (eq (cdr elt) major-mode))
3979 auto-mode-alist)))
3980 (lcsym (intern-soft (downcase (symbol-name major-mode))))
3981 (lclist (and lcsym
3982 (not (eq lcsym major-mode))
3983 (rassq lcsym auto-mode-alist)
3984 (seq-filter
3985 (lambda (elt)
3986 (eq (cdr elt) lcsym))
3987 auto-mode-alist)))
3988 (shortsym (when (stringp mode-name)
3989 (intern-soft (concat (string-trim-right mode-name "/.*")
3990 "-mode"))))
3991 (lcshortsym (when (stringp mode-name)
3992 (intern-soft (downcase
3993 (concat
3994 (string-trim-right mode-name "/.*")
3995 "-mode")))))
3996 (shlist (and shortsym
3997 (not (eq shortsym major-mode))
3998 (not (eq shortsym lcsym))
3999 (rassq shortsym auto-mode-alist)
4000 (seq-filter
4001 (lambda (elt)
4002 (eq (cdr elt) shortsym))
4003 auto-mode-alist)))
4004 (lcshlist (and lcshortsym
4005 (not (eq lcshortsym major-mode))
4006 (not (eq lcshortsym lcsym))
4007 (rassq lcshortsym auto-mode-alist)
4008 (seq-filter
4009 (lambda (elt)
4010 (eq (cdr elt) lcshortsym))
4011 auto-mode-alist)))
4012 (exts (when (or mlist lclist shlist lcshlist)
4013 (seq-union (seq-map #'car lclist)
4014 (seq-union (seq-map #'car mlist)
4015 (seq-union (seq-map #'car lcshlist)
4016 (seq-map #'car shlist))))))
4017 (ed-exts (when exts
4018 (seq-map
4019 (lambda (elt)
4020 (concat "*" (string-trim elt "\\\\" "\\\\'")))
4021 exts))))
4022 ed-exts))
4023
4024(defvar tex--buffers-list nil)
4025(defvar-local tex--old-syntax-function nil)
4026
4027(cl-defmethod xref-backend-references ((_backend (eql 'tex-etags)) identifier)
4028 "Find references of IDENTIFIER in TeX buffers and files."
4029 (require 'semantic/symref/grep)
4030 (defvar semantic-symref-filepattern-alist)
4031 (let (bufs texbufs
4032 (mode major-mode))
4033 (dolist (buf (buffer-list))
4034 (if (eq (buffer-local-value 'major-mode buf) mode)
4035 (push buf bufs)
4036 (when (string-match-p ".*\\.[tT]e[xX]" (buffer-name buf))
4037 (push buf texbufs))))
4038 (unless (seq-set-equal-p tex--buffers-list bufs)
4039 (let* ((amalist (tex--collect-file-extensions))
4040 (extlist (alist-get mode semantic-symref-filepattern-alist))
4041 (extlist-new (seq-uniq
4042 (seq-union amalist extlist #'string-match-p))))
4043 (setq tex--buffers-list bufs)
4044 (dolist (buf bufs)
4045 (when-let ((fbuf (buffer-file-name buf))
4046 (ext (file-name-extension fbuf))
4047 (finext (concat "*." ext))
4048 ((not (seq-find (lambda (elt) (string-match-p elt finext))
4049 extlist-new)))
4050 ((push finext extlist-new)))))
4051 (unless (seq-set-equal-p extlist-new extlist)
4052 (setf (alist-get mode semantic-symref-filepattern-alist)
4053 extlist-new))))
4054 (let* (setsyntax
4055 (punct (with-syntax-table (tex-find-references-syntax-table)
4056 (seq-positions identifier (list ?w ?_)
4057 (lambda (elt sycode)
4058 (not (memq (char-syntax elt) sycode))))))
4059 (end (and punct
4060 (memq (1- (length identifier)) punct)
4061 (> (length identifier) 1)
4062 (concat "\\("
4063 (regexp-quote
4064 (string (elt identifier
4065 (1- (length identifier)))))
4066 "\\)")))
4067 (beg (and punct
4068 (memq 0 punct)
4069 (concat "\\("
4070 (regexp-quote (string (elt identifier 0)))
4071 "\\)")))
4072 (text-mode-hook
4073 (if (or end beg)
4074 (progn
4075 (tex-xref-syntax-function identifier beg end)
4076 (setq setsyntax (lambda ()
4077 (setq-local syntax-propertize-function
4078 tex--xref-syntax-fun)
4079 (setq-local TeX-style-hook-applied-p t)))
4080 (cons setsyntax text-mode-hook))
4081 text-mode-hook)))
4082 (unless (memq 'doctex-mode (derived-mode-all-parents mode))
4083 (setq bufs (append texbufs bufs)))
4084 (when (or end beg)
4085 (dolist (buf bufs)
4086 (with-current-buffer buf
4087 (unless (local-variable-p 'tex--old-syntax-function)
4088 (setq tex--old-syntax-function syntax-propertize-function))
4089 (setq-local syntax-propertize-function
4090 tex--xref-syntax-fun)
4091 (syntax-ppss-flush-cache (point-min)))))
4092 (unwind-protect
4093 (xref-backend-references nil identifier)
4094 (when (or end beg)
4095 (dolist (buf bufs)
4096 (with-current-buffer buf
4097 (when buffer-file-truename
4098 (setq-local syntax-propertize-function
4099 tex--old-syntax-function)
4100 (syntax-ppss-flush-cache (point-min))))))))))
4101
3746(make-obsolete-variable 'tex-mode-load-hook 4102(make-obsolete-variable 'tex-mode-load-hook
3747 "use `with-eval-after-load' instead." "28.1") 4103 "use `with-eval-after-load' instead." "28.1")
3748(run-hooks 'tex-mode-load-hook) 4104(run-hooks 'tex-mode-load-hook)