## Monday, May 24, 2010

### LaTeX: Listings and labels

I have blogged about the listing package here several times (nicely formatting, and hyphenations). This time I will write about creating labels inside a listing. First of all, the listing package allows to define a caption and a label for a listing, e.g.: \begin{lstlisting}[caption={myListing},label={lst:myListing}] void foo(int x) { doSomething(); doMore(); } \end{lstlisting}  Now the listing can be referenced via \ref{lst:myListing}. The listing package also nicely supports line numbers, there are a whole bunch of settings for that. But why do we need line numbers? In most cases, line numbers are used to refer to a certain line within a listing. E.g, we maybe want to write something like
In line 2 of the listing 1 we call method 'doSomething'.
Well, "listing 1" will look like "listing \ref{lst:myListing}" in the LaTeX source code, but how do we reference the line? Fortunately, the listing packages allows us to escape to latex inside a listing and add a label which can then be referenced: \begin{lstlisting}[caption={myListing},label={lst:myListing},numbers=left,escapeinside={@}{@}] void foo(int x) { @\label{lst:myListing_2}@doSomething(); doMore(); } \end{lstlisting}  Now we can reference the line: In line \ref{lst:myListing_2} ... Actually, you can use any label text, however I usually use the listings label with the line number or a small marker, in order to avoid conflicts with duplicate labels. As I do not want to write "line..." and "listing ..." over and over again, I use the autoref command from the hyperref package: In \autoref{lst:myListing_2} of \autoref{lst:myListing}  autoref automatically adds a name to the reference counter. As there is no name defined for line numbers, it has to be defined previously: \providecommand*{\lstnumberautorefname}{line}  If you need names in a another language, you can add a translation: \addto\extrasngerman{% \def\lstlistingautorefname{Quellcode}% \def\lstnumberautorefname{Zeile}% }  OK, we can add a line label and easily create a reference to that label. However, writing line labels means a lot of work, especially if you use a listing relative name pattern. So, let's add a macro to TeXShop, which uses some shell commands (mainly sed) and AppleScript in order to automatically create the label, even adding the line number to the label:
--Applescript direct -- -- Create a label inside a listing, must be invoked at the position at which the label is -- to be created. -- Precondition: the listing is defined inside a lstlisting environment and -- a label is defined in the parameter list of the environment (label={..}). -- -- New newly created label consists of the label of the listing with the line number -- appended to the label. You can configure escape characters (as specified in -- escapeinside={..}{..}) and the separator between listing label and line number. -- -- E.g. label={myListing}, on line 5 leads to the following output: -- @\label{lst: myListing!5}@ -- -- (C) 2010 Jens von Pilgrim, http://jevopi.blogspot.com property texapp : "TeXShop" property escapeLeft : "@" property escapeRight : "@" property separator : "_" property maxLength : 2000 try tell application texapp if texapp = "TeXShop" then tell application "TeXShop" to set pos to the offset of the selection of the front document tell application "TeXShop" to set currentSelection to the content of the selection of the front document else if texapp = "iTeXMac" then -- ?? end if set start to 1 if (pos > maxLength) then set start to pos - maxLength end if set preceeding_text to (characters start thru pos of (the text of the front document)) as string set preceeding_text to my findAndReplace(preceeding_text, "\\", "_") set lineNumbers to do shell script ¬ "echo " & the quoted form of preceeding_text & ¬ "| sed -n '/label[:space:]*=[:space:]*{[^}]*}/=;${x;=;}'" set theLines to my splitLines(lineNumbers) -- as list set currentLine to last item of theLines as integer -- set startLine to item ((length of theLines) - 1) of theLines as integer -- length is not working when executed as macro... workaround: set startLine to first item of (rest of (reverse of theLines)) set lstLine to currentLine - startLine set lastLabel to do shell script ¬ "echo " & the quoted form of preceeding_text & ¬ "| sed -n '/label[:space:]*=[:space:]*{[^}]*}/h;$ {x;p;}'" & ¬ "| sed -n 's/.*label[:space:]*=[:space:]*{\$$[^}]*\$$}.*/\\1/g;p'" set newSelection to escapeLeft & "\\label{" & lastLabel & separator & lstLine & "}" & escapeRight & currentSelection if texapp = "TeXShop" then tell application "TeXShop" to set the selection of the front document to newSelection else if texapp = "iTeXMac" then --tell application "iTeXMac" to insert new_section in the text of the front document end if end tell on error errmesg number errn beep display dialog errmesg return end try on findAndReplace(strInString, strFind, strReplace) set ditd to text item delimiters set text item delimiters to strFind set textItems to text items of strInString set text item delimiters to strReplace if (class of strInString is string) then set res to textItems as string else -- if (class of TheString is Unicode text) then set res to textItems as Unicode text end if set text item delimiters to ditd return res end findAndReplace on splitLines(strInString) set ditd to text item delimiters set text item delimiters to " " set textItems to text items of strInString set text item delimiters to ditd return textItems end splitLines 
Now things are really easy: Simply activate the macro at the appropriate location in the listing, and a new label will automatically be created. Note: The listing must have a label defined! You can configure the script by changing the properties at the beginning of the macro.