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