Решение первоначальной проблемы с помощьюlistings

Решение первоначальной проблемы с помощьюlistings

Я использую listingsдля отображения кода Ruby с подсветкой. У меня есть следующий тестовый документ:

\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}

Выглядит это примерно так:

пример вывода

Конечно, #{some_variable}выделено фиолетовым/лиловым цветом, потому что я установил его как стиль строки, но это не совсем правильно, так как синтаксис #{}выполнит содержимое вместо того, чтобы интерпретировать этот блок как строку (только если он находится внутри " ", а не с ' ', но я бы проигнорировал эту тонкость).

Мой вопрос в том, есть ли способ настроить подсветку так, чтобы она отображала это правильно, то есть #{some_variable}имела цвет по умолчанию?

EDIT: с ответом, представленным SDF, теперь это выглядит так:

немного неправильное решение

Если вы сравните две картинки, то увидите, что теперь экранированные апострофы вокруг random wordне игнорируются, как раньше (что было правильным поведением).

EDIT 2: хотя я смог решить эту проблему, исключив string=[d]{'},, я заметил еще две проблемы. Пример теперь выглядит так:

\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}

Неправильное выделение ключевых слов и проблема с вложенными цитатами

Ключевые слова внутри двойных кавычек теперь подсвечиваются, а одинарные кавычки внутри двойных приводят к повторному возникновению исходной проблемы.

Это постепенно выходит из-под контроля... Может, мне действительно стоит перейти на Minted.

решение1

Примечание: Я обновил весь ответ, чтобы учесть эти два изменения. Есть много маленьких хаков, но я боюсь, что чем точнее мы хотим использовать listings, тем больше хаков нам нужно будет добавить. Альтернативное решение с использованием смотрите в конце ответа minted.

Решение первоначальной проблемы с помощьюlistings

Вы можете разрешить listingsобнаружение разделителей внутри другого разделителя, добавив *в его определение:

morestring=*[d]{"}

Затем мы определяем #{и }как специальные разделители. Мы придаем им собственный стиль, добавляя вторую пару квадратных скобок:

morestring=[s][]{\#\{}{\}}

Здесь мы добавляем пустые скобки, что означает, что будет использоваться стиль по умолчанию. Также не забудьте экранировать специальные символы, такие как #, {и т. д. Для более подробных объяснений посмотрите listingsдокументацию, раздел 3.3.

Замечание: soption означает, что начальный и конечный разделители различны, dчто они одинаковы. Нужно использовать bвместо , dчтобы включить экранирование обратной косой черты. Я допустил эту ошибку в своем первоначальном ответе. Также стоит отметить, что Ruby, как и большинство языков, уже имеет базовое определение, которое включает большинство строк, поэтому нет необходимости переопределять все это (если только мы не захотим переопределить его, а мы это сделаем).

Это то, \lstsetчто создает вывод, как показано в первой правке автора:

\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][]{\#\{}{\}},
}

Решение дополнительных проблем

Ключевые слова внутри строк подсвечиваются

Как сказал Дэниел в комментарии, звезда вmorestring=*[d]{"} заставляет его искать больше строкиключевые слова. Это то, что нам нужно относительно " #{- }строк", но это также происходит и с ключевыми словами. listingsне позволяет указать, что именно мы будем искать внутри строк, поэтому нам придется искать другой обходной путь.

Теперь, listingsпредлагает **возможность, чтобы стили строки и ее специального содержимого могли быть кумулятивными. Например, когда мы делаем это:

morestring=**[d][\color{mauve}]{"},
keywordstyle=\bfseries,

listingsсделает ключевые слова внутри двойных кавычек жирнымиилиловый. Дело в том, что нам нужно «кумулировать» цвета.

morestring=**[d][\color{mauve}]{"},
keywordstyle=\color{blue},

В этом случае ключевые слова внутри строк обрабатываются с помощью \color{mauve} \color{blue}, и это плохо: стиль ключевых слов переопределяет стиль строки. Мой хак заключался в замене стиля ключевых слов новой командой, которая проверяет текущий цвет и устанавливает его на синий, если он еще не лиловый:

\def\bluecolorifnotalreadymauve{%
    \extractcolorspec{.}\currentcolor
    \extractcolorspec{mauve}\stringcolor
    \ifx\currentcolor\stringcolor\else
        \color{blue}%
    \fi
}

(Благодаряэтот ответдля решения.)

Теперь мы также теряем наше первоначальное #{}исправление, потому что его (пустой) стиль "кумулируется" с \color{mauve}from "". Давайте кумулируй его обратно:

morestring=[s][\color{black}]{\#\{}{\}},

Одинарные кавычки приводят #{}к повторному появлению проблемы

Как и ключевые слова, строки в одинарных кавычках повторно обрабатываются внутри строк в двойных кавычках. И listingsне было сказано искать внутри строк в одинарных кавычках, поэтому нам придется изменить их таким же образом:

morestring=**[d]{'},

И теперь мы теряем экранирование обратной косой черты. По неизвестной причине boption не работает с **. Ну, раз уж мы об этом…

morestring=[d]{\\'},

Полностью обновленный 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}

Выход:

обновленный код Ruby с листингами

Альтернативный подход: использованиеminted

mintedуже делает все, что вы хотите, и даже больше! Вот 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}

Это вывод со стилем по умолчанию:

обновленный код Ruby с помощью minted

Главный недостатокminted является то, что он полагается наПигментыдля выполнения обработки, что означает:

  1. Его установка может оказаться немного сложной.

  2. Его сложнее настроить. (Но как только мы узнаем, как это сделать, он может стать очень мощным.)

Связанный контент