[Tex/LaTex] tikzmark, listings and beamer’s onlyenv

beamerlistingstikzmark

I found this limitation in the current tikzmark solution for highlighting lines of listings code in beamer. When I put the whole lot in an onlyenv environment, it seems the marks are not set, or found. Example:

\documentclass{beamer}
\usepackage{tikz}
\usepackage{listings}
\usetikzlibrary{tikzmark,fit}
\usetikzmarklibrary{listings}
\newcommand{\showmark}[2]{%
  \tikz[remember picture,overlay]{\draw[red,thick] ({pic cs:line-#1-#2-first}) -- ({pic cs:line-#1-#2-end});}%
}
\begin{document}
\begin{frame}[fragile]
\begin{onlyenv}<1>
\showmark{first}{2}
\begin{lstlisting}[name=first]
Listing
In first slide
\end{lstlisting}
\end{onlyenv}
\begin{onlyenv}<2>
\showmark{second}{2}
\begin{lstlisting}[name=second]
Listing
In second slide
\end{lstlisting}
\end{onlyenv}
\end{frame}
\end{document}

I see a red line in the first slide, but not in the second. Is there any (simple) workaround for this problem? Or am I doing something wrong?

PS. The \showmark macro is just a quick way of showing the problem.

Best Answer

Since you don't restart the value for the lines, the second line in the second listing is actually the fourth line; either restart the lines or use the proper value:

\documentclass{beamer}
\usepackage{tikz}
\usepackage{listings}
\usetikzlibrary{tikzmark,fit}
\usetikzmarklibrary{listings}
\newcommand{\showmark}[2]{%
  \tikz[remember picture,overlay]{\draw[red,thick] ({pic cs:line-#1-#2-first}) -- ({pic cs:line-#1-#2-end});}%
}
\begin{document}
\begin{frame}[fragile]
\begin{onlyenv}<1>
\showmark{aaa}{2}
\begin{lstlisting}[name=aaa]
Listing
In first slide
\end{lstlisting}
\end{onlyenv}
\begin{onlyenv}<2>
\showmark{second}{4}
\begin{lstlisting}[name=second]
Listing
In second slide
\end{lstlisting}

\end{onlyenv}
\end{frame}
\end{document}

enter image description here

Under normal conditions, using \resetcounteronoverlays (or \resetcountonoverlays for a TeX counter) would take care of preventing a counter from stepping on overlays; however, when the name=<name> option is used for a listing, the values for the line numbers are stored inside \lstno@<name> which is a macro and not a counter, so \resetbeameronoverlays will not work and there's no native way to prevent the undesired behaviour. One can manually set the counter to the appropriate value, or set explicitly the appropriate number for firstnumber, as shown below:

\documentclass{beamer}
\usepackage{tikz}
\usepackage{etoolbox}
\usepackage{listings}
\usetikzlibrary{tikzmark,fit}
\usetikzmarklibrary{listings}
\newcommand{\showmark}[2]{%
  \tikz[remember picture,overlay]{\draw[red,thick] ({pic cs:line-#1-#2-first}) -- ({pic cs:line-#1-#2-end});}%
}

\resetcounteronoverlays{lstnumber}

\begin{document}

\begin{frame}[fragile]
\begin{onlyenv}<1>
\showmark{first}{2}
\begin{lstlisting}[name=first]
Listing
In first slide
\end{lstlisting}
\end{onlyenv}
\begin{onlyenv}<2>
\showmark{second}{2}
\begin{lstlisting}[name=second,firstnumber=1]
Listing
In second slide
\end{lstlisting}
\end{onlyenv}
\end{frame}

\end{document}

or

\documentclass{beamer}
\usepackage{tikz}
\usepackage{etoolbox}
\usepackage{listings}
\usetikzlibrary{tikzmark,fit}
\usetikzmarklibrary{listings}
\newcommand{\showmark}[2]{%
  \tikz[remember picture,overlay]{\draw[red,thick] ({pic cs:line-#1-#2-first}) -- ({pic cs:line-#1-#2-end});}%
}

\resetcounteronoverlays{lstnumber}

\begin{document}

\begin{frame}[fragile]
\begin{onlyenv}<1>
\showmark{first}{2}
\begin{lstlisting}[name=first]
Listing
In first slide
\end{lstlisting}
\end{onlyenv}
\makeatletter
\def\lstno@second{1}
makeatother
\begin{onlyenv}<2>
\showmark{second}{2}
\begin{lstlisting}[name=second]
Listing
In second slide
\end{lstlisting}
\end{onlyenv}
\end{frame}

\end{document}

Related to this problem is Problem with listings when using line numbers, `name`, and `beamer` overlays. I initially though about manually resetting \lst@firstnumber, but as Andrew Swann explains in his answer, it's better to consider \lstno@<name>.