Occam’s Razor and Brushes

There’s no new ZIP file for this post. I have yet another set of changes in the works, which should be ready shortly. I’ll post a ZIP file then, with both sets of updates.

Razor and brush, from amazingshaving.com/Merchant2/graphics/00000001/MPS281A820.jpg
Alex Gorbatchev’s SyntaxHighlighter uses separate modules (Brushes) to process the syntax for individual programming languages. A web page that uses SyntaxHighlighter must load the main JavaScript file (shCore.js), and load the required brush files. After the brushes are loaded, the page sets any desired configuration parameters for the highlighter, then invokes SyntaxHighlighter.all, which performs the formatting and highlighting.

SyntaxHighlighter currently includes 21 brushes, each in its own JavaScript file. A web page author may wish to load only those brush files that are needed for a given page. David Chambers addressed this by using JavaScript to request the needed brushes from the server. The first time that I browsed his site, using Firefox, this worked well. But the second time, using Internet Explorer, an error message popped up in my browser window:

SyntaxHighlighter: Can't find brush for: plain

David’s script worked by extending the page’s <head> with a new <script> element to load the brushes. Firefox executed the new script as soon as it was added, but IE delayed execution of the new script until the first one was finished. That first script went on to call SyntaxHighlighter.all(). Because the new script hadn’t been run, the brushes hadn’t been loaded yet; this resulted in the error that I saw.

David resolved this problem by modifying his scripts, but I got to wondering: Was there a way to remove that ordering dependency, so that the brushes didn’t need to be loaded first? I aimed to make this work by splitting the highlighting into two phases:

  • When the web page calls SyntaxHighlighter.all(), SyntaxHighlighter formats any code whose brushes have already been loaded. This is like its previous design, but when it can’t find a brush, it no longer reports that failure as an error.
  • If a brush is loaded after the call to SyntaxHighlighter.all(), then SyntaxHighlighter makes another pass through the document, looking for any code that needs to be formatted using the newly-loaded brush.

If SyntaxHighlighter can make use of each brush as it’s loaded, then there’s no need for the web page to synchronize the brush load operations. This would have simplified David’s task considerably. Unfortunately, SyntaxHighlighter had no way of knowing when a new brush has been loaded.

The structure of a SyntaxHighlighter brush, as currently written, corresponds to the boilerplate code shown below. (The /* comments */ indicate where the contents differ for each brush.)

SyntaxHighlighter.brushes./*languageName*/ = function() {
    /* This function determines the styles for a particular syntax */ 
};
SyntaxHighlighter.brushes./*languageName*/.prototype = 
    new SyntaxHighlighter.Highlighter();
SyntaxHighlighter.brushes./*languageName*/.aliases = 
    [/* An array of alternate names for this language */ ];

Here’s the brush structure as I’ve redesigned it, with most of the boilerplate now encapsulated in a new registration method, SyntaxHighlighter.registerBrush:

SyntaxHighlighter.registerBrush(
    "/*languageName*/",
    [/* An array of alternate names for this language */ ], 
    function()
    {
        /* This function determines the styles for a particular syntax */ 
    });

The new method, SyntaxHighlighter.registerBrush, attempts to apply the brush as part of the registration process, through an internal call to the method that performs the highlighting. This call returns immediately if the web page hasn’t yet invoked SyntaxHighlighter.all(), because it can’t assume that the configuration is ready. (Any pending highlighting will be performed when SyntaxHighlighter.all() is called.) Otherwise, the newly-registered brush is applied to any code blocks that require that brush.

This encapsulation benefits everyone involved: The brush code now needs less knowledge of the highlighter’s internals, and assumes less responsibility for them. SyntaxHighlighter is given knowledge that it didn’t previously have: namely, it gets notified as brushes become available. And the web page doesn’t need any additional logic to orchestrate the brush loading.

Occam’s Razor is often paraphrased as “The simplest explanation is best.” Ironically, that’s a little too simplistic. The original Latin can be translated into English as, entities must not be multiplied beyond necessity. In software engineering, Occam’s Razor has a corollary: When entities are multiplying, find out how they became necessary. Doing so may lead to a better simplicity.

SyntaxHighlighter Revised (again), Works On Its Own (once again)

The latest posted version of SyntaxHighlighter is here. For the change history, or to download older versions from this site, see the downloads page.

Dear jQuery

I’m sorry, jQuery, but I have to kick you out. It’s not fair, I know. But then, I guess it wasn’t right for me to get you involved with SyntaxHighlighter to begin with. There are a lot of web sites out there using SyntaxHighlighter, and it’s too much to ask all of them to welcome you. Yes, I used you, plain and simple; you helped me find my way in JavaScript much more easily than I could ever have done on my own.

And I can’t say enough about how well you helped me along. It’s not as if I enjoy writing code like this:

var
    hDiv = highlighter.div,
    node = hDiv.firstChild,
    linesRegexp = /(^|\s)lines(\s|$)/;

// Find the 'lines' child of the div. (It should be an immediate child.)
while (node !== null && linesRegexp.exec(node.className) === null) {
    node = node.nextSibling;
}

when you let me express myself more clearly and concisely:

var
    hDiv = highlighter.div,
    linesDiv = jQuery(hDiv).find('.lines')[0];

I’ve written some new helper methods. I can’t claim that they’re as elegant or complete as yours, but they should be able to do what I need them to do. Most importantly, I’m not traversing the DOM or updating styles bare-handed; I’ve already had my fill of that.

So, yeah: my new find method doesn’t handle the full range of CSS selectors. But at least I can write:

var
    hDiv = highlighter.div,
    linesDiv = sh.find('.lines', hDiv)[0];

I don’t really need much more than that. Not for now, anyway.

You’re probably thinking: He’ll be back. The next time he gets hit in the face with the realities of DOM and CSS coding, he’ll come back. And you’re right. But alas, that must wait until I’ve moved on from SyntaxHighlighter. In the meantime, let’s stay in touch, OK?

Regards,

Dan Breslau
www.outofwhatbox.com


Because I’ve pulled out the jQuery integrations, the new ZIP file contains just the SyntaxHighlighter code. (Previously, I’d also been including changes to the WordPress plugin.)

Before I close, I’d like to note a couple other changes:

  • The toolbar hover is now implemented in pure CSS. Not that I’m a CSS purist, but some browsers (*cough* Internet Explorer *cough*) can consume a large chunk of the CPU running JavaScript event handers, just because the mouse pointer is inside the SyntaxHighlighter area.

The CSS for this is pretty straightforward, if you leave out (as I do below) the cruft needed to make Internet Explorer understand opacity:

.syntaxhighlighter .bar
{
	/* Hide the toolbar until a hover event occurs */
	display: none !important;
	background: transparent !important;
}

/* Reveal the toolbar when a hover event occurs */
.syntaxhighlighter .lines:hover .bar,
.syntaxhighlighter.showToolbar .lines .bar 
{
	display: block !important;
}

/* Partial transparency for toolbar; used when the mouse is
    over the SyntaxHighlighter area, but not over the toolbar. */
.syntaxhighlighter .bar .toolbar
{
	opacity: 0.78;
}

/* Nearly full opacity, used when the mouse is directly
    over the toolbar. */
.syntaxhighlighter .bar:hover .toolbar
{
	opacity: 0.95;
}

  • The ruler adjusts to the longest line in the display.
So if you're like me and sometimes find that you're going on and on without coming to a good place to break the line, now the ruler will show you just how far you've gone!

The code looks for the innerText and textContent properties, neither of which is standardized. It falls back gracefully to the previously hard-coded length of 150 chars if the browser doesn’t support either property. I’ve tested with IE, FireFox, Chrome, Safari, and Opera 9, all of which support at least one of the two properties.

More SyntaxHighlighter Updates

The latest posted version of SyntaxHighlighter is here. For the change history, or to download older versions from this site, see the downloads page.

I’ve made some more changes to the SyntaxHighlighter code on this site. For brevity, I’ll only touch on the, err, highlights of the changes here. I’ll describe some of the implementation details in later posts.

Gutter can be toggled through the toolbar.

icon for toggling line number displaysThis new toolbar icon toggles the display of line numbers, in a “gutter” on the left of the main text display. In the standard SyntaxHighlighter, the choice of displaying or not displaying the gutter was statically configured within the HTML.

In the standard SyntaxHighlighter, line numbers are included in the HTML DOM even if they’re not being displayed. As a result, copying text from a SyntaxHighlighter box into the clipboard brings the line numbers along for the ride. I’d previously modified my version of SyntaxHighlighter to include line numbers in the DOM only when they’re to be displayed.

With these two sets of changes, text can be copied simply by turning the line numbers off through the toolbar (if need be), then performing the usual copy operation. This may mean that the Flash-based method for copying the code to the clipboard is no longer needed, but I haven’t removed it.

A little transparency can go a long way.

SyntaxHighlighter’s standard toolbar completely obscures the text underneath. I felt that this was a little too disruptive to the user, especially since displaying that text is SyntaxHighlighter’s main purpose. I’ve adjusted the toolbar so that it starts out partially transparent; after a moment it turns mostly opaque. The underlying text isn’t readable, but that’s not the point. Just by being visible at all, it gives the user a subtle signal that content still exists behind the toolbar. (At least, that’s the idea. I’d really like to know what other folks think about it.)

Here’s a SyntaxHighlighter box with a live toolbar, so you can try out this change. Let your mouse hover over this snippet for a moment:

And you may marvel why I obscured myself,
Labouring to save his life, and would not rather
Make rash remonstrance of my hidden power

Toolbar remains inactive when scrolling.

The standard SyntaxHighlighter toolbar is meant to be activated when the mouse moves into the text display. However, it’s also activated by merely mousing over or using the scrollbars. I found that to be a bit of a distraction. The toolbar on this site is now activated only when the mouse moves into the main text display.

Gutter is fixed (in place.)

If you’re displaying line numbers in the gutter, the gutter remains fixed in place as you scroll horizontally (assuming that you have a scrollbar to begin with!) Here’s an example:

 Has had most favourable and happy speed:
 Tempests themselves,  high seas,  and howling winds,
 The gutter'd rocks and congregated sands --
 Traitors ensteep'd to clog the guiltless keel, --
 As having sense of beauty,  do omit
 Their mortal natures,  letting go safely by
 The divine Desdemona.

Under the hood

I’ve cleaned up the code a bit, mostly within the changes I’d made in the last round.

In particular, my last post described how I’d gone to some lengths to get the right line lengths, without which I couldn’t make the background look right. But the resulting code seemed somewhat fragile, even smelly. On further review, I realized that I could eliminate all the mumbo-jumbo that calculated the widths of margins, padding, and borders. It’s been replaced with simpler mumbo-jumbo calculations based on the scrollWidth of the container and the offsetWidth of the text lines.

But I promised not to discuss implementation in this post, so that’s all I’m sayin’ for now.

Update: Over at the SyntaxHighlighter forums, Rithiur informed me that the ZIP file I’d placed on the site for download had nonstandard configuration values. That was due to an error on my part, of course; my apologies go to anyone inconvenienced by the oversight.

At the same time, I was testing a new version of SyntaxHighlighter using updates from Rithiur that further improve the copy/paste functionality. The current ZIP file should have the original default configuration, as well as Rithiur’s updates.

Fixing A Scroll Where The Grays Came In

The latest posted version of SyntaxHighlighter is here. For the change history, or to download older versions from this site, see the downloads page.

This blog uses Alex Gorbatchev’s SyntaxHighlighter, by way of the SyntaxHighlighter Evolved WordPress plugin. It’s a great piece of work, but one minor problem had been nagging at me.

SyntaxHighlighter places alternating white and light gray backgrounds on displayed lines, as an aide to readability. However, when SyntaxHighlighter is configured for scrolling rather than text wrapping, these backgrounds don’t extend for the full length of the scrolling region. Slide the scrollbar below, and you should see a darker gray background on the right, instead of the alternating light gray and white bars that are visible in the original viewport:

 
// Scrolling this to the right exposes a grey background
public void setFocus(Object lens) {
 

Many GUI tools and APIs that I’ve used in the past incorporated some form of “stretchiness”. For example, a “stretchy” button widget might specify a minimum size—but, if more space is available after other components have been laid out, it will gladly stretch out to fill the remaining space. If there were a CSS equivalent for stretchiness, a good place to use it would be on the <span>s that display those alternating backgrounds in SyntaxHighlighter. However, I’d never encountered a “stretchiness” setting in CSS, and searching for it this time around didn’t bring me a different result. This time around, though, I asked for help.

StackOverflow user brianpeiris confirmed that what I was looking for doesn’t exist in CSS. He also provided JavaScript code to stretch the width of those <span>s to the full width of their parent. Unfortunately, this wasn’t the end of my story. For one thing, this JavaScript uses jQuery, but SyntaxHighlighter isn’t built using jQuery. Hence I had the choice of re-writing his solution, or incorporating jQuery into my SyntaxHighlighter plugin. (I chose the latter approach.) For another, the script needs to be run after the document is fully loaded. Farther below, a code snippet shows changes to SyntaxHighlighter.all() to handle this.

The JavaScript approach also required a minor update to SyntaxHighlighter’s CSS1. SyntaxHighlighter zealously guards its style definitions with the !important flag, making attempts to override them from JavaScript fail. This was solved by changing shCore.css. The definition for width was removed from a large class block:

Old version

.syntaxhighlighter,
.syntaxhighlighter div,
.syntaxhighlighter code,
.syntaxhighlighter span
{
 /*... other settings not shown here... */
	width: auto !important;
 /*... other settings not shown here... */
}

into two smaller cascading blocks: One for .syntaxhighlighter span that omits the !important flag, and another block for the other three selectors.

New version

.syntaxhighlighter,
.syntaxhighlighter div,
.syntaxhighlighter code,
.syntaxhighlighter span
{
 /*... other settings not shown here... */

   /* This is the same large block as before,
    * except that width is now set in the blocks below.
    */ 

 /*... other settings not shown here... */
}

/* Everything except .syntaxhighlighter span still has !important */
.syntaxhighlighter,
.syntaxhighlighter div,
.syntaxhighlighter code
{
	width: auto !important;
}

.syntaxhighlighter span
{
	width: auto;
}

But there was more to come: As it turned out, the stripped-down example that I’d posted on StackOverflow didn’t exercise the CSS box model quite as much as SyntaxHighlighter does. When I adapted brianpeiris’s solution in live code, the script gave each line of text within a single container a slightly greater width than the line before it, with an interesting visual effect. (Notice that this box has a scrollbar, even though the contained text doesn’t seem to be wide enough to warrant one. Scroll to the right and note the background colors.)

// Scroll me to the right...
// Scroll me to the right...
// Scroll me to the right...

I’ve already hinted at the underlying problem here: When the script adjusts the width of each content <span>, it needs to account for the space used by the line’s padding and margin. Otherwise, it uses a width that’s too large. The too-large width makes the containing <div> grow wider, so the next line has more width to fill…2

The version shown below fixes this. (It’s also been modified to run inside of the main SyntaxHighlighter jss file, shCore.js ):

fixContentSpans : function() {
    jQuery('.syntaxhighlighter &gt; div.lines').each(function(){
        var container = jQuery(this);
        var scrollWidth = this.scrollWidth;
        var width = jQuery(this).width();
        var contents = container.find('.content');
        jQuery(contents).each(function(){
            var child = jQuery(this);
            var widthAvail = 
                scrollWidth - parseFloat(child.css(&quot;margin-left&quot;))  
                - parseFloat(child.css(&quot;padding-left&quot;))
                - parseFloat(child.css(&quot;padding-right&quot;));
            var borderLeft = parseFloat(child.css(&quot;border-left-width&quot;));
            // IE uses names (e.g. &quot;medium&quot;) for border widths, resulting in NaN
            // when we parse the value.  Rather than trying to get the numeric
            // value, we'll treat it as 0. This may add a few additional pixels 
            // in the scrolling region, but probably not enough to worry about.
            if (!isNaN(borderLeft)) {
                widthAvail -= borderLeft;
            }
            child.width(widthAvail);
        });
    });
},

(Note: The standard SyntaxHighlighter style sheets don’t include padding-right settings for content lines. I added it to my version of shCore.css, for aesthetic reasons.)

A similar issue cropped up when the browser window is resized: Once again, the <span>s that paint the background colors weren’t being resized to fit the new layout. Adding a resize event handler in shCore.js fixed that problem. (The new version also shows how the new script is invoked in the document load event.)

Old version (reformatted for display) :

/**
 * Main entry point for the SyntaxHighlighter.
 * @param {Object} params Optional params to apply to all highlighted elements.
 */
all : function(params)
{
    sh.utils.addEvent(window, 'load', function() { sh.highlight(params); });
}

New version (reformatted for display) :

/**
 * Main entry point for the SyntaxHighlighter.
 * @param {Object} params Optional params to apply to all highlighted elements.
 */
all : function(params)
{
    sh.utils.addEvent(window, 'resize', function() {sh.fixContentSpans();});
    sh.utils.addEvent(window, 'load',   function() {sh.highlight(params); sh.fixContentSpans();});
}

Removing line numbers from the paste buffer

While I was under the hood, I made one other fix. As it’s currently written, shCore.js always produces the HTML for displaying line numbers, regardless of whether the line numbers will actually be displayed. (Turning off line numbers is implemented by making them invisible via CSS.) The performance impact of this is probably negligible, but there’s a side-effect that isn’t: When you copy one or more lines of code from the browser window, the line numbers (visible or not) are included in the copy. When you paste, you get something like this:

01. /**
02. * Main entry point for the SyntaxHighlighter.
03. * @param {Object} params Optional params
04. */
05. all : function(params)

I’ve changed shCore.js so that these invisible line numbers aren’t inserted into the DOM. With this change, copying from a SyntaxHighlighter display produces the program text and nothing but the program text.

Downloading

A new version of the SyntaxHighlighter Evolved plugin is available as a ZIP file. This is based on v. 2.1.0 of SyntaxHighlighter Evolved, which in turn incorporates v. 2.0.320 of SyntaxHighlighter.


1 Most likely, this problem could also have been solved with a JavaScript change to force the !important setting into the element’s style. It didn’t seem !important enough to try this.

2 This is running into the territory of the IE Box Model bug. I didn’t see a need to try to add a workaround for it; I doubt that many readers of this blog would be affected by that bug. (Actually, if I ever have grounds to say “many readers of this blog” with a straight face, I’d cheerfully add such a workaround.)