0

Leaning from Macro with lstlisting inside, I construct my environment drenv with lstlisting inside. But it doesn't work. How to improve my code to make it work?

Code:

\documentclass{article}
\usepackage{listings}

\ExplSyntaxOn \NewDocumentEnvironment{drenv}{m +v O{}} { \begin{minipage}{#1} \exp_args:Nx \scantokens% { \string\begin{lstlisting}[\unexpanded{#3}] #2 \string\end{lstlisting} } \end{minipage} }{} \ExplSyntaxOff

\begin{document} \begin{drenv}{6in} some code\rule{1in}{5pt}\par \fbox{frame box} \end{drenv}[xleftmargin=2in] \end{document}

Edit: According to the third solution in the answer(by \lstnewenvironment), I tried the follow codes, and fails either. What's wrong and how to make it work?

\documentclass{article}
\usepackage{listings}
\usepackage{xparse}
%------------------------------
\newsavebox{\mybox}
\NewDocumentEnvironment{dr}{O{}m}
{
  \begin{lrbox}{\mybox}
  \begin{minipage}{#2}
}
{
  \end{minipage}
  \end{lrbox}
  \colorbox{#1}{\usebox\mybox}
}
\lstnewenvironment{drenv}[3][]
 {
   \dr[#2]{#3}
   \lstset{#1}
 }
 {
   \enddr
 }
%------------------------------
\begin{document}
\begin{drenv}{red}{5in}
\rule{2in}{5pt}
\end{drenv}
\end{document}
lyl
  • 2,727

1 Answers1

1

Using newverbs to collect the environment body, write it to a file, then \input it.

Note that newverbs unfortunately might do some unexpected things with the literal TAB character (convert to space or removed). Nevertheless if you write to file instead of \scantokens, XeTeX will have problem with TAB characters anyway: write - How to output a tabulation into a file - TeX - LaTeX Stack Exchange. Solution using my package below doesn't have this limitation, or you can probably patch it manually if you use newverbs package.

Alternative solution using \scantokens is also provided. But this way you can more easily inspect the tempfile when something goes wrong.

Note that the mandatory arg and the optional arg are detokenized (so don't put funny catcode tokens there), and I assume that no optional arg is equivalent to empty optional arg.

The syntax is a little different, both to make the implementation easier, and arg after environment end is not really a good idea anyway xparse - Environment with a mandatory or optional argument behind the \end{env} - TeX - LaTeX Stack Exchange.

(not that it's impossible to implement the other things. Just that I don't really see the point of doing it)

Having a final optional argument omitted will probably confuse newverbs, see spacing - NewDocumentEnvironment optional argument changes behaviour with \obeylines - TeX - LaTeX Stack Exchange, so I put it before the mandatory argument.

The code in this answer does not obey expl3's naming convention (e.g. __tempwrite should be \g_lyl_tempwrite_file where the lyl is your own prefix. See expl3.pdf for more details). Use at your own risk.

By the way, you'd better not storing important document into a file named tempfile.tex otherwise you're going to have problems.


\documentclass{article}
\usepackage{listings}
\usepackage{newverbs}

\ExplSyntaxOn

\iow_new:N __tempwrite

\cs_new_protected:Npn __processcontent:nnn #1 #2 #3 { % #1 is the mandatory argument of the environment % #2 is the verbatim content of the environment body % #3 is the optional argument \tl_set:Nn __content { \begin{minipage}{#1} ^^J \begin{lstlisting}[#3] ^^J #2 ^^J } \tl_put_right:Nx __content { \string \end } \tl_put_right:Nn __content { {lstlisting} ^^J \end{minipage} } }

\cs_generate_variant:Nn \str_replace_all:Nnn {Nx} \cs_generate_variant:Nn __processcontent:nnn {VVV}

\cs_new_protected:Npn __continue: { \str_replace_all:Nxn __saved_environment_body {\cs_to_str:N ^^M} {^^J} __processcontent:VVV __saved_mandatory_arg __saved_environment_body __saved_optional_arg

\iow_open:Nn \__tempwrite {tempfile.tex}
\exp_args:NNV \iow_now:Nn \__tempwrite \__content
\iow_close:N \__tempwrite
\input {tempfile.tex}

% the 4 lines above can be replaced by::
%\exp_args:NV \scantokens \__content
%in that case the iow_new:N would be unnecessary

}

\NewDocumentEnvironment{drenv}{O{}m} { \collectverbenv{ \str_gset:Nn __saved_optional_arg {#1} \str_gset:Nn __saved_mandatory_arg {#2} \str_gset:Nn __saved_environment_body } }{ __continue: } \ExplSyntaxOff

\begin{document} \begin{drenv}[xleftmargin=2in]{6in} some code\rule{1in}{5pt}\par \fbox{frame box} \end{drenv} \end{document}

Output is as you expect...

output image

It's not explicitly documented in newverbs.pdf that endlinechar is active char 13, but it is the case.

It's also not documented in interface3.pdf that \iow_now:n will force newlinechar=10, but it is the case. (x-expanding the \iow_newline: token is the only documented safe way)


Package advertisements:

Personally, I don't like it when I have to use elaborate hacks to get tokens with weird catcode into the token list, so I wrote a package precattl for this.

Also for the purpose of collecting environment body verbatim there's also my package saveenv.

\documentclass{article}
\usepackage{listings}
\usepackage{saveenv}
\usepackage{precattl}

\ExplSyntaxOn

\iow_new:N __tempwrite

\precattl_exec:n { \cs_new_protected:Npn __processcontent:nnn #1 #2 #3 { % #1 is the mandatory argument of the environment % #2 is the verbatim content of the environment body % #3 is the optional argument \tl_set:Nn __content { \begin{minipage}{#1} ^^J \cO\begin{lstlisting}[#3] ^^J #2 ^^J \cO\end{lstlisting} ^^J \end{minipage} } }

\cs_generate_variant:Nn __processcontent:nnn {nV}

\NewDocumentEnvironment{drenv}{O{}m} { \saveenv __saved_environment_body }{ \endsaveenv

\str_replace_all:Nnn \__saved_environment_body  {\cO\^^M} {^^J}
\__processcontent:nVn {#2} \__saved_environment_body {#1}

\iow_open:Nn \__tempwrite {tempfile.tex}
\exp_args:NNV \iow_now:Nn \__tempwrite \__content
\iow_close:N \__tempwrite
\input {tempfile.tex}

% the 4 lines above can be replaced by::
%\exp_args:NV \scantokens \__content
%in that case the iow_new:N would be unnecessary

}

}

\ExplSyntaxOff

\begin{document} \begin{drenv}[xleftmargin=2in]{6in} some code\rule{1in}{5pt}\par \fbox{frame box} \end{drenv} \end{document}


For the purpose of this particular new environment, you actually don't need to rescan the tokens, lstnewenvironment can be used instead:

\lstnewenvironment{drenv}[2][]
 {
 \minipage {#2}
 \lstset{#1}
 }
 {
            \endminipage
 }

Usage is identical to the above.

user202729
  • 7,143
  • Many thanks for these wonderfue solutions!! And I just make an edit of my question. Would you have a look at it? – lyl Jul 28 '22 at 02:14
  • @lyl Not sure. You might need to use the plain form (although it becomes a different question already...?), or lstnewenvironment might simply not support that; in that case do the auxiliary file/scantokens approach. – user202729 Jul 28 '22 at 02:18