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.)

, , ,

3 Comments

  • Paul Geffen says:

    When my cursor hovers over the examples in the blog above, a four-item graphical menu appears in the top right corner. When the cursor is over the menu items, bubble help appears for three of the four.
    Item 1: “view source”
    Item 2
    Item 3: “print”
    item 4: “?”

    When the cursor is over item 2, no help appears but all four items flash. I have not tried to click on item 2. The browser is Camino on Mac OS X.

    In Safari on OS X, the little menus did not appear at first but now that I can see them I can see help for the second item. The help text is “copy to clipboard” for this. And the help text for the other three now has the full URI for a target, eg.: “http://www.outofwhatbox.com/blog/2009/05/fixing-a-scroll-where-the-grays-came-in/#viewSource” instead of the short help text.

    So much for standards.

    – Paul

    • Dan Breslau says:

      Thanks for reporting this, Paul. The second item is supposed to be “Copy to clipboard”, as you saw with Safari. The SyntaxHighlighter implements it using Adobe Flash, because Javascript disallows access to the clipboard in most circumstances. Do other Flash applications work inside Camino on your system?

      Re standards: I’m working on a related piece of Javascript that works well on Firefox, but fails miserably on Internet Explorer. (Of course, that’s a redundancy to many folks 🙂 So much, indeed.

  • Great work, Dan! I would love to see these changes — which seem to be improvements rather than alternatives — incorporated into the trunk. (We’ll have to see whether Alex agrees.)

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>