16

Suppose I patch an environment using an etoolbox command such as \AtBeginEnvironment or \AfterEndEnvironment, etc. How can I undo these changes at a later stage?

\documentclass{article}
\usepackage{etoolbox}
\let\olditemize\itemize 
\let\oldenditemize\enditemize
\AfterEndEnvironment{itemize}{bleat bleat}
\begin{document}
\begin{itemize}
\item This is an item.
\end{itemize}
\let\itemize\olditemize        %Would like to undo the change
\let\enditemize\oldenditemize  %now, but this doesn't help.
\begin{itemize}
\item This is another item.
\end{itemize}
\end{document}
Ian Thompson
  • 43,767

6 Answers6

12

First of all note that \BeforeBeginEnvironment and \AfterEndEnvironment does not patch the environment itself. It only adds material to hooks. etoolbox patches usage of these hooks to \begin and \end. That's the reason why resetting \itemize and \enditemize to their original definitions does not change anything.

But you may simply undefine the environment end hook of itemize:

\documentclass{article}
\usepackage{etoolbox}
\AfterEndEnvironment{itemize}{bleat bleat}
\begin{document}
\begin{itemize}
\item This is an item.
\end{itemize}

\csundef{@afterend@itemize@hook}% undefine the end hook of itemize
\begin{itemize}
\item This is another item.
\end{itemize}
\end{document}

\csundef is a etoolbox command and \@afterend@itemize@hook is the environment end hook of environment itemize. The environment begin hook would be \@beforebegin@itemize@hook.

Maybe a feature request for commands like \CleanBeforeBeginEnvironment or \CleanAfterBeginEnvironment could be suggested.

Alternative: If you only want to remove something but not everything, you may try this:

\documentclass{article}
\usepackage{etoolbox}

\newrobustcmd*{\RemoveFromAfterEndEnvironment}[2]{%
  \expandafter\patchcmd\csname @afterend@#1@hook\endcsname{#2}{}%
}

\AfterEndEnvironment{itemize}{bleat bleat\par}{}{}
\AfterEndEnvironment{itemize}{don't remove this\par}{}{}
\begin{document}
\begin{itemize}
\item This is an item.
\end{itemize}

\RemoveFromAfterEndEnvironment{itemize}{bleat bleat\par}{}{}
\begin{itemize}
\item This is another item.
\end{itemize}
\end{document}

The third and fourth arguments of \RemoveFromAfterEndEnviroment are the same like third and fourth arguments of \patchcmd.

Schweinebacke
  • 26,336
10

There might be an official way, but defining a new command \AfterEndItemize to specify what you want to do after \end{itemize}, and then use \renewcommand to change \AfterEndItemize to not do anything seems to work:

\documentclass{article}
\usepackage{etoolbox}

\newcommand{\AfterEndItemize}{bleat bleat}%
\AfterEndEnvironment{itemize}{\AfterEndItemize}

\begin{document}
\begin{itemize}
\item This is an item.
\end{itemize}
%
\renewcommand{\AfterEndItemize}{}%
\begin{itemize}
\item This is another item.
\end{itemize}
\end{document}
Peter Grill
  • 223,288
5

Another way is: Use a boolean switch. Conveniently etoolbox provides such switches. Compare my question and the answers in etoolbox: environment hooks in boolean switch and look at this:

\documentclass{article}

\usepackage{setspace}
  \doublespacing

\usepackage{etoolbox}
  \newbool{tightspace}
  \setbool{tightspace}{false} % that's the default state, so
                              % it's here only for clarity
%----------------------------%
\BeforeBeginEnvironment{itemize}{%
    \ifbool{tightspace}{% true part:
                        \begin{spacing}{0.9}
                       }{%
                         % false part empty - should do nothing
                        }%
}
\AfterEndEnvironment{itemize}{%
    \ifbool{tightspace}{% true part:
                        \end{spacing}
                       }{%
                         % false part
                        }%
}

\usepackage{lipsum}

\begin{document}

\begin{itemize}
  \item \lipsum[1]
\end{itemize}


\begingroup
\setbool{tightspace}{true}

\verb|\setbool{tightspace}{true}| inside a group:

\begin{itemize}
  \item \lipsum[2]
\end{itemize}
\endgroup


And from now all outside of groups:

Here is \verb|\setbool{tightspace}{false}| in effect:

\begin{itemize}
  \item \lipsum[3]
\end{itemize}


\verb|\setbool{tightspace}{true}|:
\setbool{tightspace}{true}

\begin{itemize}
  \item \lipsum[4]
\end{itemize}


And again \verb|\setbool{tightspace}{false}|:
\setbool{tightspace}{false}

\begin{itemize}
  \item \lipsum[5]
\end{itemize}

\end{document}
Speravir
  • 19,491
4

use it this way:

\documentclass{article}

\let\End\end
\usepackage{etoolbox}
\AfterEndEnvironment{itemize}{bleat bleat}
\begin{document}

\begin{itemize}
\item This is an item.
\end{itemize}

\let\end\End 
\begin{itemize}
\item This is the last item.
\end{itemize}

\end{document}
  • Could you describe a bit how that works? Aren't there any interactions with other (possibly nested) environments or if one also patches another environment? – Daniel Nov 30 '11 at 22:31
  • It is obvious that etoolbox does nothing with \itemize and \enditemize, otherwise the solution of the original poster would work. etoolbox redefines the \end{itemize}. If you want to have it local then simply put the one into a group. –  Dec 01 '11 at 07:52
  • I am asking because of the \let\End\end and \let\end\End trick, which generalized effects are not immediately clear to the uninspired. Would your trick also work if: (1) I am about to patch a second environment (\AfterEndEnvironment{enumerate}{foo bar}), which (2) in the document is used nested within the first, and (3) should be "undoable" independently from the first. – Daniel Dec 01 '11 at 08:03
  • as I wrote, in that case you have to hold the undo locally –  Dec 01 '11 at 08:15
3

My idea is that one should not do this, but rather define a new environment:

\newenvironment{itemizeplus}
  {\begin{itemize}}
  {\end{itemize}}
\AfterEndEnvironment{itemizeplus}{bleat bleat}

Thus the behavior of itemize and itemizeplus will be predictable throughout the document.

Note. A more efficient definition would be

\let\itemizeplus\itemize
\let\enditemizeplus\enditemize
\AfterEndEnvironment{itemizeplus}{bleat bleat}

or, perhaps better,

\let\itemizeplus\itemize
\def\enditemizeplus{\enditemize bleat bleat}
egreg
  • 1,121,712
0

I've expanded on @Schweinebacke's excellent answer in the event that others face the same problem that I have. I'd like a single command that patches an environment and then ensures that the patch is removed after it is executed for the first time. I've made some concessions in order to achieve this:

  1. I need to associate an identifier with my hook
  2. I will pollute the global namespace with generated \csnames
  3. If I nest the environments, the hook will execute when the innermost environment ends and never again after.
\usepackage{etoolbox}
\usepackage{xparse}

% Note  : Drop a particular code block from \AfterEnvEnvironment
% author: @Schweinebacke
% see   : https://tex.stackexchange.com/a/36770/59078
\newrobustcmd*{\RemoveFromAfterEndEnvironment}[2]{%
  \expandafter\patchcmd\csname @afterend@#1@hook\endcsname{#2}{}}

% Attach a (named) group to an environment that should be executed 
% the first time that an environment ends only once after the
% environment ends
%
% Arguments:
%   #1 - The environment to patch
%   #2 - A unique identifier for your hook
%   #3 - The group to be evaluated
%
% Example:
% \OnceAfterEndEnvironment{fooscope}{firstA}
%       {\stepcounter{foocounter}\thefoocounter}
\NewDocumentCommand\OnceAfterEndEnvironment{mmm}{{%
    \expandafter\gdef\csname#2hook\endcsname{%
        % Evaluate the code block
        #3%
        % See Note [2]
        \edef\tmpargs{{#1}{\expandafter\noexpand\csname#2hook\endcsname}}%
        \expandafter\RemoveFromAfterEndEnvironment\tmpargs{}%
        % Drop the hook definition
        {\expandafter\gundef\csname#2hook\endcsname}%
    }%
    % Note [2]: Control expansion of the second argument to this function
    % Author  : @Qrrbrbirlbel
    % See     : https://tex.stackexchange.com/a/133759/59078
    \edef\tmpargs{{#1}\expandafter\noexpand\csname#2hook\endcsname}%
    \expandafter\AfterEndEnvironment\tmpargs{}%
}}

The following example demonstrates the usage. We create a simple environment fooscope, and a counter foocounter for demonstration. We register hooks from outside and inside the environment, and modify the counter inside and outside of the hooks. This test demonstrates the expected behavior of the hooks.

\newenvironment{fooscope}
    {\par\textbf{fooscope-start}\par}
    {\par\textbf{fooscope-end}\par}
\newcounter{foocounter}
\setcounter{foocounter}{0}

\OnceAfterEndEnvironment{fooscope}{firstA}{\stepcounter{foocounter}\thefoocounter} %4
\begin{fooscope}
    first scope
    \stepcounter{foocounter} %1
    \stepcounter{foocounter} %2
    \OnceAfterEndEnvironment{fooscope}{firstB}{\stepcounter{foocounter}\thefoocounter} %5
    \stepcounter{foocounter} %3
\end{fooscope}
\vspace{1em}
\begin{fooscope}
    second scope
    \OnceAfterEndEnvironment{fooscope}{secondA}{\stepcounter{foocounter}\thefoocounter} %6
\end{fooscope}
\vspace{1em}

Which produces the following output. Note that the sequence of the counter values demonstrates the order in which the hooks were invoked.

Example Rendering

If possible, it'd be highly desirable to replace this with a version that did not require the user to pass in an identifier. I'm sure someone out there who has more expertise can do it.

Rob Hall
  • 143