Thursday, January 31, 2013

Prettify.js Perfection and Decade-Old Bugs

I'm a bit of a perfectionist, as many people who know me will tell you, so naturally I wanted to get prettify.js working exactly the way I want it to. It ended up being a bit more work than I predicted, but a lot of fun.

I already documented my experience with getting overflow working properly in Opera, here's the full documentation on my prettify.js implementation:

Adding it to blogger is simple, it's just a few lines inserted in the header:

<link href='http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.css' rel='stylesheet' type='text/css'/>
<script language='javascript' src='http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js' type='text/javascript'/>
<script language='javascript' src='http://google-code-prettify.googlecode.com/svn/trunk/src/lang-css.js' type='text/javascript'/>
<script type='text/javascript'>
    document.addEventListener('DOMContentLoaded',function() {
        prettyPrint();
    });
</script>

I wanted alternating line colors, which is simple enough: just add the class linenums to the pre tag and prettify throws the code in an ordered list. Prettify by default only numbers every 5th line, which I wasn't all that thrilled about, so I customized it by overwriting the prettify CSS on my blog. Blogger doesn't allow uploading a css file, so it's all inline in the header, which kinda sucks from a management perspective, but it could be worse. I also wanted to add a line between the numbers and the code to better separate them, that too was simple.

One thing that was a bit tricky was getting alternating line colors to play nice with my overflow. I did quite a bit of Googling before I found out the answer on StackOverflow. Apparently the problem has to do with ordered lists being block elements. Making them display:inline-block took care of it.

Below is my CSS for prettify:
#main-wrapper pre {
    overflow-wrap: normal;
    word-wrap: normal;
    overflow: auto;
    max-height: 800px;
}
ol.linenums {
    display:inline-block !important;
    margin-right: 0px;
}
ol.linenums li {
    border-left: 1px solid #a0e66a;
    padding-left: 5px;
    padding-right: 5px;
}
ol.linenums li.L0, ol.linenums li.L1, ol.linenums li.L2, ol.linenums li.L3, ol.linenums  li.L5, ol.linenums li.L6, ol.linenums li.L7, ol.linenums li.L8 {
    list-style-type: decimal;
}
Finally I wanted to add an option for people to remove the line numbers if they don't like them via a toggle, which was a simple javascript implementaiton:
<script type='text/javascript'>
    //<![CDATA[
        function toggle_visibility() {
            'use strict';
            var list, count;
            list = document.querySelectorAll('ol.linenums');
            count = 0;
            while(count < list.length) {
                if(list[count].style.paddingLeft === '0px') {
                    list[count].style.paddingLeft = '40px';
                } else {
                    list[count].style.paddingLeft = '0px';
                }
                count += 1;
            }
            return;
        }
    //]]>
</script>
With that, I'm pretty happy. I try to always do vanilla javascript instead of using jQuery. Not that there is anything wrong with jQuery: I just prefer to have my code work with as few dependencies as possible. Just call the javascript function with a <a href="#" onclick="toggle_visibility();return false;">Toggle Line Numbers</a> and it's all done.

After that I went around just checking everything out. I copied the script from Firefox into notepad and was dismayed to see that all the white space didn't copy over! Apparently there is a decade-old bug in Firefox where it won't copy whitespace in certain instances even when rendered in the browser. One such instance appears to be ordered lists inside a pre tag. I didn't know that before, though it would explain why PasteBin doesn't do a simple pre tag of an ordered list (it's quite complicated, you should take a look at their page source some time). I never knew this before. Opera does it fine and always has since me using it. I decided to see how other browsers handled it. Chrome did fine with whitespace too; I had no problem copying my script perfectly in Chrome. Then I tried Internet Explorer... Oh man, it was bad. It pasted the entire thing as one giant line. You see, I'm using the pre tag to the fullest: I'm not bothering to declare line breaks in html, since pre tags will honor them natively. Internet Explorer isn't just too stupid to copy whitespace in pre tags, it's too stupid to even copy newlines!

I found it quite entertaining and educating. I do hope that Firefox bug is fixed soon. I couldn't imagine using Firefox as my daily driver with that bug. IE? Talk about a nightmare. Anyone who stumbles upon this looking for an implementation of prettify.js on Blogger, I hope you found it helpful.

EDIT: As you may have noticed, I actually changed my mind after posting this. I'm now using a different theme for prettify.js: Tomorrow Night Eighties (adding "DejaVu Sans Mono", "Bitstream Vera Sans Mono" as fallback fonts for a more uniform cross-platform rendering). My reason for doing this? The whiteness of non linenums prettyprint is far too harsh. At the same time, I don't like how bad that bug about white-space copying in Firefox ended up being. I don't want to penalize Firefox users, so I found a theme that's not so harsh on the eyes (IMO). You should now be able to copy the code posted on here in Firefox with white-space intact. I'll leave the notes about the old way of doing things up here in case it is useful to anyone else.

0 comments:

Post a Comment