Lstinline in mathmode with correct font size

listingsluatexmath-mode

I often typeset mathematical formulas about computer programs (loop invariants, etc.).

I typeset them in typewriter font, saying in my preamble \lstset{basicstyle=\ttfamily}.

I could say

$\frac{\text{\lstinline£x£}}2$

but this is cumbersome. I want to type

$\frac{\lstinline£x£}2$

So, in a fashion similar to that answer, I created a math-mode version of \lstinline and I use \ifmmode to select the appropriate variant (see MWE at the end) :

\newcommand\lstinlinemm[1][]{%
   \hbox\bgroup % the \hbox is new
      \def\lst@boxpos{b}%
      \lsthk@PreSet\lstset{flexiblecolumns,#1}%
      \lsthk@TextStyle
      \lstinline@}

However, this does not appropriately scale the font size when typesetting eg. a fraction.

Replacing \hbox with amstext's \text@ (which is the mathmode version of \text) leads the code to be typeset in mathmode instead of textmode, and the reason for that is unclear to me. This is a problem because it gives rise to an error pertaining to \ttfamily not being available in math mode.

An interesting answer told me that using \mathchoice is more complicated than I thought, because each size version is typeset, due to TeX not knowing beforehand if the regular or scriptsize variant will be necessary.

So that would imply moving the verbatim code snippet around, which I understand is not a trivial thing.

It seems that Lua can interact with LuaTeX's input processing. This answer cites the process_input_buffer callback to store verbatim text to a lua variable and then use it. However, I understand it works line by line, so is not applicable to my case, as I of course do not want to break line in my tex source within a formula.

Paul Isambert's article quoted in the answer also gives an example using the token_filter callback. I think this might be used as well to gobble the code snippet to a lua variable, then feed it to \text. However, token_filter no longer exists since 2016.

A MWE follows :

\documentclass{standalone}
\usepackage{amsmath,listings}

\lstset{basicstyle=\ttfamily}

\makeatletter
\newcommand\lstinlinemm[1][]{%
   \hbox\bgroup
      \def\lst@boxpos{b}%
      \lsthk@PreSet\lstset{flexiblecolumns,#1}%
      \lsthk@TextStyle
      \lstinline@}
% On renomme le lstinline d'origine
\let\lstinlinetm\lstinline %%
% On met en place la sélection de la bonne version selon le mode actif
\DeclareRobustCommand\lstinline{\ifmmode\let\nextlstinline\lstinlinemm\else\let\nextlstinline\lstinlinetm\fi\nextlstinline}

\newcommand\£{\lstinline£} %£

\begin{document}
$\frac{\£x£}2$ $\frac{\text{\£x£}}2$
\end{document}

Best Answer

First, note that the "verbatim" inside command argument usually does not "actually" work even if it sometimes seem to.

Quoting from the documentation

5.1 Listings inside arguments

There are some things to consider if you want to use \lstinline or the listing environment inside arguments. Since TeX reads the argument before the ‘lst- macro’ is executed, this package can’t do anything to preserve the input: spaces shrink to one space, the tabulator and the end of line are converted to spaces, TeX’s comment character is not printable, and so on. Hence, you must work a bit more. You have to put a backslash in front of each of the following four characters: \{}%. Moreover you must protect spaces in the same manner if: (i) there are two or more spaces following each other or (ii) the space is the first character in the line. That’s not enough: Each line must be terminated with a ‘line feed’ ^^J. And you can’t escape to LaTeX inside such listings!

Second, if you know how TeX internal works, you would have figured out that in order for text size to be correct in math mode at the lowest level it's required to execute the content 4 times for each math mode.

Either way, in this case you need some deep patches of lstinline code.

The lstinline code is quite complicated, in theory the best way to patch this is to

  • first set the catcode,
  • then collect the arguments,
  • then do the setup things (e.g. including \ttfamily in this case)
  • then typeset everything.

I haven't yet to figure out how the code inside \lst@Init works exactly to separate the set-catcode part and the font-init etc. part so I just do this.

output

%! TEX program = lualatex
\documentclass{article}
\usepackage{amsmath}
\usepackage{listings}
\begin{document}

\makeatletter

\def\lstinline@#1{%
    %\lst@Init\relax  % originally this was included, we move it to later
    \lst@IfNextCharActive{\lst@InlineM#1}{\lst@InlineJ#1}% original definition, we don't handle M case
    %\lst@InlineJ#1%
}

\def\lst@InlineJ#1{%
    \def\lst@temp##1#1{%
        \text{%  ← this is added around
            \lst@Init\relax  % the init is moved to here
            \let\lst@arg\@empty \lst@InsideConvert{##1}%
            \lst@arg
            \lst@DeInit
        }%  ← we end up doing the init 4 times
        \egroup}%
    \lst@temp}

\makeatother

\lstset{basicstyle=\ttfamily}

Issue: this will disable the verbatim capturing mode e.g.
\lstinline+123    4 56+ does not preserve the double-space.

But otherwise it works.


\[
    \lstinline+123+\frac{\lstinline+123+^{\lstinline+123+_{\lstinline+123+}}}{2}
\]

\end{document}
Related Question