Resolvendo o problema inicial usandolistings

Resolvendo o problema inicial usandolistings

Estou usando listingspara 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:

saída de exemplo

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:

solução um pouco errada

Se você comparar as duas imagens, verá que agora os apóstrofos escapados random wordnã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}

Destaques de palavras-chave desgastadas e problema de cotação aninhada

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 listingsa 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 listingsdocumentação, seção 3.3.

Observação: sopção significa que os delimitadores inicial e final são diferentes, dque são iguais. É preciso usar bem vez de dpara 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 \lstsetque 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. listingsnão permite especificar exatamente o que procuraremos dentro das strings, então teremos que encontrar outra solução alternativa.

Agora listingsoferece 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,

listingsfará 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 listingsnã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, ba 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:

código Ruby atualizado com listagens

Abordagem alternativa: usandominted

mintedjá 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:

código Ruby atualizado com cunhado

A principal desvantagem deminted é que ele depende dePigmentospara fazer o processamento, o que significa:

  1. Pode ser um pouco complicado de instalar.

  2. É mais difícil personalizar. (Mas uma vez que saibamos como fazê-lo, pode ser muito poderoso.)

informação relacionada