8

Suppose I want to typeset a screen session in some programming language (GAP in my case, but it could be Python, BASH, or something else with an interactive read prompt just as well). So on my screen, I might have something like this:

gap> for i in [1..10] do
>   Print(i, ":");
> od;
1:2:3:4:5:6:7:8:9:10:
gap> 2^10;
1024
gap> 1/0;
Error, Rational operations: <divisor> must not be zero
not in any function at line 5 of *stdin*
you can replace <divisor> via 'return <divisor>;'
brk> 2^10;
1024
brk>

That is, there is a command prompt gap>, after which the user enters a multi line input. After that comes a line of output, then another prompt gap>. This repeats, until a command triggers an error, at which point the prompt changes to brk>.

I would like to use the listings package (or something else) to

  1. color the prompts gap>, >, and brk>; say, blue and red;
  2. highlight keywords like for, do, and od; say, by making them bold;
  3. but only highlight the keywords in lines starting with a prompt, hence not inside the output of my commands.

The last point is the one that is causing me troubles. I couldn't figure out a way to do that. The result is often quite ugly when words like and, not, and in are highlighted as keywords in, e.g., an error message.

Similar questions have been asked before, but of those I managed to find, none asked for exactly this, resp. got an answer that does what I want.

For the record, here is the language definition I am using for now:

\lstdefinelanguage{GAP}{%
    morekeywords=[2]{and,break,continue,do,elif,else,end,fail,false,fi,for,%
        function,if,in,local,mod,not,od,or,rec,repeat,return,then,true,%
        until,while},%
    moredelim=[s][\color{blue}]{gap}{>},%
    moredelim=[s][\color{red}]{brk}{>},%
    %moredelim=*[l][\color{blue}]{gap>},%
    %moredelim=*[l][\color{red}]{brk>},%
    sensitive=true,%
    morecomment=[l]\#,%
    morestring=[b]',%
    morestring=[b]",%
    }%

PS: To illustrate what I want, consider this image showing how my example from above could look (in addition to what I described above, I also set all user inputs in italics):

enter image description here

  • This is a duplicate of my question tex.stackexchange.com/questions/73893/… – Federico Poloni Apr 18 '13 at 16:24
  • 1
    Hm, the answer to your question proposes to use mordelim as a solution -- if you look, I am already using it. But I don't see how that can overcome point 3 of my requirements, and the example picture in the solution to your question seems to indicate that it does not satisfy point 3, as "ans" in one of the output lines is printed in bold. But I need the keyword formatting to be only applied to inputs, not outputs. – Max Apr 18 '13 at 22:02
  • Hm, you are right, sorry for the confusion. – Federico Poloni Apr 19 '13 at 7:00
  • Your specs are somewhat contradictory. For instance, in the text, you require the keywords to be bold, but they appear medium and green in your screenshot. Also, whether you actually want the user input to be slanted is unclear. Could you clarify, maybe by listing all your specs in one place? – jubobs Mar 1 '14 at 20:06
  • Yes, the spec says "keywords = bold" and the example instead makes them green -- either is fine, based on the assumption that once I know how to do it, I could change it suitably :-). The reason the "spec" and the screenshot differ comes from them being created at different times. – Max Mar 2 '14 at 14:42
2

This listings answer is about a year late, but I believe it covers all your requirements.

Edit: fixed a bug related to prompt "keywords" occurring in the middle of a line.

Desired output

desired output

Output obtained

output obtained

Code

\documentclass[a4paper]{article}
\usepackage{xcolor}
\usepackage{textcomp}
\usepackage{listings}

\makeatletter

% switch to remember whether a prompt was found on the current line
\newif\ifprompt@GAP@
% switch to flag whether a prompt `keyword' is an bona fide GAP prompt
\newif\iftoolateforprompt@GAP@

% style of GAP keywords
\definecolor{GAPkeywords}{RGB}{077,145,021}
\newcommand\GAPkeywordstyle{\color{GAPkeywords}}

% font shape of GAP input lines 
\newcommand\GAPinputfshape\slshape

\lstdefinelanguage{GAP}
{%
  basicstyle =\ttfamily,
  alsoletter=>,
  morekeywords=[2]{%
    and,break,continue,do,elif,else,end,fail,false,fi,for,function,if,in,%
    local,mod,not,od,or,rec,repeat,return,then,true,until,while,
  },%
  keywordstyle=[2]\Process@GAP@keywords,
  morekeywords=[3]{gap>,>},
  keywordstyle=[3]\Process@GAP@prompt{blue},
  morekeywords=[4]{brk>},
  keywordstyle=[4]\Process@GAP@prompt{red},
  sensitive=true,%
  upquote=true,% <--- for straight single quotes
  showstringspaces=false,
  morecomment=[l]\#,
  morestring=[b]',
  morestring=[b]",
}%

% only highlight keywords if a prompt has occured on the current line
\newcommand\Process@GAP@keywords{\ifprompt@GAP@\GAPkeywordstyle\fi}

\newcommand\Process@GAP@prompt[1]
{%
  \iftoolateforprompt@GAP@%
  \else%
      \color{#1}\upshape% customise the style of your > and gap> prompts here
      \global\prompt@GAP@true%
      \aftergroup\GAPinputfshape% we trigger slanted shape after > or gap>
  \fi%
}

% Hook into InitVarsEOL (some hook executed right after an EOL) to:
% - reset the \ifprompt@GAP@ and \iftoolateforprompt@GAP@ switches
% - reset the font shape to \upshape
\lst@AddToHook{InitVarsEOL}%
{%
  \global\prompt@GAP@false%
  \global\toolateforprompt@GAP@false%
  \upshape%
}

% Hook into PostOutput to signal that a GAP prompt
% can no occur on the current line
\lst@AddToHook{PostOutput}{\global\toolateforprompt@GAP@true}

\makeatother

\begin{document}
\begin{lstlisting}[language=GAP]
gap> for i in [1..10] do
>   Print(i, ":");
> od;
1:2:3:4:5:6:7:8:9:10:
gap> 2^10;
1024
gap> 1/0;
Error, Rational operations: <divisor> must not be zero
not in any function at line 5 of *stdin*
you can replace <divisor> via 'return <divisor>;'
brk> 2^10;
1024
brk>
\end{lstlisting}
\end{document}
  • Pretty cool! I'll give it a closer look later. In the meantime, one its tiny issue I noticed: If there are extra whitespace between prompt and commands then the highlighting does not work (I tried to add an example, but found to way to make multiple spaces in a row visible in a comment). Anyway, hopefully that would be trivial to change? – Max Mar 2 '14 at 16:36
  • @Max See my edit. I'm not sure why, but it seems that using the InitVarsEOL hook (instead of the EveryLine hook) solves problem. – jubobs Mar 2 '14 at 16:58
  • Great, that works better. But there is another odd thing: If there is output which contains something that looks like a string, say "bla", then a subsequent > is treated as if it started a prompt. Likewise, a single " or ' throws the code off. So, I wonder, can one get it to not ignore string delimiters in output text? Just like you made it ignore (or rather: not color) keywords there? Seems harder to achieve... – Max Mar 2 '14 at 18:51
  • @Max See my edit regarding the first problem you report; I think I fixed it. I'll look into the second problem (single " or '), but it looks more complicated indeed. – jubobs Mar 2 '14 at 19:12
  • @Max Unfortunately, I can't think of an easy way to prevent " and ' from starting a string on your "output lines". Perhaps I'll think of something when I'm more familiar with listings... in a couple of years! – jubobs Mar 2 '14 at 19:19
1

The one drawback is that I process whole words, not subwords. Thus, od; is a separate word from od. That can be overcome, but not in this MWE. I copied your raw (unformatted) session output into the file session.in and went from there.

\documentclass[12pt]{article}
\makeatletter%
\let\protectededef\protected@edef
\makeatother%

\parindent 0in
\renewcommand{\encodingdefault}{T1}
\usepackage{color}
\usepackage{readarray}
\usepackage{verbatimbox}
\usepackage{ifthen}
\catcode`^=12
\definecolor{darkgreen}{rgb}{0,0.5,0}
\newcounter{rowindex}\newcounter{wordindex}%

\newcommand\displaysource[1]{%
  \sffamily%
  \readdef{#1}{\x}%
  \setcounter{rowindex}{0}%
  \whiledo{\value{rowindex} < \nrecords}{%
    \addtocounter{rowindex}{1}%
    \getargsC{\csname record\roman{rowindex}\endcsname}%
    \ifthenelse{\equal{\argi}{>}  \OR%
                \equal{\argi}{gap>}  \OR%
                \equal{\argi}{brk>}}%
                {\def\userin{\itshape}}{\def\userin{\upshape}}%
    \setcounter{wordindex}{0}%
    \whiledo{\value{wordindex} < \narg}{%
      \addtocounter{wordindex}{1}%
      \protectededef\thisword{\csname arg\roman{wordindex}\endcsname}%
      \ifthenelse{\equal{\thisword}{gap>}  \OR%
                  \equal{\thisword}{>}}%
        {%
          \upshape\textcolor{blue}{\thisword~}%
        }{%
          \ifthenelse{\equal{\thisword}{brk>}}%
            {%
              \textcolor{red}{brk>~}%
            }{%
              \userin%
              \ifthenelse{\equal{\thisword}{for}  \OR%
                          \equal{\thisword}{in}  \OR%
                          \equal{\thisword}{do}  \OR%
                          \equal{\thisword}{od}  \OR%
                          \equal{\thisword}{od;}}%
              {%
                \ifthenelse{\equal{\userin}{\itshape}}%
                  {%
                    \textcolor{darkgreen}{\thisword~}%
                  }{%
                    \thisword~%
                  }%
              }{%
                \thisword~%
              }
            }%
        }%
    }%
    \\%
  }%
  \rmfamily%
}
\begin{document}

\displaysource{session.in}

\end{document}

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.