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.
This commit is contained in:
parent
98e582e74a
commit
b44c00669a
4 changed files with 572 additions and 20 deletions
|
@ -2549,6 +2549,15 @@ identifier, showing the file name and the line where the identifier is
|
||||||
referenced. The XREF mode commands are available in this buffer, see
|
referenced. The XREF mode commands are available in this buffer, see
|
||||||
@ref{Xref Commands}.
|
@ref{Xref Commands}.
|
||||||
|
|
||||||
|
When invoked in a buffer whose major mode uses the @code{etags} backend,
|
||||||
|
@kbd{M-?} searches files and buffers whose major mode matches that of
|
||||||
|
the original buffer. It guesses that mode from file extensions, so if
|
||||||
|
@kbd{M-?} seems to be skipping relevant buffers or files, try
|
||||||
|
customizing either the variable @code{semantic-symref-filepattern-alist}
|
||||||
|
(if your buffer's major mode already has an entry in it), or
|
||||||
|
@code{auto-mode-alist} (if not), thereby informing @code{xref} of the
|
||||||
|
missing extensions (@pxref{Choosing Modes}).
|
||||||
|
|
||||||
@vindex xref-auto-jump-to-first-xref
|
@vindex xref-auto-jump-to-first-xref
|
||||||
If the value of the variable @code{xref-auto-jump-to-first-xref} is
|
If the value of the variable @code{xref-auto-jump-to-first-xref} is
|
||||||
@code{t}, @code{xref-find-references} automatically jumps to the first
|
@code{t}, @code{xref-find-references} automatically jumps to the first
|
||||||
|
@ -2767,10 +2776,32 @@ Tags for variables and functions in classes are named
|
||||||
@item
|
@item
|
||||||
In @LaTeX{} documents, the arguments for @code{\chapter},
|
In @LaTeX{} documents, the arguments for @code{\chapter},
|
||||||
@code{\section}, @code{\subsection}, @code{\subsubsection},
|
@code{\section}, @code{\subsection}, @code{\subsubsection},
|
||||||
@code{\eqno}, @code{\label}, @code{\ref}, @code{\cite},
|
@code{\eqno}, @code{\label}, @code{\ref}, @code{\Ref}, @code{\footref},
|
||||||
@code{\bibitem}, @code{\part}, @code{\appendix}, @code{\entry},
|
@code{\cite}, @code{\bibitem}, @code{\part}, @code{\appendix},
|
||||||
@code{\index}, @code{\def}, @code{\newcommand}, @code{\renewcommand},
|
@code{\entry}, @code{\index}, @code{\def}, @code{\edef}, @code{\gdef},
|
||||||
@code{\newenvironment} and @code{\renewenvironment} are tags.
|
@code{\xdef}, @code{\newcommand}, @code{\renewcommand},
|
||||||
|
@code{\newenvironment}, @code{\renewenvironment},
|
||||||
|
@code{\DeclareRobustCommand}, @code{\newrobustcmd},
|
||||||
|
@code{\renewrobustcmd}, @code{\providecommand},
|
||||||
|
@code{\providerobustcmd}, @code{\NewDocumentCommand},
|
||||||
|
@code{\RenewDocumentCommand}, @code{\ProvideDocumentCommand},
|
||||||
|
@code{\DeclareDocumentCommand}, @code{\NewExpandableDocumentCommand},
|
||||||
|
@code{\RenewExpandableDocumentCommand},
|
||||||
|
@code{\ProvideExpandableDocumentCommand},
|
||||||
|
@code{\DeclareExpandableDocumentCommand},
|
||||||
|
@code{\NewDocumentEnvironment}, @code{\RenewDocumentEnvironment},
|
||||||
|
@code{\ProvideDocumentEnvironment}, @code{\DeclareDocumentEnvironment},
|
||||||
|
@code{\csdef}, @code{\csedef}, @code{\csgdef}, @code{\csxdef},
|
||||||
|
@code{\csletcs}, @code{\cslet}, @code{\letcs}, @code{\let},
|
||||||
|
@code{\cs_new_protected_nopar}, @code{\cs_new_protected},
|
||||||
|
@code{\cs_new_nopar}, @code{\cs_new_eq}, @code{\cs_new},
|
||||||
|
@code{\cs_set_protected_nopar}, @code{\cs_set_protected},
|
||||||
|
@code{\cs_set_nopar}, @code{\cs_set_eq}, @code{\cs_set},
|
||||||
|
@code{\cs_gset_protected_nopar}, @code{\cs_gset_protected},
|
||||||
|
@code{\cs_gset_nopar}, @code{\cs_gset_eq}, @code{\cs_gset},
|
||||||
|
@code{\cs_generate_from_arg_count}, and @code{\cs_generate_variant} are
|
||||||
|
tags. So too are the arguments of any starred variants of these
|
||||||
|
commands.
|
||||||
|
|
||||||
Other commands can make tags as well, if you specify them in the
|
Other commands can make tags as well, if you specify them in the
|
||||||
environment variable @env{TEXTAGS} before invoking @command{etags}. The
|
environment variable @env{TEXTAGS} before invoking @command{etags}. The
|
||||||
|
|
9
etc/NEWS
9
etc/NEWS
|
@ -343,6 +343,15 @@ the 'grep' results editable. The edits will be reflected in the buffer
|
||||||
visiting the originating file. Typing 'C-c C-c' will leave the Grep
|
visiting the originating file. Typing 'C-c C-c' will leave the Grep
|
||||||
Edit mode.
|
Edit mode.
|
||||||
|
|
||||||
|
** TeX modes
|
||||||
|
|
||||||
|
+++
|
||||||
|
*** New xref backend for TeX modes.
|
||||||
|
The new backend ('tex-etags') is on by default, and improves the
|
||||||
|
functionality of the standard 'xref' commands in TeX buffers. You can
|
||||||
|
restore the standard 'etags' backend with the 'M-x xref-etags-mode'
|
||||||
|
toggle.
|
||||||
|
|
||||||
|
|
||||||
* New Modes and Packages in Emacs 31.1
|
* New Modes and Packages in Emacs 31.1
|
||||||
|
|
||||||
|
|
186
lib-src/etags.c
186
lib-src/etags.c
|
@ -793,11 +793,27 @@ variables set with 'set!' at top level in the file.";
|
||||||
static const char *TeX_suffixes [] =
|
static const char *TeX_suffixes [] =
|
||||||
{ "bib", "clo", "cls", "ltx", "sty", "TeX", "tex", NULL };
|
{ "bib", "clo", "cls", "ltx", "sty", "TeX", "tex", NULL };
|
||||||
static const char TeX_help [] =
|
static const char TeX_help [] =
|
||||||
"In LaTeX text, the argument of any of the commands '\\chapter',\n\
|
"In LaTeX text, the argument of the commands '\\chapter', '\\section',\n\
|
||||||
'\\section', '\\subsection', '\\subsubsection', '\\eqno', '\\label',\n\
|
'\\subsection', '\\subsubsection', '\\eqno', '\\label', '\\ref',\n\
|
||||||
'\\ref', '\\cite', '\\bibitem', '\\part', '\\appendix', '\\entry',\n\
|
'\\Ref', '\\footref', '\\cite', '\\bibitem', '\\part', '\\appendix',\n\
|
||||||
'\\index', '\\def', '\\newcommand', '\\renewcommand',\n\
|
'\\entry', '\\index', '\\def', '\\edef', '\\gdef', '\\xdef',\n\
|
||||||
'\\newenvironment' or '\\renewenvironment' is a tag.\n\
|
'\\newcommand', '\\renewcommand', '\\newrobustcmd', '\\renewrobustcmd',\n\
|
||||||
|
'\\newenvironment', '\\renewenvironment', '\\DeclareRobustCommand',\n\
|
||||||
|
'\\providecommand', '\\providerobustcmd', '\\NewDocumentCommand',\n\
|
||||||
|
'\\RenewDocumentCommand', '\\ProvideDocumentCommand',\n\
|
||||||
|
'\\DeclareDocumentCommand', '\\NewExpandableDocumentCommand',\n\
|
||||||
|
'\\RenewExpandableDocumentCommand', '\\ProvideExpandableDocumentCommand',\n\
|
||||||
|
'\\DeclareExpandableDocumentCommand', '\\NewDocumentEnvironment',\n\
|
||||||
|
'\\RenewDocumentEnvironment', '\\ProvideDocumentEnvironment',\n\
|
||||||
|
'\\DeclareDocumentEnvironment','\\csdef', '\\csedef', '\\csgdef',\n\
|
||||||
|
'\\csxdef', '\\csletcs', '\\cslet', '\\letcs', '\\let',\n\
|
||||||
|
'\\cs_new_protected_nopar', '\\cs_new_protected', '\\cs_new_nopar',\n\
|
||||||
|
'\\cs_new_eq', '\\cs_new', '\\cs_set_protected_nopar',\n\
|
||||||
|
'\\cs_set_protected', '\\cs_set_nopar', '\\cs_set_eq', '\\cs_set',\n\
|
||||||
|
'\\cs_gset_protected_nopar', '\\cs_gset_protected', '\\cs_gset_nopar',\n\
|
||||||
|
'\\cs_gset_eq', '\\cs_gset', '\\cs_generate_from_arg_count', or\n\
|
||||||
|
'\\cs_generate_variant' is a tag. So is the argument of any starred\n\
|
||||||
|
variant of these commands.\n\
|
||||||
\n\
|
\n\
|
||||||
Other commands can be specified by setting the environment variable\n\
|
Other commands can be specified by setting the environment variable\n\
|
||||||
'TEXTAGS' to a colon-separated list like, for example,\n\
|
'TEXTAGS' to a colon-separated list like, for example,\n\
|
||||||
|
@ -5746,9 +5762,20 @@ static linebuffer *TEX_toktab = NULL; /* Table with tag tokens */
|
||||||
/* Default set of control sequences to put into TEX_toktab.
|
/* Default set of control sequences to put into TEX_toktab.
|
||||||
The value of environment var TEXTAGS is prepended to this. */
|
The value of environment var TEXTAGS is prepended to this. */
|
||||||
static const char *TEX_defenv = "\
|
static const char *TEX_defenv = "\
|
||||||
:chapter:section:subsection:subsubsection:eqno:label:ref:cite:bibitem\
|
:label:ref:Ref:footref:chapter:section:subsection:subsubsection:eqno:cite\
|
||||||
:part:appendix:entry:index:def\
|
:bibitem:part:appendix:entry:index:def:edef:gdef:xdef:newcommand:renewcommand\
|
||||||
:newcommand:renewcommand:newenvironment:renewenvironment";
|
:newenvironment:renewenvironment:DeclareRobustCommand:renewrobustcmd\
|
||||||
|
:newrobustcmd:providecommand:providerobustcmd:NewDocumentCommand\
|
||||||
|
:RenewDocumentCommand:ProvideDocumentCommand:DeclareDocumentCommand\
|
||||||
|
:NewExpandableDocumentCommand:RenewExpandableDocumentCommand\
|
||||||
|
:ProvideExpandableDocumentCommand:DeclareExpandableDocumentCommand\
|
||||||
|
:NewDocumentEnvironment:RenewDocumentEnvironment\
|
||||||
|
:ProvideDocumentEnvironment:DeclareDocumentEnvironment:csdef\
|
||||||
|
:csedef:csgdef:csxdef:csletcs:cslet:letcs:let:cs_new_protected_nopar\
|
||||||
|
:cs_new_protected:cs_new_nopar:cs_new_eq:cs_new:cs_set_protected_nopar\
|
||||||
|
:cs_set_protected:cs_set_nopar:cs_set_eq:cs_set:cs_gset_protected_nopar\
|
||||||
|
:cs_gset_protected:cs_gset_nopar:cs_gset_eq:cs_gset\
|
||||||
|
:cs_generate_from_arg_count:cs_generate_variant";
|
||||||
|
|
||||||
static void TEX_decode_env (const char *, const char *);
|
static void TEX_decode_env (const char *, const char *);
|
||||||
|
|
||||||
|
@ -5807,19 +5834,139 @@ TeX_commands (FILE *inf)
|
||||||
{
|
{
|
||||||
char *p;
|
char *p;
|
||||||
ptrdiff_t namelen, linelen;
|
ptrdiff_t namelen, linelen;
|
||||||
bool opgrp = false;
|
bool opgrp = false, one_esc = false, is_explthree = false;
|
||||||
|
|
||||||
cp = skip_spaces (cp + key->len);
|
cp = skip_spaces (cp + key->len);
|
||||||
|
|
||||||
|
/* 1. The canonical expl3 syntax looks something like this:
|
||||||
|
\cs_new:Npn \__hook_tl_gput:Nn { \ERROR }. First, if we
|
||||||
|
want to tag any such commands, we include only the part
|
||||||
|
before the colon (cs_new) in TEX_defenv or TEXTAGS. Second,
|
||||||
|
etags skips the argument specifier (including the colon)
|
||||||
|
after the tag token, so that it doesn't become the tag name.
|
||||||
|
Third, we set the boolean 'is_explthree' to true so that we
|
||||||
|
can remove the argument specifier from the actual tag name
|
||||||
|
(__hook_tl_gput). This all allows us to include expl3
|
||||||
|
constructs in TEX_defenv or in the environment variable
|
||||||
|
TEXTAGS without requiring a change of separator, and it also
|
||||||
|
allows us to find the definition of variant commands (with
|
||||||
|
different argument specifiers) defined using, for example,
|
||||||
|
\cs_generate_variant:Nn. Please note that the expl3 spec
|
||||||
|
requires etags to pay more attention to whitespace in the
|
||||||
|
code.
|
||||||
|
|
||||||
|
2. We also automatically remove the asterisk from starred
|
||||||
|
variants of all commands, without the need to include the
|
||||||
|
starred commands explicitly in TEX_defenv or TEXTAGS. */
|
||||||
|
if (*cp == ':')
|
||||||
|
{
|
||||||
|
while (!c_isspace (*cp) && *cp != TEX_opgrp)
|
||||||
|
cp++;
|
||||||
|
cp = skip_spaces (cp);
|
||||||
|
is_explthree = true;
|
||||||
|
}
|
||||||
|
else if (*cp == '*')
|
||||||
|
cp++;
|
||||||
|
|
||||||
|
/* Skip the optional arguments to commands in the tags list so
|
||||||
|
that these arguments don't end up as the name of the tag.
|
||||||
|
The name will instead come from the argument in curly braces
|
||||||
|
that follows the optional ones. The '\let' command gets
|
||||||
|
special treatment. */
|
||||||
|
while (*cp != '\0' && *cp != '%'
|
||||||
|
&& !streq (key->buffer, "let"))
|
||||||
|
{
|
||||||
|
if (*cp == '[')
|
||||||
|
{
|
||||||
|
while (*cp != ']' && *cp != '\0' && *cp != '%')
|
||||||
|
cp++;
|
||||||
|
}
|
||||||
|
else if (*cp == '(')
|
||||||
|
{
|
||||||
|
while (*cp != ')' && *cp != '\0' && *cp != '%')
|
||||||
|
cp++;
|
||||||
|
}
|
||||||
|
else if (*cp == ']' || *cp == ')')
|
||||||
|
cp++;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (*cp == TEX_opgrp)
|
if (*cp == TEX_opgrp)
|
||||||
{
|
{
|
||||||
opgrp = true;
|
opgrp = true;
|
||||||
cp++;
|
cp++;
|
||||||
|
cp = skip_spaces (cp); /* For expl3 code. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Removing the TeX escape character from tag names simplifies
|
||||||
|
things for editors finding tagged commands in TeX buffers.
|
||||||
|
This applies to Emacs but also to the tag-finding behavior
|
||||||
|
of at least some of the editors that use ctags, though in
|
||||||
|
the latter case this will remain suboptimal. The
|
||||||
|
undocumented ctags option '--no-duplicates' may help. */
|
||||||
|
if (*cp == TEX_esc)
|
||||||
|
{
|
||||||
|
cp++;
|
||||||
|
one_esc = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Testing !c_isspace && !c_ispunct is simpler, but halts
|
||||||
|
processing at too many places. The list as it stands tries
|
||||||
|
both to ensure that tag names will derive from macro names
|
||||||
|
rather than from optional parameters to those macros, and
|
||||||
|
also to return findable names while still allowing for
|
||||||
|
unorthodox constructs. */
|
||||||
for (p = cp;
|
for (p = cp;
|
||||||
(!c_isspace (*p) && *p != '#' &&
|
(!c_isspace (*p) && *p != '#' && *p != '=' &&
|
||||||
*p != TEX_opgrp && *p != TEX_clgrp);
|
*p != '[' && *p != '(' && *p != TEX_opgrp &&
|
||||||
|
*p != TEX_clgrp && *p != '"' && *p != '\'' &&
|
||||||
|
*p != '%' && *p != ',' && *p != '|' && *p != '$');
|
||||||
p++)
|
p++)
|
||||||
continue;
|
/* In expl3 code we remove the argument specification from
|
||||||
|
the tag name. More generally we allow only one (deleted)
|
||||||
|
escape char in a tag name, which (primarily) enables
|
||||||
|
tagging a TeX command's different, possibly temporary,
|
||||||
|
'\let' bindings. */
|
||||||
|
if (is_explthree && *p == ':')
|
||||||
|
break;
|
||||||
|
else if (*p == TEX_esc)
|
||||||
|
{ /* Second part of test is for, e.g., \cslet. */
|
||||||
|
if (!one_esc && !opgrp)
|
||||||
|
{
|
||||||
|
one_esc = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
/* For TeX files, tags without a name are basically cruft, and
|
||||||
|
in some situations they can produce spurious and confusing
|
||||||
|
matches. Try to catch as many cases as possible where a
|
||||||
|
command name is of the form '\(', but avoid, as far as
|
||||||
|
possible, the spurious matches. */
|
||||||
|
if (p == cp)
|
||||||
|
{
|
||||||
|
switch (*p)
|
||||||
|
{ /* Include =? */
|
||||||
|
case '(': case '[': case '"': case '\'':
|
||||||
|
case '\\': case '!': case '=': case ',':
|
||||||
|
case '|': case '$':
|
||||||
|
p++;
|
||||||
|
break;
|
||||||
|
case '{': case '}': case '<': case '>':
|
||||||
|
if (!opgrp)
|
||||||
|
{
|
||||||
|
p++;
|
||||||
|
if (*p == '\0' || *p == '%')
|
||||||
|
goto tex_next_line;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
namelen = p - cp;
|
namelen = p - cp;
|
||||||
linelen = lb.len;
|
linelen = lb.len;
|
||||||
if (!opgrp || *p == TEX_clgrp)
|
if (!opgrp || *p == TEX_clgrp)
|
||||||
|
@ -5828,9 +5975,18 @@ TeX_commands (FILE *inf)
|
||||||
p++;
|
p++;
|
||||||
linelen = p - lb.buffer + 1;
|
linelen = p - lb.buffer + 1;
|
||||||
}
|
}
|
||||||
make_tag (cp, namelen, true,
|
if (namelen)
|
||||||
lb.buffer, linelen, lineno, linecharno);
|
make_tag (cp, namelen, true,
|
||||||
goto tex_next_line; /* We only tag a line once */
|
lb.buffer, linelen, lineno, linecharno);
|
||||||
|
/* Lines with more than one \def or \let are surprisingly
|
||||||
|
common in TeX files, especially in the system files that
|
||||||
|
form the basis of the various TeX formats. This tags them
|
||||||
|
all. */
|
||||||
|
/* goto tex_next_line; /\* We only tag a line once *\/ */
|
||||||
|
while (*cp != '\0' && *cp != '%' && *cp != TEX_esc)
|
||||||
|
cp++;
|
||||||
|
if (*cp != TEX_esc)
|
||||||
|
goto tex_next_line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tex_next_line:
|
tex_next_line:
|
||||||
|
|
|
@ -637,6 +637,14 @@ An alternative value is \" . \", if you use a font with a narrow period."
|
||||||
3 '(tex-font-lock-append-prop 'bold) 'append)))))
|
3 '(tex-font-lock-append-prop 'bold) 'append)))))
|
||||||
"Gaudy expressions to highlight in TeX modes.")
|
"Gaudy expressions to highlight in TeX modes.")
|
||||||
|
|
||||||
|
(defvar-local tex-expl-region-list nil
|
||||||
|
"List of region boundaries where expl3 syntax is active.
|
||||||
|
It will be nil in buffers visiting files which use expl3 syntax
|
||||||
|
throughout, for example, expl3 classes or packages.")
|
||||||
|
|
||||||
|
(defvar-local tex-expl-buffer-p nil
|
||||||
|
"Non-nil in buffers using expl3 syntax throughout.")
|
||||||
|
|
||||||
(defun tex-font-lock-suscript (pos)
|
(defun tex-font-lock-suscript (pos)
|
||||||
(unless (or (memq (get-text-property pos 'face)
|
(unless (or (memq (get-text-property pos 'face)
|
||||||
'(font-lock-constant-face font-lock-builtin-face
|
'(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."
|
||||||
(pos pos))
|
(pos pos))
|
||||||
(while (eq (char-before pos) ?\\)
|
(while (eq (char-before pos) ?\\)
|
||||||
(setq pos (1- pos) odd (not odd)))
|
(setq pos (1- pos) odd (not odd)))
|
||||||
odd))
|
odd)
|
||||||
|
;; Check if POS is in an expl3 syntax region or an expl3 buffer
|
||||||
|
(when (eq (char-after pos) ?_)
|
||||||
|
(or tex-expl-buffer-p
|
||||||
|
(and
|
||||||
|
tex-expl-region-list
|
||||||
|
(catch 'result
|
||||||
|
(dolist (range tex-expl-region-list)
|
||||||
|
(and (> pos (car range))
|
||||||
|
(< pos (cdr range))
|
||||||
|
(throw 'result t))))))))
|
||||||
(if (eq (char-after pos) ?_)
|
(if (eq (char-after pos) ?_)
|
||||||
`(face subscript display (raise ,(car tex-font-script-display)))
|
`(face subscript display (raise ,(car tex-font-script-display)))
|
||||||
`(face superscript display (raise ,(cadr tex-font-script-display))))))
|
`(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
|
||||||
#'tex--prettify-symbols-compose-p)
|
#'tex--prettify-symbols-compose-p)
|
||||||
(setq-local syntax-propertize-function
|
(setq-local syntax-propertize-function
|
||||||
(syntax-propertize-rules latex-syntax-propertize-rules))
|
(syntax-propertize-rules latex-syntax-propertize-rules))
|
||||||
|
;; Don't add extra processing to `syntax-propertize' in files where
|
||||||
|
;; expl3 syntax is always active.
|
||||||
|
:after-hook (progn (tex-expl-buffer-parse)
|
||||||
|
(unless tex-expl-buffer-p
|
||||||
|
(add-hook 'syntax-propertize-extend-region-functions
|
||||||
|
#'tex-expl-region-set nil t)))
|
||||||
;; TABs in verbatim environments don't do what you think.
|
;; TABs in verbatim environments don't do what you think.
|
||||||
(setq-local indent-tabs-mode nil)
|
(setq-local indent-tabs-mode nil)
|
||||||
|
;; Set up xref backend in TeX buffers.
|
||||||
|
(add-hook 'xref-backend-functions #'tex--xref-backend nil t)
|
||||||
;; Other vars that should be buffer-local.
|
;; Other vars that should be buffer-local.
|
||||||
(make-local-variable 'tex-command)
|
(make-local-variable 'tex-command)
|
||||||
(make-local-variable 'tex-start-of-header)
|
(make-local-variable 'tex-start-of-header)
|
||||||
|
@ -1937,6 +1963,36 @@ Mark is left at original location."
|
||||||
(forward-sexp 1))))))
|
(forward-sexp 1))))))
|
||||||
(message "%s words" count))))
|
(message "%s words" count))))
|
||||||
|
|
||||||
|
(defun tex-expl-buffer-parse ()
|
||||||
|
"Identify buffers using expl3 syntax throughout."
|
||||||
|
(save-excursion
|
||||||
|
(goto-char (point-min))
|
||||||
|
(when (tex-search-noncomment
|
||||||
|
(re-search-forward
|
||||||
|
"\\\\\\(?:ExplFile\\|ProvidesExpl\\|__xparse_file\\)"
|
||||||
|
nil t))
|
||||||
|
(setq tex-expl-buffer-p t))))
|
||||||
|
|
||||||
|
(defun tex-expl-region-set (_beg _end)
|
||||||
|
"Create a list of regions where expl3 syntax is active.
|
||||||
|
This function updates the list whenever `syntax-propertize' runs, and
|
||||||
|
stores it in the buffer-local variable `tex-expl-region-list'. The list
|
||||||
|
will always be nil when the buffer visits an expl3 file, for example, an
|
||||||
|
expl3 class or package, where the entire file uses expl3 syntax."
|
||||||
|
(unless syntax-ppss--updated-cache;; Stop forward search running twice.
|
||||||
|
(setq tex-expl-region-list nil)
|
||||||
|
;; Leaving this test here allows users to set `tex-expl-buffer-p'
|
||||||
|
;; independently of the mode's automatic detection of an expl3 file.
|
||||||
|
(unless tex-expl-buffer-p
|
||||||
|
(goto-char (point-min))
|
||||||
|
(let ((case-fold-search nil))
|
||||||
|
(while (tex-search-noncomment
|
||||||
|
(search-forward "\\ExplSyntaxOn" nil t))
|
||||||
|
(let ((new-beg (point))
|
||||||
|
(new-end (or (tex-search-noncomment
|
||||||
|
(search-forward "\\ExplSyntaxOff" nil t))
|
||||||
|
(point-max))))
|
||||||
|
(push (cons new-beg new-end) tex-expl-region-list)))))))
|
||||||
|
|
||||||
|
|
||||||
;;; Invoking TeX in an inferior shell.
|
;;; Invoking TeX in an inferior shell.
|
||||||
|
@ -3743,6 +3799,306 @@ There might be text before point."
|
||||||
(process-send-region tex-chktex--process (point-min) (point-max))
|
(process-send-region tex-chktex--process (point-min) (point-max))
|
||||||
(process-send-eof tex-chktex--process))))
|
(process-send-eof tex-chktex--process))))
|
||||||
|
|
||||||
|
|
||||||
|
;;; Xref backend
|
||||||
|
|
||||||
|
;; Here we lightly adapt the default etags backend for xref so that
|
||||||
|
;; the main xref user commands (including `xref-find-definitions',
|
||||||
|
;; `xref-find-apropos', and `xref-find-references' [on M-., C-M-., and
|
||||||
|
;; M-?, respectively]) work in TeX buffers. The only methods we
|
||||||
|
;; actually modify are `xref-backend-identifier-at-point' and
|
||||||
|
;; `xref-backend-references'. Many of the complications here, and in
|
||||||
|
;; `etags' itself, are due to the necessity of parsing both the old
|
||||||
|
;; TeX syntax and the new expl3 syntax, which will continue to appear
|
||||||
|
;; together in documents for the foreseeable future. Synchronizing
|
||||||
|
;; Emacs and `etags' this way aims to improve the user experience "out
|
||||||
|
;; of the box."
|
||||||
|
|
||||||
|
(defvar tex-thingatpt-exclude-chars '(?\\ ?\{ ?\})
|
||||||
|
"Exclude these chars by default from TeX thing-at-point.
|
||||||
|
|
||||||
|
The TeX `xref-backend-identifier-at-point' method uses the characters
|
||||||
|
listed in this variable to decide on the default search string to
|
||||||
|
present to the user who calls an `xref' command. These characters
|
||||||
|
become part of a regexp which always excludes them from that default
|
||||||
|
string. For the `xref' commands to function properly in TeX buffers, at
|
||||||
|
least the TeX escape and the two TeX grouping characters should be
|
||||||
|
listed here. Should your TeX documents contain other characters which
|
||||||
|
you want to exclude by default, then you can add them to the list,
|
||||||
|
though you may wish to consult the functions
|
||||||
|
`tex-thingatpt--beginning-of-symbol' and `tex-thingatpt--end-of-symbol'
|
||||||
|
to see what the regexp already contains. If your documents contain
|
||||||
|
non-standard escape and grouping characters, then you can replace the
|
||||||
|
three listed here with your own, thereby allowing the three standard
|
||||||
|
characters to appear by default in search strings. Please be aware,
|
||||||
|
however, that the `etags' program only recognizes `\\' (92) and `!' (33)
|
||||||
|
as escape characters in TeX documents, and if it detects the latter it
|
||||||
|
also uses `<>' as the TeX grouping construct rather than `{}'. Setting
|
||||||
|
the escape and grouping chars to anything other than `\\=\\{}' or `!<>'
|
||||||
|
will not be useful without changes to `etags', at least for commands
|
||||||
|
that search tags tables, such as \\[xref-find-definitions] and \
|
||||||
|
\\[xref-find-apropos].
|
||||||
|
|
||||||
|
Should you wish to change the defaults, please also be aware that,
|
||||||
|
without further modifications to tex-mode.el, the usual text-parsing
|
||||||
|
routines for `font-lock' and the like won't work correctly, as the
|
||||||
|
default escape and grouping characters are currently hard coded in many
|
||||||
|
places.")
|
||||||
|
|
||||||
|
;; Populate `semantic-symref-filepattern-alist' for the in-tree modes;
|
||||||
|
;; AUCTeX is doing the same for its modes.
|
||||||
|
(with-eval-after-load 'semantic/symref/grep
|
||||||
|
(defvar semantic-symref-filepattern-alist)
|
||||||
|
(push '(latex-mode "*.[tT]e[xX]" "*.ltx" "*.sty" "*.cl[so]"
|
||||||
|
"*.bbl" "*.drv" "*.hva")
|
||||||
|
semantic-symref-filepattern-alist)
|
||||||
|
(push '(plain-tex-mode "*.[tT]e[xX]" "*.ins")
|
||||||
|
semantic-symref-filepattern-alist)
|
||||||
|
(push '(doctex-mode "*.dtx") semantic-symref-filepattern-alist))
|
||||||
|
|
||||||
|
(defun tex--xref-backend () 'tex-etags)
|
||||||
|
|
||||||
|
(cl-defmethod xref-backend-identifier-at-point ((_backend (eql 'tex-etags)))
|
||||||
|
(require 'etags)
|
||||||
|
(tex--thing-at-point))
|
||||||
|
|
||||||
|
;; The detection of `_' and `:' is a primitive method for determining
|
||||||
|
;; whether point is on an expl3 construct. It may fail in some
|
||||||
|
;; instances.
|
||||||
|
(defun tex--thing-at-point ()
|
||||||
|
"Demarcate `thing-at-point' for the TeX `xref' backend."
|
||||||
|
(let ((bounds (tex--bounds-of-symbol-at-point)))
|
||||||
|
(when bounds
|
||||||
|
(let ((texsym (buffer-substring-no-properties (car bounds) (cdr bounds))))
|
||||||
|
(if (and (not (string-match-p "reference" (symbol-name this-command)))
|
||||||
|
(seq-contains-p texsym ?_)
|
||||||
|
(seq-contains-p texsym ?:))
|
||||||
|
(seq-take texsym (seq-position texsym ?:))
|
||||||
|
texsym)))))
|
||||||
|
|
||||||
|
(defun tex-thingatpt--beginning-of-symbol ()
|
||||||
|
(and
|
||||||
|
(re-search-backward (concat "[]["
|
||||||
|
(mapconcat #'regexp-quote
|
||||||
|
(mapcar #'char-to-string
|
||||||
|
tex-thingatpt-exclude-chars))
|
||||||
|
"\"*`'#=&()%,|$[:cntrl:][:blank:]]"))
|
||||||
|
(forward-char)))
|
||||||
|
|
||||||
|
(defun tex-thingatpt--end-of-symbol ()
|
||||||
|
(and
|
||||||
|
(re-search-forward (concat "[]["
|
||||||
|
(mapconcat #'regexp-quote
|
||||||
|
(mapcar #'char-to-string
|
||||||
|
tex-thingatpt-exclude-chars))
|
||||||
|
"\"*`'#=&()%,|$[:cntrl:][:blank:]]"))
|
||||||
|
(backward-char)))
|
||||||
|
|
||||||
|
(defun tex--bounds-of-symbol-at-point ()
|
||||||
|
"Simplify `bounds-of-thing-at-point' for TeX `xref' backend."
|
||||||
|
(let ((orig (point)))
|
||||||
|
(ignore-errors
|
||||||
|
(save-excursion
|
||||||
|
(tex-thingatpt--end-of-symbol)
|
||||||
|
(tex-thingatpt--beginning-of-symbol)
|
||||||
|
(let ((beg (point)))
|
||||||
|
(if (<= beg orig)
|
||||||
|
(let ((real-end
|
||||||
|
(progn
|
||||||
|
(tex-thingatpt--end-of-symbol)
|
||||||
|
(point))))
|
||||||
|
(cond ((and (<= orig real-end) (< beg real-end))
|
||||||
|
(cons beg real-end))
|
||||||
|
((and (= orig real-end) (= beg real-end))
|
||||||
|
(cons beg (1+ beg)))))))))));; For 1-char TeX commands.
|
||||||
|
|
||||||
|
(cl-defmethod xref-backend-identifier-completion-table ((_backend
|
||||||
|
(eql 'tex-etags)))
|
||||||
|
(xref-backend-identifier-completion-table 'etags))
|
||||||
|
|
||||||
|
(cl-defmethod xref-backend-identifier-completion-ignore-case ((_backend
|
||||||
|
(eql
|
||||||
|
'tex-etags)))
|
||||||
|
(xref-backend-identifier-completion-ignore-case 'etags))
|
||||||
|
|
||||||
|
(cl-defmethod xref-backend-definitions ((_backend (eql 'tex-etags)) symbol)
|
||||||
|
(xref-backend-definitions 'etags symbol))
|
||||||
|
|
||||||
|
(cl-defmethod xref-backend-apropos ((_backend (eql 'tex-etags)) pattern)
|
||||||
|
(xref-backend-apropos 'etags pattern))
|
||||||
|
|
||||||
|
;; The `xref-backend-references' method requires more code than the
|
||||||
|
;; others for at least two main reasons: TeX authors have typically been
|
||||||
|
;; free in their invention of new file types with new suffixes, and they
|
||||||
|
;; have also tended sometimes to include non-symbol characters in
|
||||||
|
;; command names. When combined with the default Semantic Symbol
|
||||||
|
;; Reference API, these two characteristics of TeX code mean that a
|
||||||
|
;; command like `xref-find-references' would often fail to find any hits
|
||||||
|
;; for a symbol at point, including the one under point in the current
|
||||||
|
;; buffer, or it would find only some instances and skip others.
|
||||||
|
|
||||||
|
(defun tex-find-references-syntax-table ()
|
||||||
|
(let ((st (if (boundp 'TeX-mode-syntax-table)
|
||||||
|
(make-syntax-table TeX-mode-syntax-table)
|
||||||
|
(make-syntax-table tex-mode-syntax-table))))
|
||||||
|
st))
|
||||||
|
|
||||||
|
(defvar tex--xref-syntax-fun nil)
|
||||||
|
|
||||||
|
(defun tex-xref-syntax-function (str beg end)
|
||||||
|
"Provide a bespoke `syntax-propertize-function' for \\[xref-find-references]."
|
||||||
|
(let* (grpb tempstr
|
||||||
|
(shrtstr (if end
|
||||||
|
(progn
|
||||||
|
(setq tempstr (seq-take str (1- (length str))))
|
||||||
|
(if beg
|
||||||
|
(setq tempstr (seq-drop tempstr 1))
|
||||||
|
tempstr))
|
||||||
|
(seq-drop str 1)))
|
||||||
|
(grpa (if (and beg end)
|
||||||
|
(prog1
|
||||||
|
(list 1 "_")
|
||||||
|
(setq grpb (list 2 "_")))
|
||||||
|
(list 1 "_")))
|
||||||
|
(re (concat beg (regexp-quote shrtstr) end))
|
||||||
|
(temp-rule (if grpb
|
||||||
|
(list re grpa grpb)
|
||||||
|
(list re grpa))))
|
||||||
|
;; Simple benchmarks suggested that the speed-up from compiling this
|
||||||
|
;; function was nearly nil, so `eval' and its non-byte-compiled
|
||||||
|
;; function remain.
|
||||||
|
(setq tex--xref-syntax-fun (eval
|
||||||
|
`(syntax-propertize-rules ,temp-rule)))))
|
||||||
|
|
||||||
|
(defun tex--collect-file-extensions ()
|
||||||
|
"Gather TeX file extensions from `auto-mode-alist'."
|
||||||
|
(let* ((mlist (when (rassq major-mode auto-mode-alist)
|
||||||
|
(seq-filter
|
||||||
|
(lambda (elt)
|
||||||
|
(eq (cdr elt) major-mode))
|
||||||
|
auto-mode-alist)))
|
||||||
|
(lcsym (intern-soft (downcase (symbol-name major-mode))))
|
||||||
|
(lclist (and lcsym
|
||||||
|
(not (eq lcsym major-mode))
|
||||||
|
(rassq lcsym auto-mode-alist)
|
||||||
|
(seq-filter
|
||||||
|
(lambda (elt)
|
||||||
|
(eq (cdr elt) lcsym))
|
||||||
|
auto-mode-alist)))
|
||||||
|
(shortsym (when (stringp mode-name)
|
||||||
|
(intern-soft (concat (string-trim-right mode-name "/.*")
|
||||||
|
"-mode"))))
|
||||||
|
(lcshortsym (when (stringp mode-name)
|
||||||
|
(intern-soft (downcase
|
||||||
|
(concat
|
||||||
|
(string-trim-right mode-name "/.*")
|
||||||
|
"-mode")))))
|
||||||
|
(shlist (and shortsym
|
||||||
|
(not (eq shortsym major-mode))
|
||||||
|
(not (eq shortsym lcsym))
|
||||||
|
(rassq shortsym auto-mode-alist)
|
||||||
|
(seq-filter
|
||||||
|
(lambda (elt)
|
||||||
|
(eq (cdr elt) shortsym))
|
||||||
|
auto-mode-alist)))
|
||||||
|
(lcshlist (and lcshortsym
|
||||||
|
(not (eq lcshortsym major-mode))
|
||||||
|
(not (eq lcshortsym lcsym))
|
||||||
|
(rassq lcshortsym auto-mode-alist)
|
||||||
|
(seq-filter
|
||||||
|
(lambda (elt)
|
||||||
|
(eq (cdr elt) lcshortsym))
|
||||||
|
auto-mode-alist)))
|
||||||
|
(exts (when (or mlist lclist shlist lcshlist)
|
||||||
|
(seq-union (seq-map #'car lclist)
|
||||||
|
(seq-union (seq-map #'car mlist)
|
||||||
|
(seq-union (seq-map #'car lcshlist)
|
||||||
|
(seq-map #'car shlist))))))
|
||||||
|
(ed-exts (when exts
|
||||||
|
(seq-map
|
||||||
|
(lambda (elt)
|
||||||
|
(concat "*" (string-trim elt "\\\\" "\\\\'")))
|
||||||
|
exts))))
|
||||||
|
ed-exts))
|
||||||
|
|
||||||
|
(defvar tex--buffers-list nil)
|
||||||
|
(defvar-local tex--old-syntax-function nil)
|
||||||
|
|
||||||
|
(cl-defmethod xref-backend-references ((_backend (eql 'tex-etags)) identifier)
|
||||||
|
"Find references of IDENTIFIER in TeX buffers and files."
|
||||||
|
(require 'semantic/symref/grep)
|
||||||
|
(defvar semantic-symref-filepattern-alist)
|
||||||
|
(let (bufs texbufs
|
||||||
|
(mode major-mode))
|
||||||
|
(dolist (buf (buffer-list))
|
||||||
|
(if (eq (buffer-local-value 'major-mode buf) mode)
|
||||||
|
(push buf bufs)
|
||||||
|
(when (string-match-p ".*\\.[tT]e[xX]" (buffer-name buf))
|
||||||
|
(push buf texbufs))))
|
||||||
|
(unless (seq-set-equal-p tex--buffers-list bufs)
|
||||||
|
(let* ((amalist (tex--collect-file-extensions))
|
||||||
|
(extlist (alist-get mode semantic-symref-filepattern-alist))
|
||||||
|
(extlist-new (seq-uniq
|
||||||
|
(seq-union amalist extlist #'string-match-p))))
|
||||||
|
(setq tex--buffers-list bufs)
|
||||||
|
(dolist (buf bufs)
|
||||||
|
(when-let ((fbuf (buffer-file-name buf))
|
||||||
|
(ext (file-name-extension fbuf))
|
||||||
|
(finext (concat "*." ext))
|
||||||
|
((not (seq-find (lambda (elt) (string-match-p elt finext))
|
||||||
|
extlist-new)))
|
||||||
|
((push finext extlist-new)))))
|
||||||
|
(unless (seq-set-equal-p extlist-new extlist)
|
||||||
|
(setf (alist-get mode semantic-symref-filepattern-alist)
|
||||||
|
extlist-new))))
|
||||||
|
(let* (setsyntax
|
||||||
|
(punct (with-syntax-table (tex-find-references-syntax-table)
|
||||||
|
(seq-positions identifier (list ?w ?_)
|
||||||
|
(lambda (elt sycode)
|
||||||
|
(not (memq (char-syntax elt) sycode))))))
|
||||||
|
(end (and punct
|
||||||
|
(memq (1- (length identifier)) punct)
|
||||||
|
(> (length identifier) 1)
|
||||||
|
(concat "\\("
|
||||||
|
(regexp-quote
|
||||||
|
(string (elt identifier
|
||||||
|
(1- (length identifier)))))
|
||||||
|
"\\)")))
|
||||||
|
(beg (and punct
|
||||||
|
(memq 0 punct)
|
||||||
|
(concat "\\("
|
||||||
|
(regexp-quote (string (elt identifier 0)))
|
||||||
|
"\\)")))
|
||||||
|
(text-mode-hook
|
||||||
|
(if (or end beg)
|
||||||
|
(progn
|
||||||
|
(tex-xref-syntax-function identifier beg end)
|
||||||
|
(setq setsyntax (lambda ()
|
||||||
|
(setq-local syntax-propertize-function
|
||||||
|
tex--xref-syntax-fun)
|
||||||
|
(setq-local TeX-style-hook-applied-p t)))
|
||||||
|
(cons setsyntax text-mode-hook))
|
||||||
|
text-mode-hook)))
|
||||||
|
(unless (memq 'doctex-mode (derived-mode-all-parents mode))
|
||||||
|
(setq bufs (append texbufs bufs)))
|
||||||
|
(when (or end beg)
|
||||||
|
(dolist (buf bufs)
|
||||||
|
(with-current-buffer buf
|
||||||
|
(unless (local-variable-p 'tex--old-syntax-function)
|
||||||
|
(setq tex--old-syntax-function syntax-propertize-function))
|
||||||
|
(setq-local syntax-propertize-function
|
||||||
|
tex--xref-syntax-fun)
|
||||||
|
(syntax-ppss-flush-cache (point-min)))))
|
||||||
|
(unwind-protect
|
||||||
|
(xref-backend-references nil identifier)
|
||||||
|
(when (or end beg)
|
||||||
|
(dolist (buf bufs)
|
||||||
|
(with-current-buffer buf
|
||||||
|
(when buffer-file-truename
|
||||||
|
(setq-local syntax-propertize-function
|
||||||
|
tex--old-syntax-function)
|
||||||
|
(syntax-ppss-flush-cache (point-min))))))))))
|
||||||
|
|
||||||
(make-obsolete-variable 'tex-mode-load-hook
|
(make-obsolete-variable 'tex-mode-load-hook
|
||||||
"use `with-eval-after-load' instead." "28.1")
|
"use `with-eval-after-load' instead." "28.1")
|
||||||
(run-hooks 'tex-mode-load-hook)
|
(run-hooks 'tex-mode-load-hook)
|
||||||
|
|
Loading…
Reference in a new issue