0

I'm using \llap in a custom environment to write some text in the document left margin, aligned with a code listing (code listing is done using lstlisting). Whenever the code listing is at the bottom of the page, the llap label stays on the previous page, whereas the code listing moves to the next page. An example is shown below (dark rectangles represent the document's margins):

enter image description here

The problem in this example being that the Out [9]: label is below the page's bottom margin, and the associated code listing is on the next page.

I need long code listings to break on multiple pages, so I do not want to wrap both the label and the listing inside a minipage (which would work, but not allow page breaks inside the code listing).

Is there any way to make llap respect the bottom margin? Below is an MWE.


\documentclass{article}

\usepackage{listings}
\usepackage[breakable]{tcolorbox}
\usepackage{ifthen}
\usepackage{xcolor}
\usepackage{lipsum}
\usepackage{showframe}

% custom colors
\definecolor{incolor}{HTML}{868686}
\definecolor{outcolor}{HTML}{868686}
\definecolor{cellbackground}{HTML}{F7F7F7}

% prompt
\makeatletter
\newcommand{\boxspacing}{\kern\kvtcb@left@rule\kern\kvtcb@boxsep}
\makeatother

\newcounter{cellcounter}

\lstdefinestyle{style}{
    language=Python, 
    aboveskip=-1.4\medskipamount,
    belowskip=0.5em,
    backgroundcolor=\color{cellbackground}, 
    numbers=none,
}

\makeatletter
\lst@Key{showprompt}{true}{\def\showprompt{#1}}
\lst@Key{promptnumber}{\thecellcounter}{\def\promptnumber{#1}}
\makeatother

\lstnewenvironment{code}[1][]
{   
    \lstset{
        style=style,
        #1
    }
    \addtocounter{cellcounter}{1}
    \ifthenelse{\boolean{\showprompt}}
        {\footnotesize\ttfamily\llap{{\color{outcolor}Out\,[\thecellcounter]:\hspace{3pt}\boxspacing}}}
        {\phantom{\footnotesize\ttfamily\llap{{\color{outcolor}Out\,[\thecellcounter]:\hspace{3pt}\boxspacing}}}}
}{
}

%----------------------------------------------------------------------------------------
%   DOCUMENT
%----------------------------------------------------------------------------------------

\setlength\parindent{0pt}
\setlength\parskip{83pt}
\begin{document}

\lipsum[70]
\lipsum[70]

\lipsum[70]
\lipsum[66]

\begin{code}
df.head()
\end{code}
\begin{code}
0         False       False  ...
1         False       False  ...
3         False       False  ...
4         False       False  ...
...         ...         ...  ...
9064      False       False  ...
9065      False       False  ...
9066      False       False  ...
9067      False       False  ...
\end{code}

\end{document}

enter image description here

9
  • you have shown no code, presumably you are hiding the depth of the Out [9] text but llap does not do that, Please include a small but complete example that shows the problem. Apr 27 '20 at 12:18
  • @DavidCarlisle Thanks, David, I've added an example.
    – hbot
    Apr 27 '20 at 12:56
  • Adding \hrule height0pt works, but will mess up the baseline spacing. Apr 27 '20 at 14:59
  • Two things that can be tried quickly, neither tested. (1) Before the first \begin{code} insert \goodbreak. (2) Between the two code blocks, insert \par\nobreak. Apr 27 '20 at 17:32
  • 1
    The memoir class (a superset of book, report and article) provides various methods of putting text into a margin: the standard \marginpar; \sidepar like marginpar but marginal texts may overlap; \sidebar, like \marginpar but starts at the top of the page and where the text is long will continue on the next page; \sidefootnotes where the notes are typeset in the margin instead at the bottom of the page. Perhaps one of these will help you. May 26 '20 at 18:10
2
+50

I think It will be tricky to get this to work using your approach of inserting the label explicitly in the second mandatory argument of \lstnewenviroment. This has nothing to do with \llap, but is due to the fact that this argument is intended for set-up of the environment rather than for inserting text. Page breaks are explicitly allowed before the listing really begins, which is after your label is inserted.

You could, however, achieve the same effect by putting your label in the title field. Since page breaks are not allowed after the title, your label will then always appear on the same page as the first line of content. I'm setting belowcaptionskip=-\baselineskip field to get the title on the same line as the content and put the label in a \makebox of width \linewidth because it would be centred otherwise.


Here is a modified version of your MWE. Changes are marked as either ADDED or REMOVED.

\documentclass{article}

\usepackage{listings}
\usepackage[breakable]{tcolorbox}
\usepackage{ifthen}
\usepackage{xcolor}
\usepackage{lipsum}
\usepackage{showframe}

% custom colors
\definecolor{incolor}{HTML}{868686}
\definecolor{outcolor}{HTML}{868686}
\definecolor{cellbackground}{HTML}{F7F7F7}

% prompt
\makeatletter
\newcommand{\boxspacing}{\kern\kvtcb@left@rule\kern\kvtcb@boxsep}
\makeatother

\newcounter{cellcounter}

%% Following commands are ADDED:
\newcommand*\hbotcodefont{\footnotesize\ttfamily}
\newcommand*\hbotoutlabeltext[1]{Out\,[#1]:\hspace{3pt}}
\newcommand*{\hbotOutTitle}{%
    \hbotcodefont
    \ifthenelse{\boolean{\showprompt}}{%
        \makebox[\linewidth][l]{\llap{%
            \color{outcolor}%
            \hbotoutlabeltext{\thecellcounter}\boxspacing
        }}%
    }{}%
}

\lstdefinestyle{style}{
    language=Python,
    title=\hbotOutTitle, %% <- ADDED
    belowcaptionskip=-\baselineskip, %% <- ADDED
    %% <- REMOVED aboveskip=...
    backgroundcolor=\color{cellbackground},
    numbers=none,
}

\makeatletter
\lst@Key{showprompt}{true}{\def\showprompt{#1}}
\lst@Key{promptnumber}{\thecellcounter}{\def\promptnumber{#1}}
\makeatother

\lstnewenvironment{code}[1][]
{
    \lstset{style=style,#1}
    \hbotcodefont
    \addtocounter{cellcounter}{1}
    %% <- REMOVED label insertion code
}{
}

%----------------------------------------------------------------------------------------
%   DOCUMENT
%----------------------------------------------------------------------------------------

\setlength\parindent{0pt}
\setlength\parskip{83pt}
\begin{document}

\lipsum[70]
\lipsum[70]

\lipsum[70]
\lipsum[66]

\begin{code}
df.head()
\end{code}
\begin{code}
0         False       False  ...
1         False       False  ...
3         False       False  ...
4         False       False  ...
...         ...         ...  ...
9064      False       False  ...
9065      False       False  ...
9066      False       False  ...
9067      False       False  ...
\end{code}

\end{document}

output

0

Following the answer of @David Carlisle in this post and as I said in comment, you can replace \llap with \makebox[0pt][r] in your environment definition.

Here is the code you proposed with two changes:

  • \llap replaced (two times) with \makebox[0pt][r]
  • removed \lipsum[xx] with several blabla\par to get more precision in pagebreak testing.

You can uncomment some of the blabla\par to get the effect described in comments:

I hope this will help you solving your problem.

This "answer" is just to show you the code so you can tell me if it works as you expect.

\documentclass{article}


\usepackage{listings}
\usepackage[breakable]{tcolorbox}
\usepackage{ifthen}
\usepackage{xcolor}
\usepackage{lipsum}
\usepackage{showframe}

% custom colors
\definecolor{incolor}{HTML}{868686}
\definecolor{outcolor}{HTML}{868686}
\definecolor{cellbackground}{HTML}{F7F7F7}

% prompt
\makeatletter
\newcommand{\boxspacing}{\kern\kvtcb@left@rule\kern\kvtcb@boxsep}
\makeatother

\newcounter{cellcounter}

\lstdefinestyle{style}{
    language=Python, 
    aboveskip=-1.4\medskipamount,
    belowskip=0.5em,
    backgroundcolor=\color{cellbackground}, 
    numbers=none,
}

\makeatletter
\lst@Key{showprompt}{true}{\def\showprompt{#1}}
\lst@Key{promptnumber}{\thecellcounter}{\def\promptnumber{#1}}
\makeatother

\lstnewenvironment{code}[1][]
{   
    \lstset{
        style=style,
        #1
    }
    \addtocounter{cellcounter}{1}
    \ifthenelse{\boolean{\showprompt}}
    {\footnotesize\ttfamily\makebox[0pt][r]{{\color{outcolor}Out\,[\thecellcounter]:\hspace{3pt}\boxspacing}}}
    {\phantom{\footnotesize\ttfamily\makebox[0pt][r]{{\color{outcolor}Out\,[\thecellcounter]:\hspace{3pt}\boxspacing}}}}
}{
}

%----------------------------------------------------------------------------------------
%   DOCUMENT
%----------------------------------------------------------------------------------------

\setlength\parindent{0pt}

\begin{document}

    \lipsum[1-4]
    blabla\par
    blabla\par  
    blabla\par
    blabla\par
    blabla\par  
    blabla\par
    blabla\par
    blabla\par  
    blabla\par      % "Out [1]" and the first two lines of "Out [2]" on page 1, the rest on page 2
%   blabla\par      % "Out [1]" on the penultimate line of page 1 (last line blank)
%   blabla\par      % Only "Out [1]" on last line of page 1
%   blabla\par      % Everything on page 2
    \begin{code}
        df.head()
    \end{code}
    \begin{code}
        0         False       False  ...
        1         False       False  ...
        3         False       False  ...
        4         False       False  ...
        ...         ...         ...  ...
        9064      False       False  ...
        9065      False       False  ...
        9066      False       False  ...
        9067      False       False  ...
    \end{code}
    \begin{code}
        blabla
    \end{code}

\end{document}
3
  • Thank you for your answer, Ivan. Unfortunately, the issue persists even with makebox. For example, if you comment the last line of blabla\par that is not commented in your answer (the one right above the three commented blablas), and add a \vspace{0.5em} before it, you will see the same issue (at least I do).
    – hbot
    May 27 '20 at 7:18
  • But why would you like to add a vspace here? :) Agreed, it doesn't work in that case. Should I remove my "answer" or would you prefer to keep it for the record?
    – Ivan
    May 27 '20 at 7:30
  • You are right, I don't need the vspace there, it was just an example. But in the document that I am working on, there are also several real cases where makebox does not fix the issue (the position of the code listing can be at the bottom of the page due to figures, tables or other elements on the page, not because of vspace). If you don't mind, please keep the answer as it may help others with similar issues.
    – hbot
    May 27 '20 at 7:37

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.