[Tex/LaTex] How to emphasize or highlight single words in comments in listings

listings

I'd like to highlight single words (or phrases) in the comments of C files displayed by the listings package. Using the emph key does not work in comments (apparently listings is too intelligent in case a language is set): words in comments (and string literals) don't seem to be considered – only (key)words in the normal code are.

A workaround that is based on the literate option from this answer is shown below.

I am looking for a solution that works without modifying the actual source code and it should work with lstinputlisting as well.

\documentclass{article}
\usepackage{listings,xcolor}
\lstset{
   language=C,
}
\begin{document}
% \begin{lstlisting}[emph={Hello},emphstyle=\underbar]
\begin{lstlisting}[literate={Hello}{{{\color{red}Hello}}}5]
/*
 * Hello World Program
 */
// Hello

#include <stdio.h>

int main (void) {
  printf("Hello World!\n");
  int Hello = 0;
  int hello = 0;
  return Hello;
}
\end{lstlisting}
\end{document}

The main problem with the workaround is the spacing of the matched characters.

Best Answer

The problem in your literate text are the extra braces in the replacement text.

When the second parameter of the literate key is parsed, each LaTeX token or {...}-group—let's call them item—is considered a replacement of one character of the output. Thus, {{\color{red}Hello}} is parsed as a single item and results in the wrong spacing. The 5 at the end only tells it to assume the overall replaced text has a width of 5 times the width of a single character.

So the solution is to split the replacement text over 5 (printable) items:

{\unskip\bgroup\color{red}}Hello{\unskip\egroup}

Note that the first and last items are considered output characters now, too. As the \bgroup\color{red} and \egroup parts are not printable, there is no space occupied in the output for them. However, listings inserts an additional glue before each item, which has to be removed by \unskip to get the proper overall spacing.

See the difference in output of the following example:

\documentclass{article}
\usepackage{filecontents}
\begin{filecontents*}{\jobname.c}
/*
 * Hello World Program
 */
// Hello

#include <stdio.h>

int main (void) {
  printf("Hello World!\n");
  int Hello = 0;
  int hello = 0;
  return Hello;
}
\end{filecontents*}

\usepackage{listings,xcolor}
\lstset{
    language=C,
}

\begin{document}
\lstinputlisting[literate={Hello}{{{\color{red}Hello}}}5]{\jobname.c}
\lstinputlisting[literate={Hello}{{\unskip\bgroup\color{red}}Hello{\unskip\egroup}}5]{\jobname.c}
\end{document}

enter image description here


I'd like to add a note on why the emph version didn't work. listings has different ways to define new delimited parts of the input text. Relevant in your example are the morecomment and morestring keys. Each of them is available in a non-starred (morecomment=) and in a starred (morecomment=*) version.

When the former version is used, the text between the delimiters is not scanned for other classes like keywords or emphasized words. However, for the starred version, the text is scanned for other classes and appropriate styles are applied. In the case of predefined language styles those delimited classes are defined as the non-starred version. So if you want the text replacement inside them, you have to redefine them as starred version.

Note that it doesn't seem to be possible to choose between class styles that are applied and those that are not applied inside delimited environments. To work around the unwanted side effect of keywords also being highlighted in comments or strings, we can save the currently active style before keyword style is applied and apply it only when outside of special delimited environments:

\documentclass{article}
\usepackage{filecontents}
\begin{filecontents*}{\jobname.c}
/*
 * Hello World Program
 */
// Hello

#include <stdio.h>

int main (void) {
  printf("Hello World!\n");
  int Hello = 0;
  int hello = 0;
  World greetings;
  return Hello;
}
\end{filecontents*}

\usepackage{listings,xcolor}

\makeatletter
\lstset{
    language=C,
    emph={Hello},
    emphstyle=\emphstyle,
    morekeywords={World},
    keywordstyle=\keywordstyle,
    morecomment=*[l][\special@on\color{gray}\itshape]{//},
    morecomment=*[s][\special@on\color{gray}\itshape]{/*}{*/},
    morestring=*[b][\special@on\color{green}]",
}

\newif\ifspecial@env@
\def\special@on{\global\special@env@true}
\def\special@off{\global\special@env@false}

\lst@AddToHook{DetectKeywords}{%
    \global\let\last@lst@thestyle=\lst@thestyle
}

\def\emphstyle{%
    \last@lst@thestyle
    \aftergroup\special@off
    \underbar
}

\def\keywordstyle{%
    \ifspecial@env@
        \last@lst@thestyle
    \else
        \color{blue}%
    \fi
    \aftergroup\special@off
}
\makeatother

\begin{document}
\lstinputlisting{\jobname.c}
\end{document}

enter image description here

Here the World keyword is only highlighted when outside of comments and strings, while the underline style for the emphasized word Hello is applied everywhere.