5

I am using tcolorbox to get nice code listings in a document. As my document is a training document, every line of the code is commented. Quite often, the code has to be broken over two pages, therefore I use the breakable options. This is sometimes leading to separations between the comment and the code.

Does anybody know a way to forbid such separations ?

EDIT n°2*

Added modified MWE to show the issue with multi-line comments.

\documentclass[10pt]{article}
\usepackage[margin=4cm]{geometry}
\usepackage{filecontents}
\usepackage{lipsum}

\usepackage{listings}
\usepackage[]{matlab-prettifier}
\usepackage{tcolorbox}
\tcbuselibrary{listings,xparse,breakable}

\newtcbinputlisting{\code}[2][]{%s
    listing options={style=Matlab-editor,tabsize=2,basicstyle=\footnotesize\ttfamily},
    listing inputencoding=utf8,
    listing file={#2},
    title=\texttt{#2},
    colback=blue!5!white,colframe=blue,fonttitle=\bfseries,
    before={\vspace{1mm}},
    top=-1pt,
    bottom=-1pt,
    listing only,breakable,#1}

\makeatletter
\newcounter{linehook}

\lst@AddToHook{EOL}{%
    \addtocounter{linehook}{1}%
    \csname linehook@hook@\number\value{linehook}\endcsname
}

\lst@AddToHook{AfterBeginComment}{%
    \immediate\write\@auxout{%
        \noexpand\expandafter\gdef
        \noexpand\csname linehook@hook@\number\value{linehook}\noexpand\endcsname
        {\noexpand\breakpoint}%
    }%
}

\newcommand\breakpoint{\vadjust{\hrule \penalty-2000 \vfill}}
\makeatother

\begin{filecontents*}{\jobname.txt}
% blahblah comment (three-line comment, should not be breakable)
% blahblah comment
% blahblah comment 
blahblah 
blahblah
blahblah
blahblah
% blahblah comment
blahblah
blahblah
blahblah
% blahblah comment
blahblah
% blahblah comment
blahblah 
blahblah
blahblah 
blahblah
% blahblah comment (two-line comment, should not be breakable)
% blahblah comment
blahblah 
blahblah 
blahblah
% blahblah comment
blahblah
blahblah 
blahblah
blahblah
% blahblah comment (two-line comment, should not be breakable)
% blahblah comment (but it is :))
blahblah 
blahblah 
blahblah
% blahblah comment
blahblah 
blahblah
% blahblah comment
blahblah 
blahblah 
blahblah 
blahblah
% blahblah comment
blahblah 
blahblah
\end{filecontents*}

\begin{document}
\lipsum[1]
\lipsum[1]
\code{\jobname.txt}
\end{document}

enter image description here

EDIT

More precisely, i would like to avoid comment--comment breaks (if a comment spans multiple lines) and comment--code breaks.

Here is an example. Sorry for all the blahblah.

\documentclass{article}
\usepackage{filecontents}
\usepackage{lipsum}

\usepackage{listings}
\usepackage[]{matlab-prettifier}
\usepackage{tcolorbox}
\tcbuselibrary{listings,xparse,breakable}

\newtcbinputlisting{\code}[2][]{%s
    listing options={style=Matlab-editor,tabsize=2,basicstyle=\footnotesize\ttfamily},
    listing inputencoding=utf8,
    listing file={#2},
    title=\texttt{#2},
    colback=blue!5!white,colframe=blue,fonttitle=\bfseries,
    before={\vspace{1mm}},
    top=-1pt,
    bottom=-1pt,
    listing only,breakable,#1}

\begin{filecontents*}{code.txt}
% blahblah 
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
% blahblah comment
blahblah 
\end{filecontents*}


\begin{document}
\lipsum[1]
\lipsum[1]
\code{code.txt}

\end{document}

enter image description here

5

New version (revised)

If we don't want to add possible breakpoints manually, we need a two-stage process, i.e. two compilation runs.

listings treats every source code line it processes as its own paragraph. We need to add the vertical material for the breakpoints after that paragraph has ended but before the new one has already begun. With the manual breakpoint insertion this works fine, because we can just put it at the right place such that it will be inserted after the next paragraph end when vertical mode is entered.

For an automatic insertion we have to know if the next line actually is a comment or not. But at the time we find out that information, it's already to late, a new paragraph had already been started.

What we do instead is to use the EOL hook to execute a unique macro after every input line using

\csname linehook@hook@\number\value{linehook}\endcsname

where linehook is a counter that gives us a unique value for every source code line that occurs in the document. A useful side effect of \csname for that matter is that TeX makes the command name equal to \relax if it isn't defined, so there's no error if any of these macros is undefined (on the first run all are undefined).

Additionally, we can use the AfterBeginComment hook to run some code whenever listings has found a comment in the code. This is the time we know that we need the line macro for the current value of linehook to actually add a breakpoint. We thus write the definition for that macro into the .aux file using a helper macro \@write@linehook. On the next compilation run it will be known at the end of the respective line and instead of being \relax'ed it inserts \breakpoint to the vertical list.

To make the breakpoint not go between consecutive comment lines, we define a new switch \ifcommentline which is reset to false at the beginning of every output line via the EveryPar hook. As soon as a comment has been found it is set to true. Before resetting this switch, we can check if the previous line has been a comment line. If so, we define \@write@linehook to just ignore it's argument, otherwise we actually make it write our unique line macro to the .aux file.

Here's the full updated example. Please note that I added \hrule to the \breakpoint definition to make the places visible at that the breakpoints were inserted in the output. For your real document you would remove this command.

\documentclass{article}
\usepackage{filecontents}
\usepackage{lipsum}

\usepackage{listings}
\usepackage[]{matlab-prettifier}
\usepackage{tcolorbox}
\tcbuselibrary{listings,xparse,breakable}

\newtcbinputlisting{\code}[2][]{%s
    listing options={style=Matlab-editor,tabsize=2,basicstyle=\footnotesize\ttfamily},
    listing inputencoding=utf8,
    listing file={#2},
    title=\texttt{#2},
    colback=blue!5!white,colframe=blue,fonttitle=\bfseries,
    before={\vspace{1mm}},
    top=-1pt,
    bottom=-1pt,
    listing only,breakable,#1}

\makeatletter
\newcounter{linehook}
\newif\ifcommentline

\lst@AddToHook{EOL}{%
    \addtocounter{linehook}{1}%
    \csname linehook@hook@\number\value{linehook}\endcsname
}

\lst@AddToHook{EveryPar}{%
    \ifcommentline
        \global\let\@write@linehook=\@gobble
    \else
        \gdef\@write@linehook{\immediate\write\@auxout}%
    \fi
    \global\commentlinefalse
}

\lst@AddToHook{AfterBeginComment}{%
    \global\commentlinetrue
    \@write@linehook{%
        \noexpand\expandafter\gdef
        \noexpand\csname linehook@hook@\number\value{linehook}\noexpand\endcsname
        {\noexpand\breakpoint}%
    }%
}

\newcommand\breakpoint{\vadjust{\hrule \penalty-2000 \vfill}}
\makeatother

\begin{filecontents*}{\jobname.txt}
% blahblah comment (three-line comment, should not be breakable)
% blahblah comment
% blahblah comment 
blahblah 
blahblah
blahblah
blahblah
% blahblah comment
blahblah
blahblah
blahblah
% blahblah comment
blahblah
% blahblah comment
blahblah 
blahblah
blahblah 
blahblah
% blahblah comment (two-line comment, should not be breakable)
% blahblah comment
blahblah 
blahblah 
blahblah
% blahblah comment
blahblah
blahblah 
blahblah
blahblah
% blahblah comment (two-line comment, should not be breakable)
% blahblah comment (but it is :))
blahblah 
blahblah 
blahblah
% blahblah comment
blahblah 
blahblah
% blahblah comment
blahblah 
blahblah 
blahblah 
blahblah
% blahblah comment
blahblah 
blahblah
\end{filecontents*}

\begin{document}
\lipsum[1]
\lipsum[1]
\code{\jobname.txt}
\end{document}

enter image description here enter image description here


Old version

One way to get around the problem is to define certain places in your code where box breaks are encouraged.

This can be done by escaping from the listings using the escapeinside option and defining material via \vadjust that will be inserted after the next end of line (a paragraph break internally). The inserted material here is a negative penalty to define a "good" breakpoint, followed by a \vfill that fills the remaining box up to the final target height.

The relevant code changes are

listing options={..., escapeinside={<@}{@>}},

and

\newcommand\breakpoint{\vadjust{\penalty-2000 \vfill}}

which then can be used as <@\breakpoint@> in each line that marks the end of your logical code "blocks".

Note that this might still give you unwanted breaks if your blocks become too long (I haven't tested that, though). In this case you can decrease the penalty even further for specific places to encourage TeX even more to break at that line, or add positive penalties for lines at which you want to discourage breaks. \penality -10000 will force a break; \penalty 10000 will never break at that line.

The full example code:

\documentclass{article}
\usepackage{filecontents}
\usepackage{lipsum}

\usepackage{listings}
\usepackage[]{matlab-prettifier}
\usepackage{tcolorbox}
\tcbuselibrary{listings,xparse,breakable}

\newtcbinputlisting{\code}[2][]{%s
    listing options={style=Matlab-editor,tabsize=2,basicstyle=\footnotesize\ttfamily,
        escapeinside={<@}{@>}},
    listing inputencoding=utf8,
    listing file={#2},
    title=\texttt{#2},
    colback=blue!5!white,colframe=blue,fonttitle=\bfseries,
    before={\vspace{1mm}},
    top=-1pt,
    bottom=-1pt,
    listing only,breakable,#1}

\newcommand\breakpoint{\vadjust{\penalty-2000 \vfill}}

\begin{filecontents*}{code.txt}
% blahblah comment
blahblah 
blahblah
blahblah
blahblah<@\breakpoint@> 
% blahblah comment
blahblah
blahblah
blahblah<@\breakpoint@> 
% blahblah comment
blahblah<@\breakpoint@> 
% blahblah comment
blahblah 
blahblah
blahblah 
blahblah<@\breakpoint@> 
% blahblah comment
blahblah 
blahblah 
blahblah<@\breakpoint@> 
% blahblah comment
blahblah 
blahblah<@\breakpoint@> 
% blahblah comment
blahblah 
blahblah 
blahblah<@\breakpoint@> 
% blahblah comment
blahblah 
blahblah<@\breakpoint@> 
% blahblah comment
blahblah 
blahblah 
blahblah 
blahblah<@\breakpoint@> 
% blahblah comment
blahblah 
blahblah<@\breakpoint@> 
\end{filecontents*}


\begin{document}
\lipsum[1]
\lipsum[1]
\code{code.txt}

\end{document}

enter image description here enter image description here

  • Thank you for your answer. Though your solution works fine, I was hoping for one that would not involve modifications in the original code, something less intrusive. – BambOo Jul 1 at 6:42
  • @BambOo Please see the updated version for automatic breakpoint insertion – siracusa Jul 5 at 8:02
  • Nice answer, there is just a slight glitch though. for multiline comments this solution fails and separates the contents. – BambOo Jul 5 at 12:57
  • Sorry for the delay, I added an examble for comment-comment breaks. In matlab, multi-line comments are simply done starting the line with % at each line – BambOo Jul 10 at 8:53
  • 1
    Just tested, what can I say, perfect and many thanks ! – BambOo Jul 10 at 13:26

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.