# Macros for code annotations

I'm wondering if there are some macros for annotating listings as below easily. They are primarily used in books from Manning.

Solution

For anyone who's interested, this is my macro I finally got:

    \newcommand*{\AddNote}[5]{%
\begin{tikzpicture}[overlay, remember picture]
\coordinate (x) at (#2,0);
\coordinate (a) at ($(x)!(#1.north)!($(x)+(0,1)$)$);
\coordinate (b) at ($(a)+(0.8,0)$);
\coordinate (c) at ($(b)+(0,#3)$);
\draw [open triangle 45-] (a) -- (b) -- (c);
\node[#5] at (c) {\bf\sffamily\smaller#4};
\end{tikzpicture}%
}


Usage is: \AddNote{linemarker}{x-offset}{y-offset}{text}{style}

• If the linked to question does not help you get the desired result, I would recommend you compose a fully compilable MWE including \documentclass and the appropriate packages that sets up the problem. While solving problems is fun, setting them up is not. Then those trying to help can simply cut and paste your MWE and get started on solving problem. – Peter Grill Dec 10 '12 at 4:37
• The shift to the right is because your macro introduces two spaces: one before \begin{tikzpicture} and one after \end{tikzpicture}. Put percent signs after the opening brace of the \newcommand and after \end{tikzpicture} and that should stop things shifting. – Loop Space Dec 10 '12 at 22:16
• (Oh, I see you asked a new question about that - ignore my above comment.) – Loop Space Dec 10 '12 at 22:20

Edit 2018-05-13: This is now in the tikzmark package which is available from all good TeX distributions. See the tikzmark documentation for up to date instructions.

Without a MWE it's hard to know exactly what sort of thing you're after but I think that the listings extension to tikzmark can handle this sort of thing. You need tikzmark.dtx from TeX-SX Launchpad, run tex tikzmark.dtx and put the generated files somewhere that tex can find them (if you run latex tikzmark.dtx or pdflatex tikzmark.dtx it will complain about a missing file - ignore that).

Then the following code:

\documentclass{article}
%\url{http://tex.stackexchange.com/q/86309/86}
\usepackage{listings}
\usepackage{tikz}
\usetikzmarkextra{listings}

\tikzset{
comment/.style={
draw,
fill=blue!70,
text=white,
rounded corners,
align=left,
},
}

\begin{document}

\begin{lstlisting}[language=TeX,name=texcode,numbers=left,breakatwhitespace=true,breaklines=true]
\newcommand\balloon[4]{%
\pgfmathtruncatemacro\firstline{%
#3-1
}%
\iftikzmark{line-#2-\firstline-start}{%
\iftikzmark{line-#2-#3-first}{%
\xdef\blines{({pic cs:line-#2-\firstline-start} -| {pic           cs:line-#2-#3-first})}%
}{%
\iftikzmark{line-#2-#3-start}{%
\xdef\blines{({pic cs:line-#2-\firstline-start} -| {pic             cs:line-#2-#3-start})}%
}{%
\xdef\blines{(pic cs:line-#2-\firstline-start)}%
}%
}%
}{%
\xdef\blines{}%
}%
\foreach \k in {#3,...,#4} {%
\iftikzmark{line-#2-\k-first}{%
\xdef\blines{\blines (pic cs:line-#2-\k-first) }
}{}
\iftikzmark{line-#2-\k-end}{%
\xdef\blines{\blines (pic cs:line-#2-\k-end) }
}{}
}%
\ifx\blines\empty
\else
\edef\temp{\noexpand\tikz[remember picture,overlay] \noexpand\node[fit={\blines},balloon] (#1) {};}%
\temp
\fi
}
\end{lstlisting}

\begin{tikzpicture}[remember picture,overlay,>=stealth']
\draw[<-,ultra thick] (pic cs:line-texcode-1-end) +(1em,.7ex) -| +(2.5,1) node[above,comment,thin] {Command name};
\draw[<-,ultra thick] (pic cs:line-texcode-3-end) ++(1em,.7ex) -| +(5.8,1) node[above right,comment,thin] {Find previous line};
\draw[<-,ultra thick] (pic cs:line-texcode-5-end) ++(1em,.7ex) -| +(2.2,.5) node[above,comment,thin] {If previous line exists, add to the list};
\draw[<-,ultra thick] (pic cs:line-texcode-18-end) ++(1em,.7ex) -| +(2.2,.5) node[above,comment,thin] {Loop through rest of lines};
\draw[<-,ultra thick] (pic cs:line-texcode-28-end) ++(1em,.7ex) -| +(1,1.5) node[above,comment,thin] {Add a node covering all the lines};
\end{tikzpicture}
\end{document}


produces:

(Only it looks much nicer in the PDF. And note that there's been an eminent suggestion to replace \usetikzmarkextra with \usetikzmarklibrary which will happen sometime soon so if the above complains about \usetikzmarkextra try switching it.)

• Hi Andrew, thanks for your answer. I already managed to make a macro that fits my needs (although its not perfect ;)). Nevertheless I still have problems when using \onehalfspacing. I think I'll start a new question with that... – Matthias Dec 10 '12 at 23:17
• Note that drawing the comments after the listing has been set is too late if the listing spans more than one page and some of the lines to be commented are not on the last page. – cyberSingularity Dec 12 '12 at 21:57
• @cyberSingularity True. There are (at least) two ways to solve that. The first is to hook in to some part of the page that is always there (for example, the header) and add code that says "If there's a mark, draw its diagram". The other is to use the same hooks as I do in this code but add the actual drawing instructions there (this might be more difficult - I had issues with adding code to those hooks). – Loop Space Dec 12 '12 at 22:29
• @cyberSingularity I freely admit that the code underlying the above was developed using the "Let's just try everything and see what works" method. So if anyone should be embarrassed about their ugly code, it's me. – Loop Space Dec 12 '12 at 22:55
• Yes, using \usetikzmarklibrary is better. Great answer! – Andry Oct 8 '17 at 12:57

I promised a not-necessarily-pretty solution to extend Andrew Stacey's excellent answer to support cases where the code spans over more than one page:

\documentclass{article}
\usepackage{listings}
\usepackage{tikz}
\usetikzmarklibrary{listings}

\tikzset{
comment/.style={
draw,
fill=blue!70,
text=white,
rounded corners,
align=left,
},
}

\usepackage{eso-pic}
\usepackage[calc]{picture}
\put(0,\paperheight){\vtop{{\null}\makebox[0pt][l]{#1}}}%
}%
}%
\put(0,\paperheight){\vtop{{\null}\makebox[0pt][l]{#1}}}%
}%
}%

\setcounter{errorcontextlines}{\maxdimen}

\makeatletter
\def\getpicturepage#1{%
\@nameuse{save@pg@\@nameuse{save@pt@#1}}%
}

\newcommand*{\iftikzmarkcurrentpage}[3]{%
\iftikzmark{#1}{%
\ifcsname save@pg@\@nameuse{save@pt@#1}\endcsname
\expandafter\ifnum\getpicturepage{#1}=\the\c@page\relax
#2%
\else
#3%
\fi
\else
#3%
\fi
}{%
#3
}%
}

\newcommand*{\appendhookorcreatenew}[2]{%#1<- hook csname, #2<- code to add
% note: code in #2 is not expanded at this stage!
\ifcsname #1\endcsname
\else
\@namedef{#1}{\unexpanded{#2}}%
\fi
}
\newcommand*{\deferlinecode}[3]{% #1<-listing name, #2<-line number, #3<-code
\appendhookorcreatenew{listings-deferline-#1-#2}{#3}%
}
\newcommand*{\atEOLcode}[3]{% #1<-listing name, #2<-line number, #3<-code
\appendhookorcreatenew{listings-eol-execute-#1-#2}{#3}%
}
\newcommand*{\commenton}[4][comment,thin]{% #1<- node keys, #2<- listing name, #3<- line number, #4<- comment text
\deferlinecode{#2}{#3}{\tikz[overlay,remember picture] \draw[,>=stealth',<-,ultra thick] (pic cs:line-#2-#3-end) +(1em,.7ex) -| ($(node cs:name=current page,anchor=north east)!(pic cs:line-#2-#3-end)!(node cs:name=current page,anchor=south east) +(-5cm,0cm)$) node[#1] {#4};}%
}

\begingroup
\ifcsname listings-deferline-\lst@name-\the\c@lstnumber\endcsname
\iftikzmarkcurrentpage{line-\lst@name-\the\c@lstnumber-end}{%
}{%
}%
\edef\pagehookstuff{%
\noexpand\iftikzmarkcurrentpage{line-\lst@name-\the\c@lstnumber-end}{%
\@nameuse{listings-deferline-\lst@name-\the\c@lstnumber}%
}{}%
}%
}%
\global\expandafter\let\csname listings-deferline-\lst@name-\the\c@lstnumber\endcsname\@undefined
\pagehookstuff
\fi
\ifcsname listings-eol-execute-\lst@name-\the\c@lstnumber\endcsname
\@nameuse{listings-eol-execute-\lst@name-\the\c@lstnumber}%
\fi
\endgroup
}

\commenton[comment,thin,text width=5cm]{texcode}{3}{This line sets the title of the document but does not result in any visible output, yet.}
\atEOLcode{texcode}{3}{\vspace{5ex}}
\commenton[comment,thin]{texcode}{4}{Similarly for author.}
\atEOLcode{texcode}{4}{\vspace{2ex}}
\commenton[comment,thin]{texcode}{5}{And date.}
\commenton[comment,thin,fill=red!70,text=black]{texcode}{7}{This line actually shows them.}

\lstset{
language={[LaTeX]TeX},
numbers=left,
breaklines=true,
basicstyle=\small\ttfamily,
columns=flexible,
}

\usepackage[a5paper,landscape]{geometry}
\usepackage{url}

\begin{document}

\title{Example output of code annotation}
\author{\url{http://tex.stackexchange.com/questions/86309/macros-for-code-annotations}}
\date{16 December 2012}
\maketitle

This is only to illustrate how one might go about adding annotations to code that spans across more than one page.

The annotations to be added have to specified before the listing appears.
It is not very robust to lost notes, eg if attempting to add an annotation to a blank line where there may not be an end marker.

\begin{lstlisting}[name=texcode]
\documentclass{article}
\usepackage{listings}
\title{Sample Document}
\author{John Smith}
\date{\today}
\begin{document}
\maketitle
Hello World!
% This is a comment.
\end{document}
\end{lstlisting}

\end{document}


(Example listing borrowed from elsewhere.)

If adjacent comment nodes might overlap, I suggested just adding some extra space below commented lines. It is probably evident that I struggle a lot with positioning in TikZ.