[Tex/LaTex] Framing two-column listings with a tikz node : “missing \item” error

listingsmulticoltikz-pgf

I'm trying to use this answer to put my lstlistings environments inside a tikz node. So I want to define a new listings environment, with \lstnewenvironment.

My final goal, to be precise, is to ensure the proper framing of two-column listings generated through the multicol package using the multicols option of listings (ยง5.8 of the listings manual). Tikz comes in because 2-columns framing is ugly with the frame argument of listings, see the end of this pdf for an example.

But I also have single-column listings in my document that I'd like to decorate with tikz, and I'd love to have a single command to typeset both 2-columns and single-column listings. Fortunately, the listings manual suggests something: simply copying an optional argument of the new listings environment inside an lstset command (p.41).

That's what I tried to do:

\documentclass{minimal}

\usepackage{listings}
\usepackage{tikz}
\usepackage{multicol}

\tikzstyle{sourcecodebox} = [
 fill=white, draw=black, inner sep=1em, rectangle
]

\makeatletter
\lstnewenvironment{lstfigurelisting}[1][]{%
  \lstset{frame=none,#1}%
  \setbox\@tempboxa=\hbox\bgroup\color@setgroup
}%
{%
    \color@endgroup\egroup
    \begin{tikzpicture}
        \node[sourcecodebox] (box)
            {\makebox[.75\linewidth][l]{\box\@tempboxa}};
    \end{tikzpicture}
}
\makeatother

\begin{document}

Some code here:
\begin{lstfigurelisting}[multicols=2]
  #!/usr/bin/env python
  def main():
  print "Hello, World!"
  if __name__ == '__main__':
  main()
\end{lstfigurelisting}

\end{document}

Unfortunately, I get a Something's wrong--perhaps a missing \item pointing at multicols=2.

If I remove that optional argument, or change it to something innocuous (e.g. breaklines=true, everything works (so at least I get the framing for single-column listings). I've read the FAQ on the missing \item error, but I don't understand how this would apply, here. I do understand that the plain TeX \setbox is used to save the whole environment content in a TeX box register, but I don't see how the multi-column environment, specifically, would insert a list inside that box.

Does anybody have an idea of what's going on ? Any other alternatives to suggest as to reaching my final goal (1 and 2-column listings inside a tiks node) ?

Best Answer

I don't really know anything about listings or multicol and particularly haven't a clue as to why they complain so much about being put in a box (presumably to do with how multicol works its magic). So I went back to the original specification: to get a listings listing in a columnar setup within a TikZ node. I came up with the following solution. What seemed to be the key was to avoid precisely that situation that your code lands in: that something reads in the listing ahead of time and then resets it somewhere else. Several of the commands that I chose to use had a "read ahead" version, and I found that the code did not work if I chose that one. For example, I had to use a minipage instead of a \parbox.

Anyway, this will need tweaking I'm sure - particularly with regard to line widths - but it does seem to do the basic "put a 2-column listing in a TikZ node".

\documentclass{article}

\usepackage{listings}
\usepackage{tikz}
\usepackage{multicol}

\tikzstyle{sourcecodebox} = [
 fill=white, draw=black, inner sep=1em, rectangle
]

\makeatletter
\lstnewenvironment{lstfigurelisting}[1][]{%
  \tikzpicture
  \node[sourcecodebox] (box)
  \bgroup
  \minipage{.75\textwidth}
  \lstset{frame=none,#1}
}
{
\endminipage
\egroup;
\endtikzpicture
}
\makeatother

\begin{document}

Some code here:

\begin{lstfigurelisting}[multicols=2]
#!/usr/bin/env python
  def main():
  print "Hello, World!"
  if __name__ == '__main__':
  main()
\end{lstfigurelisting}
\end{document}

Result:

listings in a node

Related Question