Macros for code annotations

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}
\usetikzlibrary{arrows,tikzmark,shadows}
\usetikzmarkextra{listings}

\tikzset{
  comment/.style={
    draw,
    fill=blue!70,
    text=white,
    rounded corners,
    drop shadow,
    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:

annotated listings

(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.)


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}
\usetikzlibrary{arrows,tikzmark,shadows,calc}
\usetikzmarklibrary{listings}

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

\usepackage{eso-pic}
\usepackage[calc]{picture}
\newcommand{\addstufftoforegroundall}[1]{%
  \AddToShipoutPictureFG{% Add <stuff> to all following pages' foreground
    \put(0,\paperheight){\vtop{{\null}\makebox[0pt][l]{#1}}}%
  }%
}%
\newcommand{\addstufftoforegroundthis}[1]{%
  \AddToShipoutPictureFG*{% Add <stuff> to the current page foreground
    \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
        \expandafter\g@addto@macro\csname #1\endcsname{\unexpanded{#2}}%
    \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};}%
}

\lst@AddToHook{EOL}{%
    \begingroup
    \ifcsname listings-deferline-\lst@name-\the\c@lstnumber\endcsname
        \iftikzmarkcurrentpage{line-\lst@name-\the\c@lstnumber-end}{%
            \let\addstufftoforeground\addstufftoforegroundthis
        }{%
            \let\addstufftoforeground\addstufftoforegroundall
        }%
        \edef\pagehookstuff{%
            \noexpand\addstufftoforeground{%
                \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.

enter image description here