Last entry on line numbering code in blog posts

One thing I forgot to do in the last post was show how to filter a set of lines through my line numbering program, anl, from within TextMate. I did mention that you use the Filter Through Command… item in the Text menu, but didn’t show how to set the options in the item’s dialog box. Here are the options you should select:

I do this before adding the 4-space indentation that tells Markdown to wrap the lines in pre and code tags. After anl adds the line numbers, colons, and spaces, I do the 4-space indentation.

OK, on with the last two items I need to put nicely formatted line numbers in my blog’s code blocks: some JavaScript to wrap the line numbers in span tags, and CSS to style the numbers.

I put the following JavaScript function in the head sections of the various blog templates. (Movable Type has templates for the main page, the date-based archives, the category-based archives, and the individual post archives and I want the same line numbering no matter how you’re viewing the page.)

 1:  function styleLN() {
 2:     var preElems = document.getElementsByTagName('pre');
 3:     if (0 == preElems.length) {   // no pre elements; stop
 4:        return;
 5:     }
 6:     for (var i = 0; i < preElems.length; i++) {
 7:        var pre = preElems[i];
 8:        var code = pre.getElementsByTagName('code')[0];
 9:        if (null == code) {        // no code; move on
10:            continue;
11:        }
12:        var oldContent = code.innerHTML;
13:        var newContent = oldContent.replace(/^( *)(\d+):(  )/mg, 
14:                    '<span class="ln">$1$2<' + '/span>$3');
15:        code.innerHTML = newContent;
16:     }
17:  }

Line 2 grabs all the pre elements in the document. If there aren’t any, Lines 3-4 exit the function and no changes are made. If there are pre elements, the loop that starts on Line 6 goes through all of them, looking for those that contain code elements. The contents of each code element is filtered through a search-and-replace operation on Lines 13-14. The operation searches for lines that start with zero or more spaces, one or more digits, a colon, and two spaces. If found, it replaces that with an opening span tag with a class attribute of “ln”, the leading spaces (if any), the digits, a closing span tag, and the two spaces. Line 15 then stuffs the altered text back into the document. The function is invoked by adding onLoad="styleLN()" to the template’s body tag.

If you’re wondering why the replacement string on Line 14 is the concatenation of two strings, it’s because HTML Tidy complains when it sees a string with </ followed by a letter. I don’t know of any current browsers that fail when they encounter such a string, but I like to keep HTML Tidy happy nonetheless.

The colon is removed (and you know how painful that can be) because I’m going to style the numbers to make them distinct from the following code lines and no longer need the extra separation the colon provided in the Markdown source. Here’s the CSS that styles the line numbers:

span.ln {
  color: #888;
  font-size: 75%;
}

A medium gray text with a slightly smaller size than the code itself. I’ve played around with different background colors and padding to get more of a “gutter” look like you might see in TextMate and BBEdit (and vi and Emacs and any number of other editors), but the extra styling didn’t add anything.

(By the way, if you want to see the code with the colons, just tell your browser to display the source of this page.)

One problem with displaying the line numbers this way is that readers of the blog can no longer just copy code from my posts and expect it to run—the line numbers will get selected along with the code itself. The line numbers and separation space are easily deleted by doing a global search on the regular expression

/^ *\d+  /

(that’s one space between the caret and the asterisk and two spaces after the plus sign) and replacing it with nothing. Depending on the features of your text editor, you may also be able to do a “column” or “rectangular” selection to grab all the line numbers and leading space and delete them with a single keystroke. (TextMate does column selection if you drag while holding down the Option key.) This can be much faster than search and replace if the code isn’t very long and you don’t have to do much scrolling.

I did spend some time thinking about whether the clearer explanations that line numbers allow are worth the extra effort needed to delete them and, obviously, decided that they are. If you think otherwise, let me know.

Tags: