Syntax Highlighting a la StackOverflow with Google Prettify

Friday, April 09, 2010

Enclosed find notes for implementing a simple version of Stackoverflow-style code syntax highlighting with Google Prettify in two scenarios:

  • Prettify all code on the page, at load
  • Prettify code as the user types it

Scroll down to the comment form to play with it (better yet, head over to Stackoverflow and look at theirs). There's also a six-step non-TL;DR version of this post at the very bottom of the page.

Also, I've been wanting to sing the praises of Google Prettify for a long time, as it's an awesome little library, free, open, extensible, with support for most major programming languages, along with a few obscure ones. It doesn't even require you to specify what language your code is in. It just knows. It just works.

So assuming you've already downloaded and installed Google Prettify, let's get started.

Syntax Highlighting All Code on the Page

By default, Google Prettify requires that you decorate your <pre> and <code> blocks with a "prettyprint" CSS class, like so:

<pre class="prettyprint">
// Some source code
class Foo
{
   public int Bar { get; set; }
}
</pre>

After the prettifier does its thing, voila, syntax-highlighted code:

class Foo
{
public int Bar { get; set; }
}

It's easy to use and works fine for most purposes. But maybe, just maybe, we'd prefer not to clutter our pre/code tags with any extraneous class names at all. Maybe we're applying Google Prettify to existing legacy markup. We should be able to create a standard <pre><code> pair (or single <pre> and/or <code> if that's your style) and the formatting should just cleave to that.

<pre><code>
// Some source code
class Foo
{
   public int Bar { get; set; }
}
</code></pre>

And in fact if you do a "View Source" over at Stackoverflow.com, or here on Coding the Wheel for that matter, that's what you'll see. No "prettyprint" class names, despite the fact that Google Prettify is being used. Just clean <pre><code> pairs.

So how is it done?

Well, you could tweak the Prettify source to look for <pre><code> pairs instead of "prettyprint" classes. That's a straightforward change, and you'd make it here, in prettify.js:


    function doWork() {
      var endTime = (window['PR_SHOULD_USE_CONTINUATION'] ?
                     clock.now() + 250 /* ms */ :
                     Infinity);
      for (; k < elements.length && clock.now() < endTime; k++) {
        var cs = elements[k];
        if (cs.className && cs.className.indexOf('prettyprint') >= 0) {

But messing around with 3rd-party source is, well, messy. A better approach would to be to add the prettyprint tags in Javascript during page load (or whenever). In fact, if you look at the minified Stackoverflow master .js file you'll see that's exactly what Stackoverflow does:


// Stolen from StackOverflow. Find all </code><pre><code> 
// elements on the page and add the "prettyprint" style. If at least one 
// prettyprint element was found, call the Google Prettify prettyPrint() API.
//http://sstatic.net/so/js/master.js?v=6523
function styleCode() 
{
    if (typeof disableStyleCode != "undefined") 
    {
        return;
    }
    var a = false;
    $("pre code").parent().each(function() 
    {
        if (!$(this).hasClass("prettyprint")) 
        {
            $(this).addClass("prettyprint");
            a = true
        }
    });
    if (a) { prettyPrint() } 
}

That will loop over all the <pre><code> blocks on the page and, if any are found, decorate each <pre> with the "prettyprint" class. Once that's done, styleCode() calls the prettyPrint() entry point, which is one of two workhorse functions provided by Google Prettify.

That's basically one-stop shopping for all your (client-side) syntax highlighting needs. Just invoke the function during page load and you're good to go.

<script type="text/javascript">
        $(function() {
            styleCode();
        });
    </script>

Or this way:

<body onload="styleCode()">

Since we're applying these class names directly to the DOM, they're not visible when you do a View Source. Also, when the user has Javascript turned off, or if the "user" happens to be a search engine, his <pre><code> code tags won't carry an extraneous "prettyprint" reference.

Dynamic As-You-Type Syntax Highlighting

The above approach works fine for single-shot syntax highlighting. But if you want to do syntax highlighting on the fly, as the user types, you'll need to make a couple changes. Specifically, instead of doing syntax highlighting once, on page load, you'll need to do it repeatedly, as the user types. The question is how and how often.

To make this a bit clearer, let's assume you have a typical <textarea> comment field with a <div> preview area. As the user types into the <textarea>, a preview appears in the preview <div>. And if the user types a code sample, we'd like that code sample to be syntax highlighted when it appears in the preview.

Let's start with a "naive" approach. What if we called our styleCode() API every time we detected a keypress in the form's <textarea>?

$(document).ready(function() {
        $('#mytextarea').keydown(function() {
            prettyPaint();
        });
    });

Or this way:

<textarea ... onkeydown="prettyPrint()"></textarea>

This actually works, but it's messy, it's wasteful, and it doesn't look great. What ends up happening is this:

  1. The user types some text.
  2. The editor (the Stackoverflow fork of the WMD editor, in this case) runs some Javascript, creating a running (but unprettified) preview of that text in the preview area. This is visible for a split-second.
  3. The <textarea> keydown event happens; our code responds by calling the styleCode() function.
  4. Google Prettify massages the <pre><code> blocks in WMD-generated preview.

The end result is that the preview text flutters back and forth between the unprettified and the prettified version of the WMD-generated preview. Not the effect we're looking for. No matter what we do, if we try to syntax-highlight the preview area at a frequency of "every keystroke", we're going to run into visual fluttering, at least with the WMD + Google Prettify stack as it exists today.

What we can do instead is implement a time-based algorithm like this:

Okay, if the user hasn't pressed a key in the last couple seconds, he's reached a lull in his typing. Now would be a good time to call the Google Prettify stuff.

This isn't exactly what Stackoverflow does, but the effect is close enough. And we can do it succinctly with a nice piece of jQuery code. (Note: here I'm using the jQuery Timers plugin. You could easily achieve the same effect with normal Javascript timer calls.)

$(document).ready(function() {
		$('#mytextarea').keydown(function() {
			$(this).stopTime();
			$(this).oneTime(1000, function() { styleCode(); });
		});
	});

That creates a single timer with an expiration exactly one second (or two seconds, etc., depending on the feel you're looking for) from the time of the user's last keypress. The effect is that pressing a key "pushes the timer back", so the prettify doesn't actually run until the keyboard is "idle". Step by step:

  1. The user edits some text in the <textarea>.
  2. We respond to the onkeydown event.
  3. We cancel the timer, if it exists (by calling stopTime()).
  4. We initialize the timer, set to repeat once, with a duration of one or two seconds (by calling oneTime()).
  5. (At some point in the future) When the timer manages to fire, we do the prettification (by calling styleCode()).

And actually we can clean this code up even more.

The call to styleCode() is overkill because it prettifies all the code blocks on the page. We don't want to prettify all the code blocks on the page in this context (remember, we're on a sliding 1-2 second timer here); we want to prettify the code in the preview <div> only. (Note: we can still call styleCode() at page load, in order to prettify code blocks outside of the comment DIV. But within the timer function, all we want to do is prettify the preview area.)

Luckily, Google Prettify supports another API, prettyPrintOne(), which allows you to pass in a string to be prettified. So let's define a stylePreview() function which will go in and a) add the "prettyprint" CSS class to the preview <div>, and b) replace the preview <div> content with prettified markup returned by prettyPrintOne().

function stylePreview() {
    $("#my_preview_div pre").addClass("prettyprint");
    $("#my_preview_div code").html(prettyPrintOne($("#my_preview_div code").html()));
}

Use that call in your timer function...

$(document).ready(function() {
$('#mytextarea').keydown(function() {
$(this).stopTime();
$(this).oneTime(1000, function() { stylePreview(); });
});
});

...and you're done, or close to being done. There are still a couple flaws with this approach:

  • When the user uses the editor buttons to markup pieces of code, syntax highlighting won't be invoked until the user actually presses a key inside the <textarea>. And unfortunately the onchange event won't help you here, since it's not fired until the control loses focus.
  • This technique doesn't take advantage of whatever hooks/extensibility points your editor might offer.

The Stackoverflow editor fixes this problem (I believe) by either polling for changes or hooking into WMD's polling cycle. For Coding the Wheel, I decided a momentary lapse of highlighting wasn't a big deal, provided the elements are CSS'd such that the prettified- and non-prettified elements don't shift in space or size. The effect of applying on-the-fly syntax highlighting to a previously unhighlighted piece of code should be to add color, not to change the point size, the font family, etc.

Conclusion

This article is a longish explanation of something that's actually pretty simple. Here's the TL;DR version:

  1. Install Google Prettify. Don't forget to reference the prettify.js and prettify.css files.
  2. Add the styleCode() and stylePreview() functions shown above to one of your .js files.
  3. Subscribe to the keydown event of the <textarea> and set a timer, as above.
  4. In the timer function, call the stylePreview() function.
  5. If necessary, call styleCode() at page load to prettify static code blocks.
  6. Tweak cosmetics as necessary.

Some additional stuff I didn't have time to get into:

  • If you're using or want to use the WMD editor, use the Stackoverflow fork and the MarkdownSharp converter.
  • Style unprettified and prettified code blocks (in your CSS) such that the only difference between the two is color. That way, dynamic syntax highlighting will look natural when it "turns on", and users who don't have Javascript will still get the right code layout, just lacking color.
  • Merge the prettify.js and language plugin files into a single .js file, and minimize/pack it.
  • Look into specific editor hooks that you can use to improve on this technique.
  • Make sure non-Javascript users still get a decent experience, too. Again, a syntax-highlighted code block should degrade into a non-highlighted code block with the same formatting.

Hopefully somebody out there will find this useful. Speaking as a reader, I'd always prefer to be able to submit HTML/Markdown comments, even though 90% of the time all I need is plain text. There's always that time where you really want to lay something out, and that's when plain text fails.

Questions? Bugs? Comments? You know what to do...

Tags: Internet, stackoverflow.com, jQuery, WMD, Google Prettify, Javascript, HTML, code

52 comment(s)

public class Foo() {

}

Two posts in one day! WOW.

while (true)
   doSomething(); 

You should probably mention to people that they need to indent code samples four spaces or use the code button with the 1s and 0s.

Nice. The only thing I'd change is, get rid of the scrollbars. Let it overflow if need be. Those scrollbars are annoying.

Nice work. We're using a similar approach for an internal wiki. Albeit, we're not highlighting source code!

By the way, instead of doing this

$(document).ready(function() {
        $('#mytextarea').keydown(function() {
            $(this).stopTime();
            $(this).oneTime(1000, function() { stylePreview(); });
        });
    });

You could just listen for the onchange event:

$(document).ready(function() {
        $('#mytextarea').change(function() {
            $(this).stopTime();
            $(this).oneTime(1000, function() { stylePreview(); });
        });
    });

That should be cleaner than catching individual keypresses.

Thomas, last time I checked, onchange isn't fired until focus leaves the edit box. So that wouldn't really work.

I despise jQuery but applaud your tenacity in getting that POS to do anything elegant. Not a bad effect. One glitch: when you submit a comment, it doesn't highlight until the page is refreshed. Just FYI. The keypress-highlighting works great.

I despise jQuery but applaud your tenacity in getting that POS to do anything elegant. Not a bad effect. One glitch: when you submit a comment, it doesn't highlight until the page is refreshed. Just FYI. The keypress-highlighting works great.

2nded.








































Maybe that's unnecessary optimization, but you could use the child CSS selector to ensure stricter matching of your pre/code pairs. It may also speed up jquery a bit.

$("pre > code").parent().each(function() 
{
    if (!$(this).hasClass("prettyprint")) 
    {
        $(this).addClass("prettyprint");
        a = true
    }
});

class a {

}

class a {

}

Pokerbot...

Just wanted to mention, I've been using the original WMD editor for about a year and have had good results with it. I didn't even know there was a Stackoverflow WMD editor. So I checked it out and was pleased to discover the Stackoverflow version has de-obfuscated source, so you can actually see what's going on (though you won't need to). Best news I've had in weeks. It means you can customize the WMD as little or as much as you like.

JD, I'm going to implement this. If it doesn't work, I'm coming after you.

Nice solution, I started implementing this into a control of mine however it seems that the stopTime and oneTime functions are part of a jQuery timing plugin not in the Core? Maybe you already mentioned that somewhere.

I got it going using standard javascript setTimeout and clearTimeout functions but then noticed since I was using SyntaxHighlighter instead of google's code prettify it would jump around quite a lot after being rendered. This was due to the way SyntaxHighlighter removes the pre tag and replaces with div's allowing the code snippets to word wrap with the screen width.

End the end I just went with a button the user can click to prettify their code snippets they have typed in the WMD control when they desire.

Just thought you would be interested :)

Prettify is nice, but syntax-highlighter is better...

xfsdfsdfsdfsdf

sadfsdfsafasfsadf

This is a test. SELECT * FROM SQL WHERE MyField = "1"

This is another test.

FOR X = 1 to 10
    Do Something
Next

awesome article. thanks for the thorough explanation!

alert("Gfdgdf")

fdfdsfdsfdsf

       $link = mysql_connect("localhost");
    mysql_select_db("test");
    $sql = "SELECT id FROM `test`.`db` WHERE 1";
    $query = mysql_query_db($sql);
           $link = mysql_connect("localhost");
    mysql_select_db("test");
    $sql = "SELECT id FROM `test`.`db` WHERE 1";
    $query = mysql_query_db($sql);
           $link = mysql_connect("localhost");
    mysql_select_db("test");
    $sql = "SELECT id FROM `test`.`db` WHERE 1";
    $query = mysql_query_db($sql);
$(document).ready(function() {
        $('#mytextarea').keydown(function() {
            $(this).stopTime();
            $(this).oneTime(1000, function() { stylePreview(); });
        });
    });

this is a test

again

$(document).ready(function() {
        $('#mytextarea').keydown(function() {
            $(this).stopTime();
            $(this).oneTime(1000, function() { stylePreview(); });
        });
    });

$(document).ready(function() { $('#mytextarea').keydown(function() { $(this).stopTime(); $(this).oneTime(1000, function() { stylePreview(); }); }); });

Thanks for a nice tutorial!

How is this done without using jquery? I tried translating it like this:

function updateCode() {
 document.getElementById('preview_div').innerHTML = prettyPrintOne(document.getElementById('code_textarea').value);
}

The result is that it executes the HTML instead of showing the code.

I am just familiarizing myself with google prettify. It is fairly difficult to get used to for a novice amongst such things. I know it is very useful though so I am willing to put in the work.

Hmm,

I havea problem with prettyPrintOne() remove my line breaks in the code blocks. So all my code turns into a single line of text.

Mike

What version of Pretty print are you using?

Hey, I have been working on live-preview comments for a while now, and I think I got it to a good place. I have a couple of comments on your code here.

  1. If the comment contains more than 1 comment block, you'll notice this that the first comment block is replaces the second. This can be fixed by looping over the < code > elements using jquery's each function.

  2. You don't actually need to wait for the user to stop typing in order to color the code blocks. Since you are already pulling the code text out into jquery, you can just update it before you send the text to the live preview at all.

  3. Rather than update the preview on a per-character basis, which seems like a lot. You could use setInterval() to check for updates every 100 or 200ms. This solves over-updating the preview.

The updated function is slightly longer, but I believe it functions much cleaner to the end user:

    // Setup the Live Preview - This will loop every 200ms.  If no changes are
    // found, it no work will be done.  Otherwise it will run the text through
    // showdown and prettify to create the live-preview.
    var showdown = new Showdown.converter();
    var prev_comment = "";
    var update_live_preview = function() {
        new_comment = $('#id_comment').val();    // Point this to the comment input
        if (new_comment != prev_comment) {
            // Calculate values to display
            var comment = $("<div>"+ showdown.makeHtml(new_comment) +"</div>");
            comment.find('pre').addClass('prettyprint');
            comment.find('p code').addClass('prettyprint');
            comment.find('code').each(function() {
                $(this).html(prettyPrintOne($(this).html()));
            });
            // Update the live preview values
            $('#commentpreview div.text').html(comment);    // Point this to live-preview area
            prev_comment = new_comment;
        }
    }
    setInterval(update_live_preview, 200);    // Change update value to your liking

You can see an example of this all here: http://pushingkarma.com

select * from mathus

function test(){ blast(); }

Hi, I've got one problem with Google Prettify, when I call prettyPrint/One multiple times on some element, all "Google prettify" related elements get duplicated. So if I type 10 caharacters there is 10 copies of so it looks like:

keyword

Any idea, how to solve this?

How can I have this work on my textarea while I type, on real time textarea syntax highlighter?

Good work,I never regreted reading it.By the way you can vist our online store that just logo in your mouse.

Reading a good airtcle made me learn musc from it,that there are many defferent ideas of authors,share with us and affact us at the same time.Then happy to move your mouse and log in the web to visit my online store.

class Foo { public int Bar { get; set; } }

br:after {
    content: ".";
    visibility: hidden;
    display: block;
}
enter code here

I love my ghd outlet Glattetang! It heats up very quickly and works well. My glamour hair is annoyingly thick and poofy, but ghdstraighteneroutletaustralia works wonders! I have never been so fully satisfied with just ghd straightener outlet supplier! What a pleasure shopping at this ghd outlet australia! Thank you very much for this wonderful shopping experience. I will be shopping ghd outlet very very often.

scsd`sdsdsd
`
ccxckjhjk
#include<iostream>
int main()

eeeee

{
return 0;
}
var t = 1;
var s = 1;

Why do you not use Markdown, the syntax and editor that stackoverflow used?

public static void main()
{
}

public class Slider{

}

function test() { alert('hi'); }

public static void main()

Some random text!

<a href="www.example.com" title="ThisTitle">Some Random Words</a>

What on earth!

<?php
echo "Hello World!";
?>    

Use the form below to leave a comment.






Coding the Wheel has appeared on the New York Time's Freakonomics blog, Jeff Atwood's Coding Horror, and the front page of Reddit, Slashdot, Digg.

On Twitter

Thanks for reading!

If you enjoyed this post, consider subscribing to Coding the Wheel by RSS or email. You can also follow us on Twitter and Facebook. And even if you didn't enjoy this post, better subscribe anyway. Keep an eye on us.

Question? Ask us.

About

Poker

Coding the Wheel =
Code, poker, technology, games, design, geekery.


Hire

You've read our technical articles, you've tolerated our rants and raves. Now you can hire us anytime, day or night, for any project large or small.

Learn more

We Like

Speculation, by Edmund Jorgensen.