## 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:
\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.