5
votes

I'm using LaTeX with the listings package, and I number every fifth line of the code by having:

\lstset{
numbers=left,
stepnumber=5,
firstnumber=1,
numberfirstline=true
}

Now because of the numberfirstline=true in these settings, apart from the multiples of five the first line of each listing is numbered too, but I would like to have the last line of the code be numbered as well. So if I have a listing with 17 lines of code, the lines I would like to see numbered are 1 5 10 15 17.

Is there some way that I can make listings behave like this? I tried numberlastline=true, but that does not seem to exist.

Edit: I would prefer to use this with \lstinputlisting, and without having to modify the code that get imported that way.

1
I haven't had the time to figure this out yet, but in case you're able to figure it out before me, here is my current logic. The problem with what you want is that line numbers are determined and output on each new line, and you don't know if there will be a next line until you finish the current one. Thus, you really need to know what the last line is before even reaching it, and this can only be done manually or by scanning the input an additional time before processing it.Hiko Haieto
My running idea is two steps: effectively add a lastlinenumber=n ability by doing something similar to the question I linked in my answer, and then to scan the input into a temporary file and count the lines to be able to get the last line number dynamically. It may also be possible to process the input without creating a temporary file while still being able to count lines, but I'm not immediately sure if and how that could be done.Hiko Haieto
Unfortunately this was around the time I had some upcoming business travel so I ran out of time to fix your problem earlier. I don't know if you are still facing this issue or not, but I have updated my answer to reflect your needs. It still requires manually specifying the last line for each listing, but uses already existing keys and makes enough sense. I gave up on the temporary file idea to dynamically and portably calculate a line count because manipulating low-level I/O like that was harder than I thought. If you want this, I suggested a possible non-portable solution in my answer.Hiko Haieto

1 Answers

4
votes

Actually, since you're using numberfirstline=true, this is simpler to accomplish than you might think. The listings package provides a mechanism to escape out of the verbatim mode in the middle of a listing via escapeinside to be able to insert additional macros. The trick here is that after returning from the escaped code, it considers the next line as the "first" line again, even though no counts are reset. Thus, all you have to do is escape out (and do nothing!) on the second to last line of the listing. If you do it on the very last, you're too late! The example below demonstrates using this to get what you desire.

\documentclass{article}
\usepackage{listings}

\lstset{
numbers=left,
stepnumber=5,
firstnumber=1,
numberfirstline=true
escapeinside={|(}{)|}
}

\begin{document}

\begin{lstlisting}
lorem ipsum dolor sit amet,
consectetur adipiscing elit,
sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua.
ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat.
duis aute irure dolor in reprehenderit
in voluptate velit esse cillum dolore
eu fugiat nulla pariatur.
excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt|()|
mollit anim id est laborum.
\end{lstlisting}

\end{document}

listing with number on last line

You should also check out this question about arbitrarily altering line numbering.

UPDATE

This requires modification to the listing source, and as such may be insufficient when using \lstinputlisting. An alternative approach is to modify the logic of the listings package itself to number the last line when it is explicitly specified via the lastline or linerange keys, as is demonstrated below.

\documentclass{article}
\usepackage{listings}

\lstset{
numbers=left,
stepnumber=5,
firstnumber=1,
numberfirstline=true,
}

\makeatletter

\gdef\lst@SkipOrPrintLabel{%
    \ifnum\lst@skipnumbers=\z@
        \global\advance\lst@skipnumbers-\lst@stepnumber\relax
        \lst@PlaceNumber
        \lst@numberfirstlinefalse
    \else
        \ifnum\lst@lineno=\lst@lastline
            \lst@PlaceNumber
            \lst@numberfirstlinefalse
        \fi
        \lst@ifnumberfirstline
            \lst@PlaceNumber
            \lst@numberfirstlinefalse
        \fi
    \fi
    \global\advance\lst@skipnumbers\@ne}

\makeatother

\begin{document}

\lstinputlisting[lastline=13]{input.tex}

\end{document}

This does require you to manually specify the last line number for each listing, but this seems like a reasonable sacrifice considering the difficulty of manipulating temporary files, which may potentially be the only real solution to dynamically discovering line count which only requires pure TeX. If a non-portable solution is acceptable, you could run a shell command such as wc -l via \write18 to get the number of lines. This would of course require the chosen command to be present on all systems the source is used on, which may cause difficulties if those systems span across different operating systems. See this question for an explanation of \write18 and this question for how to capture it's output.

Worth noting is that when using linerange, it will number the last line of each individual range, but not the first line for each range when using numberfirstline=true. While this can similarly be changed, linerange already results in enough such quirks that if this would be a problem, then it is most likely separate from the issue at hand here.