当我想全局修改文档中的命令时,我通常会在宏的帮助下使用模板中原始命令的复制版本\let
。例如,如果我想在每次创建部分时执行某些操作,我可能会说:
\let\svsection\section
\renewcommand\section[1]{\mychanges\svsection{#1}}
我突然意识到我不知道如何或是否可以为环境做类似的事情。我希望的是这样的(为了简单起见,我省略了参数,不要关注这一点):
\let\svfigure\figure
\renewenvironment{figure}{\blahblah\begin{svfigure}}
{\end{svfigure}\moreblah}
当然,这种语法不起作用,因为\figure
它不是一个命令。
但我希望环境的创建abc
总是伴随着相关构造命令的创建,例如\start@abc
和\end@abc
,以便这些相关命令可以\let
复制原始环境。
我也意识到一种方法可能是“修补”原始环境,但我不认为这真正是我所要求的,因为我不确定修补是否可以轻松撤消,而\let
以相反方向重做(例如\let\section\svsection
)将完全撤消重新定义的效果。
知道如何复制环境是一个很方便的功能,这样就可以\renew
按照我描述的方式编辑原始副本。
答案1
当你使用以下方式定义环境时
\newenvironment{foo}[1]{start code with #1}{end code}
内部发生的事情(在检查可选参数、测试环境是否存在以及处理可能的星号参数之后\newenvironment
)基本上可以归结为
\newcommand\foo[1]{start code with #1}% actually \new@command
\def\endfoo{end code}
这意味着如果环境没有可选参数
\let\myfoo\foo
\let\endmyfoo\endfoo
将环境复制{foo}
为 environment {myfoo}
。(如果它有一个可选参数,\LetLtxMacro
应该使用它来代替\let
,请参阅何时使用 \LetLtxMacro?。
在article
课堂上{figure}
,环境定义为
\newenvironment{figure}
{\@float{figure}}
{\end@float}
这么说吧
\let\myfigure\figure
\let\endmyfigure\endfigure
作品:
\documentclass{article}
\newenvironment{foo}[1]{start foo with (#1)}{end foo}
\let\myfoo\foo
\let\endmyfoo\endfoo
\let\myfigure\figure
\let\endmyfigure\endfigure
\begin{document}
\begin{myfoo}{arg}
bar
\end{myfoo}
\begin{myfigure}
copied figure
\caption{my figure}
\end{myfigure}
\end{document}
某些环境(例如{verbatim}
或 AMSmath 的){align}
无法通过这种方式复制,因为它们需要分别精确地找到\end{verbatim}
或。\end{align}
答案2
像你这样修补\section
并不是一个特别好的方法,因为你失去了原始命令的所有灵活性;在修补命令之前,你总是必须查看命令是如何定义的。你会\section
发现不例如,论点。
对于环境也是如此;它们通过一对命令实现:
\newenvironment{foo}[1]
{something with #1 at the start}
{something at the end}
LaTeX 定义\foo
并\endfoo
在某种程度上基本等同于
\newcommand{\foo}[1]{something with #1 at the start}
\def\endfoo{something at the end}
在标准 LaTeX 中,该\end...
命令始终没有参数(并且\newcommand
不允许定义它)。
了解以下几点也很重要什么时候命令被执行;这是的定义\begin
:
% latex.ltx, line 3944:
\def\begin#1{%
\@ifundefined{#1}%
{\def\reserved@a{\@latex@error{Environment #1 undefined}\@eha}}%
{\def\reserved@a{\def\@currenvir{#1}%
\edef\@currenvline{\on@line}%
\csname #1\endcsname}}%
\@ignorefalse
\begingroup\@endpefalse\reserved@a}
除了一些细节之外,\begin{foo}
LaTeX 本质上是
\begingroup<bookkeping stuff>\foo
进行一些簿记,以便以后检查该\end
部分。同样,\end{foo}
\endfoo<checks>\endgroup
因此,与普通宏相同的方法将起作用;然而,
\let\svfigure\figure
\let\endsvfigure\endfigure
\renewenvironment{figure}
{\blahblah\begin{svfigure}}
{\end{svfigure}\moreblah}
这不是修补figure
环境的特别好的方法。首先,\end{fiugre}
输入文件中的输入错误会导致令人费解的消息
! LaTeX Error: \begin{svfigure} on input line 11 ended by \end{fiugre}.
See the LaTeX manual or LaTeX Companion for explanation.
Type H <return> for immediate help.
...
l.13 \end{fiugre}
并且用户不会理解,因为他们不知道svfigure
他们所处的环境绝不开始。通常的解决方案是说
\let\svfigure\figure
\let\endsvfigure\endfigure
\renewenvironment{figure}
{\blahblah\svfigure}
{\endsvfigure\moreblah}
这样 ,<bookkeeping stuff>
意志 得以 实现 ,figure
而 不是 内心svfigure
.
然而,并非所有环境都适合以这种方式重新定义。
带有可选参数的环境应使用\LetLtxMacro
(with \usepackage{letltxmacro}
) 而不是\let
,但对于宏也是如此。对于\end...
命令来说\let
,这始终足够了。更重要的是,这种方式可能不适用于使用不同工具定义的环境,特别是\NewEnviron
( environ
package) 或\NewDocumentEnvironment
( xparse
package)。
其他一些环境有自己的特点:不要尝试重新定义lrbox
(这无论如何都没有意义),也不要verbatim
。比如
\let\svverbatim\verbatim
\let\endsvverbatim\endverbatim
\renewenvironment{verbatim}
{<something>\svverbatim}
{\endsvverbatim<something>}
除非您加载该包,否则将无法正常工作,并使您处于永久逐字模式verbatim
。但不要尝试使用此方法更改 中的字体大小verbatim
,它不会起作用。
修补宏和环境有时是一项神秘的活动,最适合巫师而不是熟练的 LaTeX 程序员。
答案3
如果您使用的是 LaTeX 2023-06-01 或更高版本1,则命令\NewEnvironmentCopy
、\RenewEnvironmentCopy
和\DeclareEnvironmentCopy
已内置于内核中,因此您可以使用:
\NewEnvironmentCopy{<new env>}{<existing env>}
进行复制。无论环境是使用\newenvironment
还是定义的,此方法都会起作用\NewDocumentEnvironment
。此方法应优于使用\let
,因为即使环境采用可选参数或类似的东西,它也不会失败。
同一版本添加了\ShowEnvironment{<env>}
在终端上查看环境的定义。
1同时,您可以使用-dev
格式。
答案4
\NewCommandCopy
有效。但是,它的缺点是,当*
需要包含环境名称等时,需要令人讨厌的语法。
\NewEnvironmentCopy
您可以按如下方式实现自己的命令(在较新的 LaTeX 内核中\NewEnvironmentCopy
是内置的,因此如果已经定义,则最好使用\ProvideDocumentCommand
它来默默地回退到使用现有命令):
%! TEX program = lualatex
\documentclass{article}
\usepackage{amsmath}
\begin{document}
\NewDocumentEnvironment{test}{m}{hello #1}{end #1}
\begin{test}{aaa}
environment body
\end{test}
\ExplSyntaxOn
\ProvideDocumentCommand \NewEnvironmentCopy {mm} {
\expandafter \NewCommandCopy \csname#1\expandafter\endcsname \csname#2\endcsname
\expandafter \NewCommandCopy \csname end#1\expandafter\endcsname \csname end#2\endcsname
}
\ProvideDocumentCommand \RenewEnvironmentCopy {mm} {
\expandafter \RenewCommandCopy \csname#1\expandafter\endcsname \csname#2\endcsname
\expandafter \RenewCommandCopy \csname end#1\expandafter\endcsname \csname end#2\endcsname
}
\ExplSyntaxOff
\NewEnvironmentCopy{testb}{test}
\RenewEnvironmentCopy{testb}{test}
\NewEnvironmentCopy{oldalign*}{align*}
\begin{testb}{aaa}
environment body
\end{testb}
\RenewDocumentEnvironment{test}{m}{redefined #1}{stop #1}
\begin{testb}{aaa}
environment body
\end{testb}
\begin{oldalign*}
1 &= 1
\end{oldalign*}
\end{document}
请注意,如果使用\let
而不是,\NewCommandCopy
那么行为将会不同且出乎意料(请自己尝试)。
或者(根据具体用例可能适用也可能不适用),你可以使用 LaTeX 中的钩子系统:
%! TEX program = lualatex
\documentclass{article}
\usepackage{amsmath}
\begin{document}
\AddToHook{env/align*/begin}{x}
\AddToHook{env/align*/end}{y}
\begin{align*}
1 &= 1 \\
2 &= 2
\end{align*}
\end{document}