5

despite my precarious knowledge of the environment, I have been able to style listings to my taste (which is nothing fancy) for modern JavaScript.

The show-stopper is that I need to syntax-highlight React.js, which isn't properly rendered via listing. I like very much how I've been able to zebra/alternate the lines' background color, line numbers, fonts, etc. but the jsx language isn't recognized. I've searched extensively for ways to make it work, and apparently the recommendation is to switch to the Minted package altogether, and install a separate third-party jsx lexer.

It works, but I haven't found a way to alternate the background color of each line in the zebra fashion.

Screenshot

Any suggestion on how to turn the Minted output to something closer to the listing example (both in terms of line highlighting and, if possible, to line numbers, etc) would be greatly appreciated.

MWE (requires --shell-escape and jsx-lexer):

\documentclass[]{scrbook}

\usepackage{xcolor} \usepackage[]{cleveref} \usepackage{listings}

\definecolor{listing-background}{HTML}{FFFFFF} \definecolor{listing-background-alternate}{HTML}{F8F8F8} \definecolor{listing-rule}{HTML}{B3B2B3} \definecolor{listing-numbers}{HTML}{B3B2B3} \definecolor{listing-text-color}{HTML}{000000} \definecolor{listing-keyword}{HTML}{007F00} \definecolor{listing-keyword-2}{HTML}{1284CA} \definecolor{listing-keyword-3}{HTML}{9137CB} \definecolor{listing-keyword-4}{HTML}{407F7F} \definecolor{listing-identifier}{HTML}{000000} \definecolor{listing-identifier}{HTML}{435489} \definecolor{listing-string}{HTML}{BA2121} \definecolor{listing-comment}{HTML}{8E8E8E}

\lstdefinelanguage{es6}{ morekeywords=[1]{break, continue, delete, else, for, function, if, in, new, return, this, typeof, var, void, while, with, await, async, case, catch, class, const, default, do, enum, export, extends, finally, from, implements, import, instanceof, let, static, super, switch, throw, try }, morekeywords=[2]{false, null, true, boolean, number, undefined, Array, Boolean, Date, Math, Number, String, Object }, morekeywords=[3]{eval, parseInt, parseFloat, escape, unescape }, otherkeywords = {+,-}, sensitive, morecomment=[s]{/}{/}, morecomment=[l]//, morecomment=[s]{/*}{/}, morestring=[b]', morestring=[b]" }[keywords, comments, strings]

\makeatletter \let\old@lstKV@SwitchCases\lstKV@SwitchCases \def\lstKV@SwitchCases#1#2#3{} \makeatother \usepackage{lstlinebgrd} \makeatletter \let\lstKV@SwitchCases\old@lstKV@SwitchCases

\lst@Key{numbers}{none}{% \def\lst@PlaceNumber{\lst@linebgrd}% \lstKV@SwitchCases{#1}% {none:\% left:\def\lst@PlaceNumber{\llap{\normalfont \lst@numberstyle{\thelstnumber}\kern\lst@numbersep}\lst@linebgrd}\% right:\def\lst@PlaceNumber{\rlap{\normalfont \kern\linewidth \kern\lst@numbersep \lst@numberstyle{\thelstnumber}}\lst@linebgrd}% }{\PackageError{Listings}{Numbers #1 unknown}@ehc}} \makeatother

\lstdefinestyle{fancylisting}{ basicstyle=\ttfamily\linespread{1.0}\color{listing-text-color}\small, numbers = left, xleftmargin = 2.7em, framexleftmargin = 2.5em, backgroundcolor = \color{listing-background}, breaklines = true, frame = single, framesep = 0.19em, rulecolor = \color{listing-rule}, frameround = ffff, tabsize = 4, numberstyle = \color{listing-numbers}\footnotesize\ttfamily{}, linebackgroundcolor={\ifodd\value{lstnumber}\color{listing-background-alternate}\fi}, aboveskip = 1.2em, belowskip = 1em, abovecaptionskip = 0em, belowcaptionskip = 1.0em, keywordstyle = {\color{listing-keyword}\bfseries}, keywordstyle = {[2]\color{listing-keyword-2}}, keywordstyle = {[3]\color{listing-keyword-3}\bfseries\itshape}, keywordstyle = {[4]\color{listing-keyword-4}}, sensitive = true, identifierstyle = \color{listing-identifier}, commentstyle = \color{listing-comment}, stringstyle = \color{listing-string}, showstringspaces = false, escapeinside = {/@}{@/} } \lstset{style=fancylisting}

\lstnewenvironment{es6}[1][] {\lstset{ language=es6, morekeywords=[4]{+,<,>,-,=}, #1 }} {}

\lstnewenvironment{bash}[1][] {\lstset{ language=bash, linebackgroundcolor=\color{white}, backgroundcolor=\color{white}, numbers=none, frame=l, framerule=0.5pt, rulecolor = \color{listing-rule}, #1 }} {}

\crefname{lstlisting}{listing}{listings} \Crefname{lstlisting}{Listing}{Listings} \crefname{lstinputlisting}{listing}{listings} \Crefname{lstinputlisting}{Listing}{Listings}

\usepackage{fancyvrb} \usepackage[cache=false,outputdir=.texpadtmp]{minted}

\begin{document}

Minted (syntax recognized, ugly formatting)

\begin{minted}{jsx} const BlogTitle = ({ children }) => ( <h3>{children}</h3> ); // class component class BlogPost extends React.Component { renderTitle(title) { return <BlogTitle>{title}</BlogTitle> }; render() { return ( <div className="blog-body"> {this.renderTitle(this.props.title)} <p>{this.props.body}</p> </div> ); } } \end{minted}

Listing (syntax not recognized, desired formatting):

\begin{es6} const BlogTitle = ({ children }) => ( <h3>{children}</h3> ); // class component class BlogPost extends React.Component { renderTitle(title) { return <BlogTitle>{title}</BlogTitle> }; render() { return ( <div className="blog-body"> {this.renderTitle(this.props.title)} <p>{this.props.body}</p> </div> ); } } \end{es6}

\end{document}

Thank you!

unDavide
  • 265
  • 3
    Hi! Could you please add a complete, compilable document that shows the problem? It will ease the task of looking for a solution a great deal. See minimal working example (MWE) – Rmano Mar 15 '21 at 11:54
  • 1
    Hi, I've added a MWE and clarified a bit the question (for some reason the initial greeting in the post's first line has been eaten out, but the rest of the content is there). Thanks! – unDavide Mar 15 '21 at 13:48
  • Maybe this old question might help with the line numbering at least https://tex.stackexchange.com/questions/376090/highlight-function-in-minted-with-background-color?rq=1 – StiggyStardust Mar 17 '21 at 17:45

1 Answers1

4

This is an experimental solution with a lot of tweaking! For it to work properly, you need to manually specify the line height and guarantee that all lines have equal line height. Another problem is that my pygments does not have jsx lexer. Therefore, you may want to change the code to minted language=jsx on your end.

enter image description here

The code

  • It is important to change the value of \g_lst_line_height_dim based on your document setup. Otherwise, the background and foreground may not be aligned. If you are changing the margins of the tcolorbox, then all other four margin variables needs to be changed.
  • It is possible to use more than two colors in the cycle.
  • If you uncomment \bool_gset_true:N \g_lst_debug_bool, then the code listing will be rendered in debug mode. In debug mode, it is easier to determine the parameters for the listing box. Here is an example output of debug mode: enter image description here
\documentclass{scrbook}
\usepackage{tcolorbox}
\usepackage{fancyvrb}
\usepackage{expl3}
\usepackage{tikz}
\usepackage{xcolor}

\usetikzlibrary{math, calc} \tcbuselibrary{listings, minted, hooks, skins}

% change line number style \renewcommand{\theFancyVerbLine}{% \ttfamily\textcolor[rgb]{0.3,0.3,0.3}{\tiny{\arabic{FancyVerbLine}}}% }

\definecolor{listing-background}{HTML}{FFFFFF} \definecolor{listing-background-alternate}{HTML}{F8F8F8}

\makeatletter \tcbset{ lststyle/.style={ enhanced, left=8mm, top=0mm, bottom=0mm, boxsep=0.5mm, listing only, listing engine=minted, minted language=html, % I don't have jsx lexer on my computer arc=0mm, colframe=black!5, colback=white, minted options={ obeytabs, breaklines, linenos, autogobble, fontsize=\scriptsize, numbersep=2.5mm, }, underlay=\my@lst@underlay } }

\ExplSyntaxOn

% several variables that need tuning when the configuration changes

% first of all, define the margins of the box \dim_new:N \g_lst_left_dim \dim_new:N \g_lst_right_dim \dim_new:N \g_lst_top_dim \dim_new:N \g_lst_bottom_dim

\dim_gset:Nn \g_lst_left_dim {7.5mm} \dim_gset:Nn \g_lst_right_dim {0.5mm} \dim_gset:Nn \g_lst_top_dim {0.5mm} \dim_gset:Nn \g_lst_bottom_dim {0.5mm}

% next step, define the line height \dim_new:N \g_lst_line_height_dim \dim_gset:Nn \g_lst_line_height_dim {9.38pt}

% this is a switch to turn on debug mode for determining the parameters above \bool_new:N \g_lst_debug_bool \bool_gset_false:N \g_lst_debug_bool %\bool_gset_true:N \g_lst_debug_bool % turn on debug mode

% a list of colors to be used \clist_new:N \g_lst_colors_clist \clist_set:Nn \g_lst_colors_clist {listing-background, listing-background-alternate}

\int_new:N \l_lst_tmpa_int \tl_new:N \l_lst_tmpa_tl \tl_new:N \l_lst_tmpb_tl \tl_new:N \l_lst_tmpc_tl \dim_new:N \l_lst_tmpa_dim \fp_new:N \l_lst_tmpa_fp \fp_new:N \l_lst_tmpb_fp \fp_new:N \l_lst_tmpc_fp

\cs_set:Npn __lst_anchor_dist:nnnnN #1#2#3#4#5 { \pgfpointdiff{\pgfpointanchor{#1}{#2}}% {\pgfpointanchor{#3}{#4}}% \edef#5{\fp_eval:n {sqrt(\pgf@x\pgf@x + \pgf@y\pgf@y)}} }

% setup the drawing function \cs_set:Npn \my@lst@underlay { \int_set:Nn \l_lst_tmpa_int {0}

% compute height of tcolorbox
% warning: does not work if the box is breakable!
\__lst_anchor_dist:nnnnN {interior}{north~west}{interior}{south~west}\l_lst_tmpa_tl;

% compute line width of tcolorbox
\__lst_anchor_dist:nnnnN {interior}{north~west}{interior}{north~east}\l_lst_tmpb_tl;

\fp_set:Nn \l_lst_tmpa_fp {\g_lst_top_dim}
\fp_set:Nn \l_lst_tmpb_fp {\l_lst_tmpa_tl - \g_lst_bottom_dim}
\dim_set:Nn \l_lst_tmpa_dim {\l_lst_tmpb_tl pt - \g_lst_right_dim - \g_lst_left_dim}

\fp_do_while:nNnn \l_lst_tmpa_fp &lt; \l_lst_tmpb_fp {
    % compute position of end of line
    \fp_set:Nn \l_lst_tmpc_fp {\l_lst_tmpa_fp + \g_lst_line_height_dim}
    \fp_compare:nNnT \l_lst_tmpc_fp &lt; \l_lst_tmpb_fp {
        % if we haven't reached end of box
        % acquire current color
        \edef\l_lst_tmpc_tl{\clist_item:Nn \g_lst_colors_clist {
                \int_mod:nn {\l_lst_tmpa_int}{\clist_count:N \g_lst_colors_clist} + 1
            }
        }

        \bool_if:NTF \g_lst_debug_bool {
            \draw[draw=black] 
            ($(interior.north~west)+(\dim_use:N \g_lst_left_dim, -\fp_use:N \l_lst_tmpa_fp pt)$) 
            rectangle ++(\dim_use:N \l_lst_tmpa_dim, -\dim_use:N \g_lst_line_height_dim);
        } {
            \draw[draw=none,fill=\l_lst_tmpc_tl]
            ($(interior.north~west)+(\dim_use:N \g_lst_left_dim, -\fp_use:N \l_lst_tmpa_fp pt)$) 
            rectangle ++(\dim_use:N \l_lst_tmpa_dim, -\dim_use:N \g_lst_line_height_dim);
        }

    }
    \fp_set_eq:NN \l_lst_tmpa_fp \l_lst_tmpc_fp
    \int_incr:N \l_lst_tmpa_int
}

}

\ExplSyntaxOff \makeatother

\begin{document}

\begin{tcblisting}{lststyle} const BlogTitle = ({ children }) => ( <h3>{children}</h3> ); // class component class BlogPost extends React.Component { renderTitle(title) { return <BlogTitle>{title}</BlogTitle> }; render() { return ( <div className="blog-body"> {this.renderTitle(this.props.title)} <p>{this.props.body}</p> </div> ); } } \end{tcblisting}

\end{document}

Alan Xiang
  • 5,227
  • Wow, that's definitely over my head, but thanks! A couple of issues though: the highlighting starts spot-on, perfectly centered, but after ~30/40 lines microscopic errors build up and the sync between code lines and the zebra is lost :-( Also, it doesn't seem to be able to page-break. – unDavide Mar 19 '21 at 11:48
  • Update: I've tweaked the line height to fit properly the font I'm using, the only issue at the moment is the missing page-break. Bear with my total lack of experience, but there would be a way to call it like \begin{react}[optionalParameters] passing optional parameters (like linenos=off, etc). Lastly, where can I find a comprehensive list of those? Thanks!! The bounty is yours :-) – unDavide Mar 19 '21 at 12:09
  • @unDavide Page breaking is a difficult problem. I can look into that in the next few days when I have time. Passing optional arguments is easier to implement. A comprehensive list of parameters can be found in minted's doc and tcolorbox's doc. – Alan Xiang Mar 19 '21 at 16:35
  • I've added breakable in the list of tcbuselibrary{} and breakable=true, in tcbset{} and that did the work! – unDavide Mar 19 '21 at 17:27
  • @unDavide It works on breakable boxes? I thought I need to spend a couple more hours on this. Anyways, enjoy TeXing! – Alan Xiang Mar 19 '21 at 21:09
  • I've tested a long chunk of code and it spreads in three different pages, so it's fine! I'm not sure I'm "enjoying" TeXing rather crawling through a massive amount of code I'm not sure I can understand fully, but as long as the job's done I'm a happy camper. Thanks again! – unDavide Mar 19 '21 at 22:12
  • Actually there's just one small issue: if the number of lines is even, the last line isn't background-colored (i.e. the listing ends with two white lines instead of white+gray. I tried to work out that myself but failed miserably, if you have time to fix that it would be awesome. Thanks! – unDavide Mar 20 '21 at 10:36
  • 1
    @unDavide Try changing \fp_compare:nNnT \l_lst_tmpc_fp < \l_lst_tmpb_fp to \fp_compare:nNnT \l_lst_tmpc_fp < {\l_lst_tmpb_fp + 2}, you can change the added number to modify the look. – Alan Xiang Mar 20 '21 at 17:04