8

The problem:

Is it possible to highlight all the numbers inside a lstenvironment except the numbers in comments, strings or variable names?

Example of expected output (Python in Google Colaboratory):

enter image description here

What I have tried:

Using How can I change the color of digits when using the listings package? I've managed to highlight the numbers this way:

enter image description here

In particular, I thought I had found the solution in the listings package:

literate = [*]<replacement item>. . .<replacement item>

First note that there are no commas between the items. Each item consists of three arguments: {<replace>}{<replacement text>}{<length>}. <replace> is the original character sequence. Instead of printing these characters, we use <replacement text>, which takes the width of <length> characters in the output.

[...]

The optional star indicates that literate replacements should not be made in strings, comments, and other delimited text.

Because of using the star: \literate=* numbers inside strings and comments are not being colored, that's nice, but numbers in the name of variables are being colored. I have read a lot of answers about highlight numbers using listings but none of them satisfy my requirement.

I think it will be possible to programming that "every number inside my lstenvironment should be colored except the ones preceded by letter or not colored number", but I'm not sure, and I do not know where to start.

Possible duplicates that do not work for me:

  • Not viable but useful answers
  1. Listings with keywords that contain both letters and numbers

Add:

alsoletter=0123456789,
keywords={[4]@invariant,0,1,2,3,4,5,6,7,8,9},
keywordstyle={[4]\color{greenpy}}

to the definition of mypy (the lststyle I defined).

This will work only if I add manually all the numbers appearing in my code as keywords (and . in decimal numbers wouldn't be colored).

  1. Gonzalo Medina's answer here: listing package: colored numbers, but not colored in variable names

Adding to lstset: literate=*{number}{{{\color{greenpy}number}}}1 and escapeinside={!!}

  1. karlkoeller answer here: Listings: recognize numbers and `1e-3`

2 and 3 will work only if I enclose (with some escape sign, using escapeinside) manually all the numbers appearing in my code.

I can't use neither of this solutions because I have lots of code.

  • Answers that don't respond to my question.
  1. Listings package: How can I format all numbers?

  2. Coloring digits with the listings package

  3. How can I change the color of digits when using the listings package?

  4. How to color digits with the listings package

MWE:

\documentclass{article}

\DeclareFixedFont{\ttm}{T1}{txtt}{m}{n}{10} % It will be the basic font style

\usepackage{xcolor} \definecolor{purpy}{rgb}{0.769,0.02,0.894} % Custom highlighting colors \definecolor{bluepy}{rgb}{0.082,0.02,1} \definecolor{brownpy}{rgb}{0.557,0.388,0.184} \definecolor{greenpy}{rgb}{0.118,0.553,0.388} \definecolor{strpy}{rgb}{0.796,0.102,0.118} \definecolor{commentpy}{rgb}{0.024,0.514,0.078} \definecolor{Background}{rgb}{0.9,0.95,0.95}

\usepackage{listings} \lstdefinestyle{mypy}{ %My Python style definition (based on Google Colaboratory) language=Python, numbers=left, numberstyle=\footnotesize, numbersep=1em, xleftmargin=1em, framextopmargin=2em, framexbottommargin=2em, showspaces=false, showtabs=false, showstringspaces=false, columns=flexible, keepspaces=true, tabsize=4, basicstyle=\ttm, backgroundcolor=\color{Background}, keywords={as,assert,async,await,break,continue,del,elif,else,except,finally,for,from,if,import,pass,raise,return,try,while,with,yield}, keywordstyle={\ttm\color{purpy}}, keywords={[2]@invariant,False,None,True,and,class,def,global,in,is,lambda,nonlocal,not,or}, keywordstyle={[2]\ttm\color{bluepy}}, keywords={[3]@invariant,abs,all,any,ascii,bin,bool,bytearray,bytes,callable,chr,classmethod,compile,complex,delattr,dict,dir,divmod,enumerate,eval,exec,filter,float,format,frozenset,getattr,globals,hasattr,hash,help,hex,id,input,int,isinstance,issubclass,iter,len,list,locals,map,max,memoryview,min,next,object,oct,open,ord,pow,print,property,range,repr,reversed,roundset,setattr,slice,sorted,@staticmethod,str,sum,super,tuple,type,vars,zip, myfun}, % myfun should be brown in its definition keywordstyle={[3]\ttm\color{brownpy}},
stringstyle=\color{strpy}, commentstyle=\color{commentpy}, % literate= *{0}{{{\color{greenpy}0}}}1 % Coloring all the digits {1}{{{\color{greenpy}1}}}1 {2}{{{\color{greenpy}2}}}1 {3}{{{\color{greenpy}3}}}1 {4}{{{\color{greenpy}4}}}1 {5}{{{\color{greenpy}5}}}1 {6}{{{\color{greenpy}6}}}1 {7}{{{\color{greenpy}7}}}1 {8}{{{\color{greenpy}8}}}1 {9}{{{\color{greenpy}9}}}1 {.0}{{{\color{greenpy}.0}}}2 {.1}{{{\color{greenpy}.1}}}2 {.2}{{{\color{greenpy}.2}}}2 {.3}{{{\color{greenpy}.3}}}2 {.4}{{{\color{greenpy}.4}}}2 {.5}{{{\color{greenpy}.5}}}2 {.6}{{{\color{greenpy}.6}}}2 {.7}{{{\color{greenpy}.7}}}2 {.8}{{{\color{greenpy}.8}}}2 {.9}{{{\color{greenpy}.9}}}2 {e+}{{{\color{greenpy}e+}}}2 {e-}{{{\color{greenpy}e-}}}2 }

% Displaying minus symbol properly \makeatletter \lst@CCPutMacro \lst@ProcessOther{"2D}{\lst@ttfamily{-{}}{-}} @empty\z@@empty \makeatother

% Desired environment definition \lstnewenvironment{python}[1][]{ \lstset{style=mypy, frame=l, numbers=none} }{}

\begin{document}

\begin{python}
def myfun(a11,a12): return a11-a12+15+0.35; print("H3ll0 W0rld") \end{python}

\end{document}

Extra question:

Is it possible to highlight every function name automatically? (But just in its definition) Every word between def and (, just like def myfun(a11,a12):

In the example above, everytime I call myfun, its name will be displayed brown, and it is supposed to be highlighted just in its definition.

Blooment
  • 666
  • did you check https://tex.stackexchange.com/a/570717/2388? – Ulrike Fischer May 09 '21 at 10:40
  • I vote positive for your effort.... – Sebastiano May 09 '21 at 11:33
  • @UlrikeFischer I'm compiling the document with PdfLaTeX (I'm using TeXstudio/MikTeX). It is a huge document and 1. PdfLaTeX compiles it about 3 times faster, 2. Reconvert the entire document to LuaLaTeX is not viable. – Blooment May 09 '21 at 14:09
  • You can try the minted package https://www.overleaf.com/learn/latex/Code_Highlighting_with_minted – Alan Xiang May 10 '21 at 02:50
  • @AlanXiang minted needs to be run with -shell-escape, and that's a big issue. – Blooment May 10 '21 at 07:39
  • Have you considered the finalizecache and frozencache options of minted? Might be a possibility to avoid -shell-escape. – Linear Christmas May 10 '21 at 10:34
  • @LinearChristmas I don't know too much about minted. But it needs Pygments to work, and I'm trying to make this work exclusively using TeX, and I've spent a lot of hours trying to do it with listings. That's why I'm looking for a detailed answer that complement my post, not another different solution. But thanks for your help. – Blooment May 10 '21 at 16:25
  • No worries! I myself have used minted only a couple of times, and exclusively with shell-escape (my personal, small documents). From what I gather, if you use minted with finalizecache and frozencache, then Pygments/Python are *not* used at all. But you might lose some options you require from minted, or lose a degree of freedom. I do not know anything in depth, this is from a cursory look at the documentation. Good luck with your question, however! – Linear Christmas May 10 '21 at 17:15
  • Isn't listings deprecated? – hola May 11 '21 at 12:35
  • @Á.Iborra minted works because Pygments contains a syntax parser that distinguishes variable names, number literals, strings, etc. The listings package is unable to do this, it doesn't have a powerful syntax parser, which means a generic solution is essentially impossible. If you only have just several short code snippets, I would recommend using the escapeinside feature to color them manually. If you are looking for a pure LaTeX solution, you need to write a Python parser in LaTeX. – Alan Xiang May 13 '21 at 05:51
  • @Á.Iborra I cannot picture how a computer that is capable of running LaTeX is unable to run Python. LaTeX is designed for typesetting, it is simply infeasible to perform all tasks in LaTeX, which means you will need help from external tools. I understand you have spent a lot of time on this, but I assume you will still need 20+ hours to write a Python parser in LaTeX for a generic solution. Is it really worth it to use so much extra time for something that you can do with literally 2-3 lines of existing code? – Alan Xiang May 13 '21 at 05:58
  • @AlanXiang Alan Xiang 2-3 lines of existing code? 1º Minted have no options like columns=flexible + keepspaces=true that allows you to copy the source from pdf without losing the indentation. 2º Customize a pygments.style is way hard than modify listings options (e.g. to add new keywords you would need to write your own lexer. It would be such a pain trying to declare different styles of keywords, and the listo go on). – Blooment May 13 '21 at 10:59
  • @AlanXiang 3º Changes have to be made on all PCs that want to compile the document. 4º Future updates to Pygments could break your modifications and so your document may nor be correctly syntax-highlighted in the future. So, it's not like 2-3 lines of existing code. I've spent some hours with minted now and customization is way way hard than listings. – Blooment May 13 '21 at 11:01

1 Answers1

4

It is pretty clear that you cannot do this using listings alone, unless you are willing to devote a huge amount of time to it. External tools will always be helpful. If you insist on not using minted, I would say you can still benefit from Pygments. For example, you can process your Python code with the following Python script, which tries to apply different styles for numbers and function names, respectively.

from pygments.lexers.python import Python3Lexer
from pygments import highlight, lex
from pygments.token import *

py_str = r''' def myfun(a11, a12): return a11-a12+15+0.35 print("h3ll0 w0rld") '''

lex_result = list(lex(py_str, Python3Lexer()))

def listing_escape(s): return '%{})'.format(s)

def number_style(s): return listing_escape('\StyleNumber{%s}'%s)

def function_name_style(s): return listing_escape('\StyleFuncName{%s}'%s)

result = '' for item in lex_result: if is_token_subtype(item[0], Number): result += number_style(item[1]) elif is_token_subtype(item[0], Name.Function): result += function_name_style(item[1]) else: result += item[1]

print(result)

The output of the script above is:

def %*\StyleFuncName{myfun}*)(a11, a12):
    return a11-a12+%*\StyleNumber{15}*)+%*\StyleNumber{0.35}*)
print("h3ll0 w0rld")

Now, you need to modify your LaTeX source code. Basically, you need to define escapeinside and style functions for numbers and function names.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{listings}
\usepackage{xcolor}

\definecolor{purpy}{rgb}{0.769,0.02,0.894} % Custom highlighting colors \definecolor{bluepy}{rgb}{0.082,0.02,1} \definecolor{brownpy}{rgb}{0.557,0.388,0.184} \definecolor{greenpy}{rgb}{0.118,0.553,0.388} \definecolor{strpy}{rgb}{0.796,0.102,0.118} \definecolor{commentpy}{rgb}{0.024,0.514,0.078} \definecolor{Background}{rgb}{0.9,0.95,0.95}

\DeclareFixedFont{\ttm}{T1}{txtt}{m}{n}{10} % It will be the basic font style

\usepackage{listings} \lstdefinestyle{mypy}{ %My Python style definition (based on Google Colaboratory) language=Python, escapeinside={%}{)}, numbers=left, numberstyle=\footnotesize, numbersep=1em, xleftmargin=1em, framextopmargin=2em, framexbottommargin=2em, showspaces=false, showtabs=false, showstringspaces=false, columns=flexible, keepspaces=true, tabsize=4, basicstyle=\ttm, backgroundcolor=\color{Background}, keywords={as,assert,async,await,break,continue,del,elif,else,except,finally,for,from,if,import,pass,raise,return,try,while,with,yield}, keywordstyle={\ttm\color{purpy}}, keywords={[2]@invariant,False,None,True,and,class,def,global,in,is,lambda,nonlocal,not,or}, keywordstyle={[2]\ttm\color{bluepy}}, keywords={[3]@invariant,abs,all,any,ascii,bin,bool,bytearray,bytes,callable,chr,classmethod,compile,complex,delattr,dict,dir,divmod,enumerate,eval,exec,filter,float,format,frozenset,getattr,globals,hasattr,hash,help,hex,id,input,int,isinstance,issubclass,iter,len,list,locals,map,max,memoryview,min,next,object,oct,open,ord,pow,print,property,range,repr,reversed,roundset,setattr,slice,sorted,@staticmethod,str,sum,super,tuple,type,vars,zip, myfun}, % myfun should be brown in its definition keywordstyle={[3]\ttm\color{brownpy}},
stringstyle=\color{strpy}, commentstyle=\color{commentpy}, }

% Displaying minus symbol properly \makeatletter \lst@CCPutMacro \lst@ProcessOther{"2D}{\lst@ttfamily{-{}}{-}} @empty\z@@empty \makeatother

% Desired environment definition \lstnewenvironment{python}[1][]{ \lstset{style=mypy, frame=l, numbers=none} }{}

% number style \newcommand{\StyleNumber}[1]{\color{cyan}\detokenize{#1}} % function name style \newcommand{\StyleFuncName}[1]{\color{orange}\detokenize{#1}}

\begin{document}

\begin{python}
def %\StyleFuncName{myfun})(a11, a12): return a11-a12+%\StyleNumber{15})+%\StyleNumber{0.35}) print("h3ll0 w0rld") \end{python}

\end{document}

This would give you:

enter image description here

This is still a listing based solution, without minted and shell escape evil, yay!

Alan Xiang
  • 5,227
  • This is a nice solution, you avoided minted and shell escape but you forgot the main idea behind the environment: copy the source code straight from python to the environment, without annoying handmade edits, like add %* \Style * – Blooment May 15 '21 at 18:37
  • I didn't specify that, but I think it was obvious, since I said I was not using Gonzalo Medina's and karkoeller answers since I have lots of code – Blooment May 15 '21 at 18:40