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.

13

1 Answer 1

4
+300

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!

2
  • 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, 2021 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, 2021 at 18:40

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .