[Tex/LaTex] Position tikz pictures at left margin of a listing code block

listingstikz-pgf

I'm trying to finish my attempt at having good looking unlimited line annotations for the listing environments, using tikz. The original problem was that indentation caused shifts in the positioning, so I changed the code to use the overlay feature.

My original question is here:
How to add numerical (ala No Starch Press) listing notes without XeTeX?

Unfortunately, I'm still unable to figure out how to properly align them:

Update: the code is merged with Werner's version.

\documentclass{article}
\usepackage{libertine}
\usepackage[log-declarations=false]{xparse}
\usepackage[quiet]{fontspec}
\setromanfont[Ligatures={Common,TeX}]{Linux  Libertine O}
\setmainfont[Ligatures={Common,TeX}]{Linux  Libertine O}
\setmonofont[SmallCapsFont={Latin Modern Mono Caps}]{Latin Modern Mono Light}
\setsansfont{Linux Biolinum O}
\usepackage{xunicode}
\usepackage[x11names, rgb]{xcolor}
\usepackage{tikz}
\usetikzlibrary{snakes,arrows,shapes}
\usepackage{amsmath}
\usepackage{listings}

\newcommand{\mynumold}[1]{{\oldstylenums{#1}}}

\newcounter{lstNoteCounter}

\newcommand*\lnnum[2][,]{\tikz[baseline=(char.base), overlay]{
            \node[shape=circle,draw,inner sep=0.8pt,
                    fill=black, text=white, #1] (char) {\rmfamily\bfseries\footnotesize#2};}}

\newcommand*{\lnote}{\stepcounter{lstNoteCounter}\llap{{\lnnum{\thelstNoteCounter}}\hskip 4em}}
\newcommand*{\linenum}[1]{%
  \setbox0=\hbox{\rmfamily\bfseries\footnotesize#1}%
  \lnnum[anchor=west]{#1}\hspace*{\dimexpr1ex+\wd0\relax}%
}
\lstnewenvironment{annotatedcsource}[1][]
{
    \setcounter{lstNoteCounter}{0}
        \lstset{
            basicstyle=\ttfamily,
            frame=lines,
            framexleftmargin=0.5em,
            framexrightmargin=0.5em,
            basicstyle=\ttfamily\footnotesize,
            numberstyle=\normalsize\itshape\mynumold,
            backgroundcolor=\color{LemonChiffon1},
            showstringspaces=false,
            numbers=left,
            numbersep=2.5em,
            escapeinside={(*@}{@*)},#1}
        }
{}

\begin{document}
\pagestyle{empty}

\begin{annotatedcsource}
void
rc4_init(struct rc4_state *const state, const u_char *key, int keylen)
{
    u_char j;
    int i, k;

    /* Initialize state with identity permutation */
    for (i = 0; i < 256; i++)
        (*@\lnote@*)state->perm[i] = (u_char)i; 
    state->index1 = 0;
    (*@\lnote@*)state->index2 = 0;

    /* Randomize the permutation using key data */
    for (j = i = k = 0; i < 256; i++) {
        j += state->perm[i] + key[k]; 
        (*@\lnote@*)swap_bytes(&state->perm[i], &state->perm[j]);
        if (++k >= keylen)
            (*@\lnote@*)k = 0;
    }
\end{annotatedcsource}

Some source code doing whatever \linenum{1}, with line numbers and annotations \linenum{2}.

\begin{annotatedcsource}
void hello(int times)
{
    int i;

    (*@\lnote@*)if (times > 4) {
        printf("Feeling chatty today?\n");
        return;
    }

    /* foobar */
    (*@\lnote@*)for (i = 0; i < times; i++) {
        printf("Hello %d!\n", i);
    }

    printf("Adios!\n");

    (*@\lnote@*)return;
}
\end{annotatedcsource}

Some source code doing whatever \linenum{1}, with line numbers and annotations \linenum{2}.

\end{document}

Output:
incorrectly aligned output makes soze a sad panda

Desired output:

  • Properly aligned circled numbers to the left or right margins along the line numbers.
    • In other words, the circled numbers must appear outside of the code block box, to the right or left of the line numbers.
  • Any kind of source code listing should work fine, no indentation assumptions (please).
  • Properly spaced circled numbers for the actual references embedded in the text.

Like this:

enter image description here

Best Answer

The following seems a bit of a hack, but provides the output you're after:

enter image description here

\documentclass{article}
%\usepackage{libertine}
%\usepackage[log-declarations=false]{xparse}
%\usepackage[quiet]{fontspec}
%\setromanfont[Ligatures={Common,TeX}]{Linux  Libertine O}
%\setmainfont[Ligatures={Common,TeX}]{Linux  Libertine O}
%\setmonofont[SmallCapsFont={Latin Modern Mono Caps}]{Latin Modern Mono Light}
%\setsansfont{Linux Biolinum O}
%\usepackage{xunicode}
\usepackage[x11names, rgb]{xcolor}
\usepackage{tikz}
%\usetikzlibrary{snakes,arrows,shapes}
\usepackage{amsmath}
\usepackage{listings}

%\newcommand{\mynumold}[1]{{\addfontfeature{Numbers=OldStyle}#1}}
\newcommand{\mynumold}[1]{{\oldstylenums{#1}}}

\newcounter{lstNoteCounter}

\newcommand*\lnnum[2][,]{\tikz[baseline=(char.base), overlay]{
            \node[shape=circle,draw,inner sep=0.8pt,
                    fill=black, text=white, #1] (char) {\rmfamily\bfseries\footnotesize#2};}}

\newcommand*{\lnote}{\stepcounter{lstNoteCounter}\llap{{\lnnum{\thelstNoteCounter}}\hskip 4em}}
\newcommand*{\linenum}[1]{%
  \setbox0=\hbox{\rmfamily\bfseries\footnotesize#1}%
  \lnnum[anchor=west]{#1}\hspace*{\dimexpr1ex+\wd0\relax}%
}
\lstnewenvironment{annotatedcsource}[1][]
{
    \setcounter{lstNoteCounter}{0}
        \lstset{
            basicstyle=\ttfamily,
            frame=lines,
            framexleftmargin=0.5em,
            framexrightmargin=0.5em,
            basicstyle=\ttfamily\footnotesize,
            numberstyle=\normalsize\itshape\mynumold,
            backgroundcolor=\color{LemonChiffon1},
            showstringspaces=false,
            numbers=left,
            numbersep=2.5em,
            escapeinside={(*@}{@*)},#1}
        }
{}

\begin{document}
\pagestyle{empty}

\begin{annotatedcsource}[caption={Foobar}]
void hello(int times)
{
    int i;

    (*@\lnote@*)if (times > 4) {
        printf("Feeling chatty today?\n");
        return;
    }

    /* foobar */
    (*@\lnote@*)for (i = 0; i < times; i++) {
        printf("Hello %d!\n", i);
    }

    printf("Adios!\n");

    (*@\lnote@*)return;
}
\end{annotatedcsource}

Some source code doing whatever \linenum{1}, with line numbers and annotations \linenum{2}.

\end{document}

numbersep=2.5em pushes the listing numbers 2.5em away from the listing edge. As such, this leaves room to push \lnote a length 4em from where it's called; into the gap. This is the hack I'm referring to: moving the TikZ node 4em (a fixed length), since one may require a length that depends on the indentation of the listing. \linenum{<num>} now sets \lnnum with anchor=west and pads it with some horizontal space to properly align any following text.


An alternative version that sets the TikZ node in a "less-hacky" way. :)

enter image description here

\documentclass{article}
%\usepackage{libertine}
%\usepackage[log-declarations=false]{xparse}
%\usepackage[quiet]{fontspec}
%\setromanfont[Ligatures={Common,TeX}]{Linux  Libertine O}
%\setmainfont[Ligatures={Common,TeX}]{Linux  Libertine O}
%\setmonofont[SmallCapsFont={Latin Modern Mono Caps}]{Latin Modern Mono Light}
%\setsansfont{Linux Biolinum O}
%\usepackage{xunicode}
\usepackage[x11names, rgb]{xcolor}
\usepackage{tikz}
%\usetikzlibrary{snakes,arrows,shapes}
\usepackage{amsmath}
\usepackage{listings}

\makeatletter
\let\insertnotenum\relax
\lst@Key{numbers}{none}{%
    \let\lst@PlaceNumber\@empty
    \lstKV@SwitchCases{#1}%
    {none&\\%
     left&\def\lst@PlaceNumber{\llap{\normalfont
                \lst@numberstyle{\thelstnumber}\kern\lst@numbersep\insertnotenum}}\\%
     right&\def\lst@PlaceNumber{\rlap{\normalfont
                \kern\linewidth \kern\lst@numbersep
                \lst@numberstyle{\thelstnumber}}}%
    }{\PackageError{Listings}{Numbers #1 unknown}\@ehc}}
\makeatother

%\newcommand{\mynumold}[1]{{\addfontfeature{Numbers=OldStyle}#1}}
\newcommand{\mynumold}[1]{{\oldstylenums{#1}}}

\newcounter{lstNoteCounter}

\newcommand*\lnnum[2][,]{\tikz[baseline=(char.base), overlay]{
            \node[shape=circle,draw,inner sep=0.8pt,
                    fill=black, text=white, #1] (char) {\normalfont\rmfamily\bfseries\footnotesize#2};}}
\makeatletter
\newcommand*{\lnote}{%
  \stepcounter{lstNoteCounter}%
  \global\def\insertnotenum{\lnnum[anchor=east,xshift=-1.5ex]{\thelstNoteCounter}%
  \global\let\insertnotenum\relax}%
}
\makeatother
\newcommand*{\linenum}[1]{%
  \setbox0=\hbox{\rmfamily\bfseries\footnotesize#1}%
  \lnnum[anchor=west]{#1}\hspace*{\dimexpr1ex+\wd0\relax}%
}
\lstnewenvironment{annotatedcsource}[1][]
{
    \setcounter{lstNoteCounter}{0}
        \lstset{
            basicstyle=\ttfamily,
            frame=lines,
            framexleftmargin=0.5em,
            framexrightmargin=0.5em,
            basicstyle=\ttfamily\footnotesize,
            numberstyle=\normalsize\itshape\mynumold,
            backgroundcolor=\color{LemonChiffon1},
            showstringspaces=false,
            numbers=left,
            numbersep=2.5em,
            escapeinside={(*@}{@*)},#1}
        }
{}

\begin{document}
\pagestyle{empty}

\begin{annotatedcsource}[caption={Foobar}]
void hello(int times)(*@\lnote@*)
{
    int i;
    (*@\lnote@*)
    if (times > 4) {
        printf("Feeling chatty today?\n");
        return;
    }

    (*@\lnote@*)/* foobar */
    for (i = 0; i < times; i++) {(*@\lnote@*)
        printf("Hello %d!\n", i);
    }

    printf("Adios!\n");
    (*@\lnote@*)
    return;
}
\end{annotatedcsource}

Some source code doing whatever \linenum{1}, with line numbers and annotations \linenum{2}.

\end{document}

The modifications include:

  1. A change in the way the numbers key-value is handled. The modification inserts \insertnotenum as part of the line number printing mechanism (or \lst@PlaceNumber);
  2. \insertnotenum is a self-destruct macro that removes itself after it is used;
  3. Use (*@\lnote@*) before the line that you want referenced, so that the line number of the following line is appropriately marked.

Note that this version now sets \lnote at the same horizontal distance, regardless of the code indentation.

Related Question