5

I need to execute a \renewenvironment within a group. Using \global\renewenvironment is not sufficient.

What changes do I need to make to the \DisableMyEnvironment macro in the MWE below so as to achieve the equivalent of \global\renewenvironment?

Currently, the MWE yields:

enter image description here

Once this is working as desired, the second line in blue should be in black.

Notes:

  • There are two test cases. The default one is within a \foreach and commenting out \def\ForeachTestCase{} uses a simple group.

References:

Code:

\def\ForeachTestCase{}%

\documentclass{article}
\usepackage{xcolor}
\usepackage{xstring}
\usepackage{tikz}

\newenvironment{MyEnvironment}[1][blue]{%
    \begingroup
        \color{#1}%
}{%
    \endgroup
}%

\newcommand*{\DisableMyEnvironment}{%
    %% How make this global??
    \renewenvironment{MyEnvironment}[1][]{}{}%
}%

\newcommand*{\MyTestText}[1]{%
    Some before text

    \begin{MyEnvironment}
        This should be in #1.
    \end{MyEnvironment}

    Some after text%
}

\begin{document}

\MyTestText{blue}

%\DisableMyEnvironment% <-- This works, but want it to work when used in a group as follows
\ifdefined\ForeachTestCase
    \foreach \x in {1,...,3} {%
        \IfStrEqCase{\x}{%
            {1}{}%
            {2}{\DisableMyEnvironment}%
            {3}{}%
        }%
    }
\else
    \begingroup
        \DisableMyEnvironment
    \endgroup
\fi

\medskip\par
\MyTestText{black}

\end{document}
  • I don't know your real application, but one option is to use \aftergroup\DisableMyEnvironment. I don't know if this will do what you want, though. – Phelype Oleinik Aug 28 '18 at 18:47
  • @PhelypeOleinik: Yep, that works fine in the MWE. It doesn't seem to work in my actual use case (which is within a \fpreach). Will look into it and try to figure it out. – Peter Grill Aug 28 '18 at 18:50
  • I read somewhere that a \foreach does its thing inside two grouping levels, so \aftergroup\aftergroup\aftergroup\DisableMyEnvironment should do. Not the prettiest code there is though :P – Phelype Oleinik Aug 28 '18 at 18:52
  • @PhelypeOleinik: Yep that was it. Thanks. You should post an answer as that seems to be the cleanest approach and does not require dealing with both the \environement and \endenvironment separately. – Peter Grill Aug 28 '18 at 19:16
  • It's a bit hacky, but would work: {\let\def\gdef\renewenvironment{MyEnvironment}[1][]{}{}} and it's global! – Andreas Storvik Strauman Aug 28 '18 at 19:29
5

Create a dummy (or alternate) environment in the global scope (preamble), here, XEnvironment, and then in the \DisableMyEnvironment macro, globally reassign by way of

\global\let\MyEnvironment\XEnvironment%
\global\let\endMyEnvironment\endXEnvironment%

MWE:

\documentclass{article}
\usepackage{xcolor}
\newenvironment{XEnvironment}[1][]{}{}%
\newenvironment{MyEnvironment}[1][blue]{%
    \begingroup
        \color{#1}%
}{%
    \endgroup
}%

\newcommand*{\DisableMyEnvironment}{%
    %% How make this global??
    \global\let\MyEnvironment\XEnvironment%
    \global\let\endMyEnvironment\endXEnvironment%
}%

\newcommand*{\MyTestText}[1]{%
    Some before text

    \begin{MyEnvironment}
        This should be in #1.
    \end{MyEnvironment}

    Some after text%
}

\begin{document}

\MyTestText{blue}

%\DisableMyEnvironment% <-- This works, but want it to work when used in a group as follows
\begingroup
    \DisableMyEnvironment
\endgroup

\medskip\par
\MyTestText{black}


\end{document}

enter image description here

  • Thanks. I was initially opposed to using two separate commands to disable and define a new dummy environment, but then this does seem simpler to use as then you don't have to worry about which grouping level you are in (works fine for both the original text case and the new \foreach case. – Peter Grill Aug 28 '18 at 19:27
  • @PeterGrill I am not sure why using 2 commands should be an issue. For example, to use a changed catcode in a macro, the catcode has to be changed prior to the macro definition, so that it takes effect when the macro's argument gets tokenized. While cumbersome, nobody really complains that the catcode change should occur inside the macro definition. In summary, it's all in the preamble...it's not like you have to define things on the fly. – Steven B. Segletes Aug 28 '18 at 19:46
  • 1
    Agreed. I attempted to say that with the "initially".. Hiding it in the preamble is perfectly fine. – Peter Grill Aug 28 '18 at 20:56
3

Edit: First approach didn't seem to work with optional arguments.

Edit 2: Found an easier way using \globaldefs:

Final answer:

In spirit of answering how to do a \renewenvironment global:

\documentclass{article}
\begin{document}
  \newenvironment{test}{HELLO}{WORLD}
  \bgroup
    \globaldefs=1
    \renewenvironment{test}[1][world]{foo #1}{bar}
  \egroup
  \begin{test}[hello]
  \end{test}
\end{document}

Second answer:

Two commands loosely inspired by \makeatletter and \makeatother: \makerenewglobal and \makerenewlocal. When \makerenewglobal is called, \renewenvironment do only global definitions. If \makerenewlocal is called, it does the oposite: makes all \renewenvironment-definitions local.

\documentclass{article}
\makeatletter
\let\@local@newenv\@newenv
\long\def\@global@newenv#1#2#3#4{%
  \@ifundefined{#1}{\global\expandafter\let\csname#1\expandafter\endcsname\csname end#1\endcsname}{\relax}
  {\let\def\gdef\expandafter\new@command\csname #1\endcsname#2{#3}}%
  \l@ngrel@x\expandafter\gdef\csname end#1\endcsname{#4}%
}
\gdef\makerenewglobal{\global\let\@newenv\@global@newenv}
\gdef\makerenewlocal{\global\let\@newenv\@local@newenv}
\begin{document}
  \newenvironment{test}{HELLO}{WORLD}
  \bgroup\makerenewglobal
    \renewenvironment{test}[1][world]{foo #1}{bar}
  \egroup
  \begin{test}[hello]
  \end{test}
\end{document}

First answer:

In spirit of semi-generality; a command, \makeenvglobal{theenv}, that makes a environment global:

\documentclass{article}
\gdef\makeenvglobal#1{%
  \global\expandafter\expandafter\let\csname #1\expandafter\endcsname\csname #1\endcsname
  \global\expandafter\expandafter\let\csname end#1\expandafter\endcsname\csname end#1\endcsname
}
\begin{document}
  \newenvironment{test}{HELLO}{WORLD}
  \bgroup
    \renewenvironment{test}{foo}{bar}
    \makeenvglobal{test}
  \egroup
  \begin{test}
  \end{test}
\end{document}

prints foo bar

  • Does this need to be altered to handle the case where the environment having an optional parameter (as in the MWE)? – Peter Grill Aug 28 '18 at 19:17
  • @PeterGrill Oh. Yeah somethings going on. Give me a minute! – Andreas Storvik Strauman Aug 28 '18 at 19:22
  • @PeterGrill See new answer! – Andreas Storvik Strauman Aug 28 '18 at 19:40
  • Yep, revised answer works. – Peter Grill Aug 28 '18 at 21:00
1

As requested :)

I read somewhere here that \foreach executes its commands inside two groups. You can check this statement with this (requires e-TeX):

\documentclass{article}
\usepackage{pgffor}
\begin{document}
\foreach \i in {0}{\the\currentgrouplevel}
\end{document}

which will print 2 :)

If you want to do a definition outside the scope of this \foreach, then you can use a couple of \aftergroups. In your case you want to run \DisableMyEnvironment (one token) outside of two grouping levels.

If you do \aftergroup\DisableMyEnvironment, this will insert \DisableMyEnvironment after the first group (one \aftergroup per token). To make it reach the second level, you have to \aftergroup that (two tokens), so \aftergroup\aftergroup \aftergroup\DisableMyEnvironment.

Have in mind that this is not a global assignment, rather just local to another grouping level. If you are in, say, a third grouping level, the changes will be restored after the third one ends.


Just a few days ago I was doing something (can't remember what though --- or can I?) and was using a very long chain of \aftergroups, and was getting it wrong because I was missing one or another. What did I do? An even longer chain of \expandafter =D

I made a (probably suboptimal) macro \afterNgroups{<N>}{<token-list>} that inserts the <token-list> after <N> groups:

\makeatletter
\def\afterNgroups#1#2{%
  \count@\z@
  \toks@{#2}%
  \loop
  \ifnum\count@<#1 %
    \toks2{}%
    \expandafter\@tfor\expandafter\tkn\expandafter:\expandafter=\the\toks@\do{%
      \toks2
        \expandafter\expandafter\expandafter
       {\expandafter\the\expandafter\toks\expandafter2\expandafter\aftergroup\tkn}}%
      \toks@\expandafter{\the\toks2}%
    \advance\count@\@ne
  \repeat
  \the\toks@}
\makeatother

you can use this macro for your case with \afterNgroups{2}{\DisableMyEnvironment}. It apparently works for trivial cases. Didn't test it extensively though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.