# Why doesn't \lstset{escapeinside={((}{))}} work as expected?

Here is my code:

\documentclass{article}
\usepackage{listings}
\lstset{basicstyle=\ttfamily,columns=fullflexible}
\begin{document}

\lstset{escapeinside={((:}{:))}}
\begin{lstlisting}
foo
((:\textit{bar}:))
baz
\end{lstlisting}

\lstset{escapeinside={((}{))}}
\begin{lstlisting}
foo
((\textit{bar}))
baz
\end{lstlisting}

\end{document}


Here is the output:

• As far as I understand escapeinside it should be defined with two single tokens: \lstset{escapeinside=()}. and then (\textit{bar}). Jun 2 '19 at 7:51
• @Red-Cloud I learnt about putting multiple character-tokens for escapeinside from these two answers: (1) tex.stackexchange.com/a/181309/18587 (2) tex.stackexchange.com/a/8860/18587 Jun 2 '19 at 7:56
• I see, never used it in this way. Jun 2 '19 at 8:19
• @Red-Cloud Just went through the documentation: listings.pdf. The documentation seems to be conflicting. In section 5.12, page 40, it says, escapeinside=<character><character>. But in section 9, page 56, it shows \lstset{escapeinside={(*@}{@*)}} as an example. Jun 2 '19 at 8:24
• I think this is related to a bug in listings, see Closing parenthesis as delimiter not matched when breaklines=true. However, the given solution doesn't seem to fix this issue here. Jun 2 '19 at 19:55

The package certainly allows for more than one open and close characters in escapeinside, for the code changes depending if there is one, two, or more than two characters. In the case of more than one <character> as delimiter, escapeinside works by making the first token in the open and close group active characters which look ahead and check if they are followed by the defined characters.

In the first example (let's assume, for simplicity, that you used escapeinside={(:}{:)}), ( is made an active character which looks ahead and checks if it is followed by a : character and, if so, starts the escaping. Inside the escaped code, : is made active and it looks for a ) character.

In the second example, ( is made an active character which looks ahead for another (, and starts the escaped code. Inside the escaped code, ) is made active, and looks ahead for a ). However it never finds a ) character, but a ) active character, so it doesn't end the escaped code and just prints a ) instead. The next ) does the same and prints another )1.

This problem does not show in your first example because the character made active and the character looked for are different, so it doesn't show the problem.

Here's a proposal to patch that issue and make escapeinside work with whatever character. Beware, though! I didn't test this code extensively (read: I didn't test at all except for the given code) so I don't know if fixing this will break something else. I'd say it won't because it's a very specific bit of code which doesn't seem to be used elsewhere, but the warning is here so you can't blame me afterwards :-)

I modified two definitions, \lst@CDefIt and \lst@IfNextChars@@, which are used with two token delimiters and three-or-more token delimiters, respectively. I changed the token comparison from \ifx to \if, which will ignore the catcode difference and will only compare the character itself, so it should do what you're looking for. I marked the two lines I changed. Adding this code to your preamble should do the trick:

\makeatletter
\def\lst@CDefIt#1#2#3#4#5#6#7#8{%
\ifx\@empty#2\@empty
\def#1{#6\def\lst@next{#7#4#8}\lst@next}%
\else \ifx\@empty#3\@empty
\def#1##1{%
#6%
% \ifx##1#2\def\lst@next{#7#4#8}\else % <--- Before
\if\noexpand##1\noexpand#2\def\lst@next{#7#4#8}\else % <--- After
\def\lst@next{#5##1}\fi
\lst@next}%
\else
\def#1{%
#6%
\lst@IfNextCharsArg{#2#3}{#7#4#8}%
{\expandafter#5\lst@eaten}}%
\fi \fi}
\def\lst@IfNextChars@@#1#2\relax#3{%
% \ifx#1#3% <--- Before
\if\noexpand#1\noexpand#3% <--- After
\ifx\lst@tofind\@empty
\let\lst@next\@tempa
\else
\let\lst@next\lst@IfNextChars@
\fi
\expandafter\lst@next
\else
\expandafter\@tempb
\fi}
\makeatother


1 If you read carefully, you'll notice that the explanation is faulty because it doesn't make sense for the open code to work and the close code to fail if they do the same thing. Well, it doesn't :) I imagine that the open code is very different from the close code, thus one works and the other doesn't. To be honest debugging listings on Sunday is painful, so I assumed that part of the code works and didn't try too hard finding out why.

• If only debugging listings were not painful during the week ... ;) Jun 3 '19 at 5:12