Better late than never :)
As stated by Qrrbrbirlbel in his comment, the problem is that, because %
is a (one-line) comment delimiter in the Octave
language, listings
treats your closing overlay delimiter @
as part of the comment.
One workaround is to undefine %
as a comment delimiter, but use literate
to apply the comment style when that character is encountered anyway. You also need to reset the style at the beginning of each line.
Two caveats of this approach:
- any
%
character that occurs within a string literal will mess up the highlighting,
- keywords get highlighted even in comments.
\documentclass[dvipsnames,cmyk]{beamer}
\usepackage{arev}
\usepackage{listings}
% remove navigation symbols
\setbeamertemplate{navigation symbols}{}
\lstdefinestyle{highlight}{
keywordstyle=\color{red},
commentstyle=\color{green},
}
\makeatletter
\lstdefinestyle{base}{
language = Octave,
emptylines = 1,
breaklines = true,
basicstyle = \tiny\ttfamily\color{black!40},
keywordstyle = \color{red!40},
commentstyle =\color{green!40},
moredelim = **[is][\only<+>{\color{black}\lstset{style=highlight}}]{@}{@},
%
% Undefine % as a comment delimiter, but still apply comment style when it's encountered.
deletecomment =[l]\%,
literate ={\%}{{\lst@commentstyle\%}}1,
}
% Reset the style at the beginning of every ``true'' line
\lst@AddToHook{EveryPar}{\lst@basicstyle}
\makeatother
\begin{document}
\begin{frame}[fragile]%{Listings overlay}
\begin{lstlisting}[style=base, gobble=0]
@% This comment will be highlighted; it is also bigger than the frame size so it is expected that line is breaked into at least two. Note that unwanted empty line may be generated
@
@a = 2@ % aaa
@b = 1 % aaa @
@c = 1 % aaa @
@d = 1 % aaa @
\end{lstlisting}
\end{frame}
\end{document}
Note:
I updated the whole answer to take the two edits into account.
There are a lot of little hacks, but I'm afraid the more precise we want to be using listings
, the more hacks we'll need to add.
See at the end of the answer for an alternative solution using minted
.
Solving the initial issue using listings
You can allow listings
to detect delimiters inside another delimiter by adding a *
in its definition:
morestring=*[d]{"}
Then we define #{
and }
as special delimiters. We give them their own style by adding a second pair of square brackets:
morestring=[s][]{\#\{}{\}}
Here, we add empty brackets, which means the default style will be used.
Also, don't forget to escape special characters such as #
, {
, etc.
For more detailed explanations, have a look at listings
documentation, section 3.3.
Remark:
s
option means that the beginning and ending delimiters are different, d
that they are the same.
One has to use b
instead of d
to enable backslash escaping. I made that mistake in my original answer.
It's also worth noting that Ruby, like most languages, already has a basic definition, which includes most strings, so there's no need to re-define it all
(unless we want to override it, and we will).
This is the \lstset
that produces the output as seen in the OP's first edit:
\lstdefinestyle{Ruby} {
aboveskip=3mm,
belowskip=3mm,
showstringspaces=false,
columns=flexible,
basicstyle={\footnotesize\ttfamily},
numberstyle={\tiny},
numbers=left,
keywordstyle=\color{blue},
commentstyle=\color{dkgreen},
stringstyle=\color{mauve},
breaklines=true,
breakatwhitespace=true,
tabsize=2,
morestring=[d]{'}, % wrong: should be [b]
morestring=*[d]{"},
morestring=[s][]{\#\{}{\}},
}
Solving additional issues
Keywords inside strings are getting highlighted
As Daniel said in a comment, the star in morestring=*[d]{"}
causes it to look further for more strings and keywords.
That's what we want regarding "#{
-}
strings", but it also happens for keywords.
listings
doesn't allow to specify what exactly we'll be looking for inside the strings, so we'll have find another work-around.
Now, listings
offers a **
option so that the styles of the string and its special content can be cumulated.
For example, when we do this:
morestring=**[d][\color{mauve}]{"},
keywordstyle=\bfseries,
listings
will make keywords inside double-quotes both bold and mauve. Thing is, we need to "cumulate" colors.
morestring=**[d][\color{mauve}]{"},
keywordstyle=\color{blue},
In this case, keywords inside strings are processed with \color{mauve} \color{blue}
, and that's bad: the keyword style overrides the string style.
My hack was to replace the keyword style with a new command that checks the current color and sets it to blue if it's not already mauve:
\def\bluecolorifnotalreadymauve{%
\extractcolorspec{.}\currentcolor
\extractcolorspec{mauve}\stringcolor
\ifx\currentcolor\stringcolor\else
\color{blue}%
\fi
}
(Thanks to this answer for the solution.)
Now we also lose our original #{}
fix, because its (empty) style is "cumulated" with the \color{mauve}
from ""
.
Let's cumulate it back:
morestring=[s][\color{black}]{\#\{}{\}},
Single quotes cause the #{}
problem to resurface
Just like keywords, single-quotes strings are re-processed inside double-quotes strings.
And listings
hasn't been told to look inside single-quotes strings, so we'll have to change them the same way:
morestring=**[d]{'},
And now we lose backslash escaping.
For an unknown reason, b
option doesn't work well with **
.
Well, while we're at it…
morestring=[d]{\\'},
Full updated MWE
\documentclass{article}
\usepackage{xcolor}
\usepackage[procnames]{listings}
\definecolor{dkgreen}{rgb}{0,0.6,0}
\definecolor{gray}{rgb}{0.5,0.5,0.5}
\definecolor{mauve}{rgb}{0.58,0,0.82}
\definecolor{light-gray}{gray}{0.25}
\def\bluecolorifnotalreadymauve{%
\extractcolorspec{.}\currentcolor
\extractcolorspec{mauve}\stringcolor
\ifx\currentcolor\stringcolor\else
\color{blue}%
\fi
}
\lstdefinestyle{Ruby} {
aboveskip=3mm,
belowskip=3mm,
showstringspaces=false,
columns=flexible,
basicstyle=\footnotesize\ttfamily,
numberstyle=\tiny,
numbers=left,
keywordstyle=\bluecolorifnotalreadymauve,
commentstyle=\color{dkgreen},
stringstyle=\color{mauve},
breaklines=true,
breakatwhitespace=true,
tabsize=2,
moredelim=[s][\color{black}]{\#\{}{\}}, % same as morestring in this case
morestring=**[d]{'},
morestring=[d]{\\'},
morestring=**[d]{"},
procnamekeys={def}, % bonus, for function names
procnamestyle=\color{red},
}
\lstset{language=Ruby}
\begin{document}
\begin{lstlisting}[style=Ruby,float=ht,caption={...},label={lst:sourcecode},captionpos=b]
def some_function
File.open(filename, 'w+') do |f|
[...]
# a comment
f.puts "whatever #{some_variable} another string part"
f.puts 'this string contains apostrophes: \'random word\''
f.puts 'i do love keywords like class'
f.puts "i do love keywords like class"
f.puts "now single quotes 'inside #{double quotes}'"
[...]
end
end
\end{lstlisting}
\end{document}
Output:
Alternate approach: using minted
minted
already does everything you want, and so much more! Here is a MWE:
\documentclass{article}
\usepackage{minted}
\begin{document}
\begin{listing}[H]
\begin{minted}[fontsize=\footnotesize, linenos]{Ruby}
def some_function
File.open(filename, 'w+') do |f|
[...]
# a comment
f.puts "whatever #{some_variable} another string part"
f.puts 'this string contains apostrophes: \'random word\''
f.puts 'i do love keywords like class'
f.puts "i do love keywords like class"
f.puts "now single quotes 'inside #{double quotes}'"
[...]
end
end
\end{minted}
\caption{...}
\end{listing}
\end{document}
This is the output with the default style:
The main downside of minted
is that it relies on Pygments to do the processing, which means:
It can be a bit tricky to install.
It's harder to customize. (But once we know how to, it can be very powerful.)
Best Answer
Here is one way of doing it using
moredelim
. I assumed you meant words starting withop-
, not words starting withcp-
. I didn't usekeywordsprefix
because it currently only allows for one prefix "class", and thelistings
documentation (v1.5b) still considers it a buggy feature; see subsection 4.18 about that.Remember to load
lmodern
if you want some stuff to be typeset in bold typewriter font.