Estou usando listings
para exibir o código Ruby com destaque. Tenho o seguinte documento de teste:
\documentclass{article}
\usepackage{xcolor}
\usepackage{listings}
\definecolor{dkgreen}{rgb}{0,0.6,0}
\definecolor{mauve}{rgb}{0.58,0,0.82}
\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,
sensitive = true
}
\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\''
[...]
end
end
\end{lstlisting}
\end{document}
Que se parece com isto:
Claro, #{some_variable}
está destacado em roxo/malva porque eu o configurei como stringstyle, mas isso não está correto, já que a sintaxe #{}
executará o conteúdo em vez de interpretar este bloco como string (somente se inside " "
, não with ' '
, mas eu estaria disposto a ignorar essa sutileza).
Minha dúvida é: existe uma maneira de configurar o realce para representar isso corretamente, de forma que #{some_variable}
fique com a cor padrão?
EDIT: com a resposta apresentada pelo SDF, agora fica assim:
Se você comparar as duas imagens, verá que agora os apóstrofos escapados random word
não estão sendo ignorados como antes (que era o comportamento correto).
EDIT 2: embora tenha conseguido resolver esse problema omitindo string=[d]{'},
, notei mais dois problemas. O exemplo agora fica assim:
\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}
\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,
sensitive = true,
morestring=*[d]{"},
morestring=[s][]{\#\{}{\}},
procnamekeys={def},
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}
Palavras-chave entre aspas duplas agora estão sendo destacadas, e também aspas simples dentro de aspas duplas fazem com que o problema original reapareça.
Isso está lentamente ficando fora de controle... Talvez eu realmente devesse mudar para o cunhado.
Responder1
Observação:
Atualizei toda a resposta para levar em consideração as duas edições. Existem muitos pequenos hacks, mas temo que quanto mais precisos quisermos usar listings
, mais hacks precisaremos adicionar. Veja no final da resposta uma solução alternativa usando minted
.
Resolvendo o problema inicial usandolistings
Você pode permitir listings
a detecção de delimitadores dentro de outro delimitador adicionando um *
em sua definição:
morestring=*[d]{"}
Então definimos #{
e }
como delimitadores especiais. Damos a eles seu próprio estilo adicionando um segundo par de colchetes:
morestring=[s][]{\#\{}{\}}
Aqui, adicionamos colchetes vazios, o que significa que o estilo padrão será usado. Além disso, não se esqueça de escapar caracteres especiais como #
, {
, etc. Para explicações mais detalhadas, dê uma olhada na listings
documentação, seção 3.3.
Observação:
s
opção significa que os delimitadores inicial e final são diferentes, d
que são iguais. É preciso usar b
em vez de d
para ativar o escape de barra invertida. Cometi esse erro na minha resposta original. Também vale a pena notar que Ruby, como a maioria das linguagens, já possui uma definição básica, que inclui a maioria das strings, portanto não há necessidade de redefinir tudo (a menos que queiramos substituí-la, e o faremos).
Isto é o \lstset
que produz a saída conforme visto na primeira edição do OP:
\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][]{\#\{}{\}},
}
Resolvendo problemas adicionais
Palavras-chave dentro de strings estão sendo destacadas
Como Daniel disse em um comentário, a estrela demorestring=*[d]{"}
faz com que ele procure mais cordasepalavras-chave. É isso que queremos em relação às " #{
-strings }
", mas também acontece com palavras-chave.
listings
não permite especificar exatamente o que procuraremos dentro das strings, então teremos que encontrar outra solução alternativa.
Agora listings
oferece uma **
opção para que os estilos da string e seu conteúdo especial possam ser acumulados. Por exemplo, quando fazemos isso:
morestring=**[d][\color{mauve}]{"},
keywordstyle=\bfseries,
listings
fará com que as palavras-chave entre aspas duplas fiquem em negritoemalva. O problema é que precisamos “acumular” cores.
morestring=**[d][\color{mauve}]{"},
keywordstyle=\color{blue},
Nesse caso, palavras-chave dentro de strings são processadas com \color{mauve} \color{blue}
, e isso é ruim: o estilo da palavra-chave substitui o estilo da string. Meu truque foi substituir a palavra-chave style por um novo comando que verifica a cor atual e a define como azul, se ainda não estiver malva:
\def\bluecolorifnotalreadymauve{%
\extractcolorspec{.}\currentcolor
\extractcolorspec{mauve}\stringcolor
\ifx\currentcolor\stringcolor\else
\color{blue}%
\fi
}
(Graças aesta respostapara a solução.)
Agora também perdemos nossa #{}
correção original, pois seu estilo (vazio) está "acumulado" com o \color{mauve}
from ""
. Vamos acumular de volta:
morestring=[s][\color{black}]{\#\{}{\}},
Aspas simples fazem com que o #{}
problema reapareça
Assim como as palavras-chave, as strings entre aspas simples são reprocessadas dentro de strings entre aspas duplas. E listings
não foi dito para olhar dentro de strings entre aspas simples, então teremos que alterá-las da mesma maneira:
morestring=**[d]{'},
E agora perdemos o escape da barra invertida. Por um motivo desconhecido, b
a opção não funciona bem com o **
. Bem, já que estamos nisso…
morestring=[d]{\\'},
MWE totalmente atualizado
\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}
Saída:
Abordagem alternativa: usandominted
minted
já faz tudo o que você quer e muito mais! Aqui está um 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}
Esta é a saída com o estilo padrão:
A principal desvantagem deminted
é que ele depende dePigmentospara fazer o processamento, o que significa:
Pode ser um pouco complicado de instalar.
É mais difícil personalizar. (Mas uma vez que saibamos como fazê-lo, pode ser muito poderoso.)