Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with:

Color Coding Sample Code Getting Much Closer

By Ben Nadel on
Tags: ColdFusion

I am almost done figuring out the code I need to color code my code sample. Color coding will make things much easier to read for you guys. The problem is, this is REALLY freakin' hard. I have looked at Ray Camden's color coding and some other coding, and it just doesn't apply here. The problem I have is that my code is structured in lists rather than just plain text or <pre> tags. I do this to exert more control over the layout, display, and wrapping of the code. So far, I am really pleased with it.

So, to start off, let's take a look at the code that I would need to color code:

  • <div class="code">
  • <ul>
  • <li>&lt;!--- Set up my occupation. ---&gt;</li>
  • <li>&lt;cfset strMyOccupation = "Mail man" /&gt;</li>
  • <li></li>
  • <li></li>
  • <li>&lt;!--- Create a new query. ---&gt;</li>
  • <li>&lt;cfset qGirls = QueryNew( "id, name, fantasy" ) /&gt;</li>
  • <li></li>
  • <li>&lt;!--- Add rows to the query. ---&gt;</li>
  • <li>&lt;cfset QueryAddRow( qGirls, 2 ) /&gt;</li>
  • <li></li>
  • <li>&lt;!--- Populate query. ---&gt;</li>
  • <li>&lt;cfset qGirls[ "id" ][ 1 ] = 1 /&gt;</li>
  • <li>&lt;cfset qGirls[ "name" ][ 1 ] = "Julia" /&gt;</li>
  • <li>&lt;cfset qGirls[ "fantasy" ][ 1 ] = "Cabana boy" /&gt;</li>
  • <li>&lt;cfset qGirls[ "id" ][ 2 ] = 2 /&gt;</li>
  • <li>&lt;cfset qGirls[ "name" ][ 2 ] = "Katie" /&gt;</li>
  • <li>&lt;cfset qGirls[ "fantasy" ][ 2 ] = "Mail man" /&gt;</li>
  • <li></li>
  • <li></li>
  • <li>&lt;!--- Loop over the query. ---&gt;</li>
  • <li>&lt;cfloop query="qGirls"&gt;</li>
  • <li class="tab1"></li>
  • <li class="tab1">&lt;!--- Check to see if I am the fantasy. ---&gt;</li>
  • <li class="tab1">&lt;cfif (qGirls.fantasy EQ strMyOccupation)&gt;</li>
  • <li class="tab2"></li>
  • <li class="tab2">&lt;!--- This is the girl for me!! ---&gt;</li>
  • <li class="tab2">&lt;cfmail</li>
  • <li class="tab3">to="xxx@yyy.zzz"</li>
  • <li class="tab3">from="xxx@yyy.zzz"</li>
  • <li class="tab3">subject="Dude! Email this chick now!!"</li>
  • <li class="tab3">type="HTML"&gt;</li>
  • <li class="tab3"></li>
  • <li class="tab3">Ben,&lt;br /&gt;</li>
  • <li class="tab3">This girl is just your type... or rather</li>
  • <li class="tab3">you might just be her's. Giver a call or</li>
  • <li class="tab3">email here ASAP.&lt;br /&gt;</li>
  • <li class="tab3">&lt;br /&gt;</li>
  • <li class="tab3"></li>
  • <li class="tab3">Regards,&lt;br /&gt;</li>
  • <li class="tab3">Your better half.</li>
  • <li class="tab2"></li>
  • <li class="tab2">&lt;/cfmail&gt;</li>
  • <li class="tab1"></li>
  • <li class="tab1">&lt;/cfif&gt;</li>
  • <li></li>
  • <li>&lt;/cfloop&gt;</li>
  • </ul>
  • </div>

As you can see, each line of code is broken up into it's own LI element that is contained within a DIV of class "code". The major problem is that a single tag (such as CFMail in my example), can wrap multiple lines and therefore be spread across several LI tags. Heck yeah! I love a challenge.

So, the hardest part in is figuring out how to start and end color code span tags such that they get the correct code and still comply with XHTML compliance. My function is NOT quite there, but is definitely pretty close. Currently, it has some limitations:

  1. Cannot handle comments spread across several lines.
  2. Does not color encode free standing numbers (as in <cfset x = 2 />).
  3. Cannot handle quotes inside quotes (as in <cfset x = "te""st" />).
  4. Does not work inside CFScript blocks (as you can see in the function below).

There are probably a few other limitation, but as you will see soon, I am almost there. Let's take a look at the function which makes this all possible:

  • <cffunction
  • name="ColorCode"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="This takes code samples and color codes for display.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="Code" type="string" required="true" />
  •  
  • <cfscript>
  •  
  • // Define the local scope.
  • var LOCAL = StructNew();
  •  
  • // Define some constants. At times, we will want to
  • // replace common characters with special characters
  • // so that they will not be manipulated until we
  • // want them to.
  • LOCAL.OpenTag = Chr( 500 );
  • LOCAL.CloseTag = Chr( 501 );
  • LOCAL.OpenTag2 = Chr( 600 );
  • LOCAL.CloseTag2 = Chr( 601 );
  • LOCAL.OpenAttribute = Chr( 700 );
  • LOCAL.CloseAttribute = Chr( 701 );
  • LOCAL.RealOpenAttribute = Chr( 800 );
  • LOCAL.RealCloseAttribute = Chr( 801 );
  • LOCAL.LineBreak = Chr( 900 );
  •  
  • // Set up the span classes to be used for colors.
  • LOCAL.Classes.CFML = "cfmlcodecolor";
  • LOCAL.Classes.Comment = "commentcodecolor";
  • LOCAL.Classes.Attribute = "attributecodecolor";
  •  
  •  
  • // Replace out line breaks. We are doing this so that
  • // our regular expression never need to worry about
  • // matching new line and carriage return charactrers.
  • // This may or may not be needed.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "#Chr( 13 )##Chr( 10 )#",
  • LOCAL.LineBreak
  • );
  •  
  •  
  • // We have to escape the attribute quotes that we do
  • // NOT want to mess with - those ones that are in
  • // standard html tags that ARE parts of code, not
  • // code samples.
  • while( true ){
  •  
  • // Get the new code with replace quotes.
  • LOCAL.EscapedAttributes = ARGUMENTS.Code.ReplaceFirst(
  • "(<[\w]+[^"">]+)("")([^""]*)("")([^>]*>)",
  • (
  • "$1" &
  • LOCAL.RealOpenAttribute &
  • "$3" &
  • LOCAL.RealCloseAttribute &
  • "$5"
  • ));
  •  
  • // Check to see if our last replace did anything.
  • if (LOCAL.EscapedAttributes.Equals( ARGUMENTS.Code )){
  • ARGUMENTS.Code = LOCAL.EscapedAttributes;
  •  
  • // The last replace didn't do anything,
  • // so break out of this loop.
  • break;
  • } else {
  • ARGUMENTS.Code = LOCAL.EscapedAttributes;
  • }
  •  
  • }
  •  
  •  
  • // Replace the regular tags with special characters
  • // so that we don't mess with them. These are tags
  • // that are part of the code, NOT the example code.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "<",
  • LOCAL.OpenTag
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • ">",
  • LOCAL.CloseTag
  • );
  •  
  •  
  • // Replace the sample code (that we want to color
  • // code) with real tags so that we can parse it.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "<",
  • "<"
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • ">",
  • ">"
  • );
  •  
  •  
  • // Now, we have to esacpe the attribute valus of
  • // that tags we just put in.
  • // NOTE: We already escaped the REAL attribute
  • // values, so we do not have to worry about these.
  • while( true ){
  •  
  • // Get the new code with replace quotes.
  • LOCAL.EscapedAttributes = ARGUMENTS.Code.ReplaceFirst(
  • "(<[\w]+[^"">]+)("")([^""]*)("")([^>]*>)",
  • (
  • "$1" &
  • LOCAL.OpenAttribute &
  • "$3" &
  • LOCAL.CloseAttribute &
  • "$5"
  • ));
  •  
  • // Check to see if our last replace did anything.
  • if (LOCAL.EscapedAttributes.Equals( ARGUMENTS.Code )){
  • ARGUMENTS.Code = LOCAL.EscapedAttributes;
  •  
  • // The last replace didn't do anything,
  • // so break out of this loop.
  • break;
  • } else {
  • ARGUMENTS.Code = LOCAL.EscapedAttributes;
  • }
  •  
  • }
  •  
  •  
  • // Surround the comments with span tag.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(<!---((?!--->).)*--->)",
  • (
  • LOCAL.OpenTag &
  • "span class=""" &
  • LOCAL.Classes.Comment &
  • """" &
  • LOCAL.CloseTag &
  • "$1" &
  • LOCAL.OpenTag &
  • "/span" &
  • LOCAL.CloseTag
  • ));
  •  
  • // Surround the cf code with span.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(</?cf+[^>]+?>)",
  • (
  • LOCAL.OpenTag &
  • "span class=""" &
  • LOCAL.Classes.CFML &
  • """" &
  • LOCAL.CloseTag &
  • "$1" &
  • LOCAL.OpenTag &
  • "/span" &
  • LOCAL.CloseTag
  • ));
  •  
  • // Surround the attributes with span.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(#LOCAL.OpenAttribute#[^#LOCAL.CloseAttribute#]*#LOCAL.CloseAttribute#)",
  • (
  • LOCAL.OpenTag &
  • "span class=""" &
  • LOCAL.Classes.Attribute &
  • """" &
  • LOCAL.CloseTag &
  • "$1" &
  • LOCAL.OpenTag &
  • "/span" &
  • LOCAL.CloseTag
  • ));
  •  
  •  
  • // Replace in attribute quotes.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(#LOCAL.OpenAttribute#|#LOCAL.RealOpenAttribute#)",
  • """"
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(#LOCAL.CloseAttribute#|#LOCAL.RealCloseAttribute#)",
  • """"
  • );
  •  
  •  
  • // Our cfml tags might wrap around some other tags (ex.
  • // LIs in an unordered list). To make sure that this
  • // doesn't break the color coding, we have to end the
  • // CFML span before the tag, then continue it after
  • // the matching closing tag.
  • while( true ){
  •  
  • // Get the new code with around-tag spans.
  • LOCAL.NewSpans = ARGUMENTS.Code.ReplaceFirst(
  • "(<cf[^#LOCAL.OpenTag#>]+)(#LOCAL.OpenTag#)([^#LOCAL.CloseTag#]+)(#LOCAL.CloseTag#)([^#LOCAL.OpenTag#]*)(#LOCAL.OpenTag#)([^#LOCAL.CloseTag#]+)(#LOCAL.CloseTag#)([^>]*>)",
  • (
  • "$1" &
  • LOCAL.OpenTag2 &
  • "/span" &
  • LOCAL.CloseTag2 &
  • LOCAL.OpenTag2 &
  • "$3" &
  • LOCAL.CloseTag2 &
  • "$5" &
  • LOCAL.OpenTag2 &
  • "$7" &
  • LOCAL.CloseTag2 &
  • LOCAL.OpenTag2 &
  • "span class=""" &
  • LOCAL.Classes.CFML &
  • """" &
  • LOCAL.CloseTag2 &
  • "$9"
  • ));
  •  
  • // Check to see if our last replace did anything.
  • if (LOCAL.NewSpans.Equals( ARGUMENTS.Code )){
  • ARGUMENTS.Code = LOCAL.NewSpans;
  •  
  • // The last replace didn't do anything,
  • // so break out of this loop.
  • break;
  • } else {
  • ARGUMENTS.Code = LOCAL.NewSpans;
  • }
  •  
  • }
  •  
  •  
  • // Replace back in the old tags constants.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "#LOCAL.OpenTag2#{1}",
  • LOCAL.OpenTag
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "#LOCAL.CloseTag2#{1}",
  • LOCAL.CloseTag
  • );
  •  
  • // Replace out brackets.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "<",
  • "<"
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • ">",
  • ">"
  • );
  •  
  •  
  • // Replace in original tag brackets.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(#LOCAL.OpenTag#|#LOCAL.OpenTag2#)",
  • "<"
  • );
  •  
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • "(#LOCAL.CloseTag#|#LOCAL.CloseTag2#)",
  • ">"
  • );
  •  
  •  
  • // Replace the original line breaks.
  • ARGUMENTS.Code = ARGUMENTS.Code.ReplaceAll(
  • LOCAL.LineBreak,
  • Chr( 13 ) & Chr( 10 )
  • );
  •  
  • // Return color coded text.
  • return( ARGUMENTS.Code.ToString() );
  •  
  • </cfscript>
  • </cffunction>

Yeah, it IS a beefy function. As you can see, I take full advantage of the very sexy Java string regular expressions which can be access via the Java methods that live underneath the ColdFusion string objects. Now, if I were to run the sample code listed above through this, I would get the following:

  • <!--- Set up my occupation. --->
  • <cfset strMyOccupation = "Mail man" />
  •  
  •  
  • <!--- Create a new query. --->
  • <cfset qGirls = QueryNew( "id, name, fantasy" ) />
  •  
  • <!--- Add rows to the query. --->
  • <cfset QueryAddRow( qGirls, 2 ) />
  •  
  • <!--- Populate query. --->
  • <cfset qGirls[ "id" ][ 1 ] = 1 />
  • <cfset qGirls[ "name" ][ 1 ] = "Julia" />
  • <cfset qGirls[ "fantasy" ][ 1 ] = "Cabana boy" />
  •  
  • <cfset qGirls[ "id" ][ 2 ] = 2 />
  • <cfset qGirls[ "name" ][ 2 ] = "Katie" />
  • <cfset qGirls[ "fantasy" ][ 2 ] = "Mail man" />
  •  
  •  
  • <!--- Loop over the query. --->
  • <cfloop query="qGirls">
  •  
  • <!--- Check to see if I am the fantasy. --->
  • <cfif (qGirls.fantasy EQ strMyOccupation)>
  •  
  • <!--- This is the girl for me!! --->
  • <cfmail
  • to="xxx@yyy.zzz"
  • from="xxx@yyy.zzz"
  • subject="Dude! Email this chick now!!"
  • type="HTML">
  •  
  • Ben,<br />
  • This girl is just your type... or rather
  • you might just be her's. Giver a call or
  • email here ASAP.<br />
  • <br />
  •  
  • Regards,<br />
  • Your better half.
  •  
  • </cfmail>
  •  
  • </cfif>
  •  
  • </cfloop>

So, as you can see, I AM getting very close. There are some more kinks to work out, but oh man, I am hot on the trail of greatness.

Tweet This Great article by @BenNadel - Color Coding Sample Code Getting Much Closer Thanks my man — you rock the party that rocks the body!



Reader Comments

Surely you want to use CSS and styles to do this? Or am I not understanding the problem? If you used a combination of DIV's and CSS selectors wouldn't that be easier?

Khalid,

I am using Divs and CSS. The problem is that the sample code is escaped and is not actually spans. So, even where I have <cfset ../> in the code, behind the scenes, I actually have <cfset ... /> which does not provide me with any CSS hooks, unless I am unfamiliar with them.

If you would use a ordered list instead of a unordered list, you could easily add a little js and css to have a show/hide line numbers method on the site (would work without a page refresh).

Trond,

I am not sure how an ordered vs. unordered list would help any? In the HTML, they still both have LI elements. As far as Javascript, I was hoping to have all the "code crunching" done before the viewing of this page. I would rather format the code pre-Database insertion rather than on display. That way, I never have to worry about processing of code, or load time or anything like that.

But, like I said, I am just trying stuff here. I am always open to new suggestions. If I see something better demonstrated, I will adopt it for sure.

Well . . . I use NetNewsWire. Your code in the News Item tab really does come out as a list, with everything lined up at the left edge, which kind of makes it difficult to read your code without index. I'd have to say that I prefer the usage of CODE or PRE tag for formatting the code - much more readable and easier to copy-paste.

LI should be reserved for text that really does appear as a list, ordered or unordered.

Hi Ben,

I put together a little demo.

What I did was the following.
Changed the <ul> to <ol>.
A search and replace in css: replaced "code ul" with "code ol"
Removed the "list-style-type: none;" property from the css.
Added "margin-left: 30px" on the code ol element in the css.
Added a little js in the head section and a little a element to trigger the js.

The demo is here: http://cfskill.com/colorcode/

Lola Lee,

Yeah, I see that on Feed-Squirrel also. I don't really ever read a blog post outside of the parent site, so I don't have too much experience with display without proper CSS and what not.

How does the PRE tag display? Does it display as indented? I might end up switching.

Trond,

That is a pretty sexy demo. Thanks for putting that together. However, when you copy and paste the text from the browser, the line numbers still display on the pasted copy page. I am considering switching over to the PRE tag as Lola Lee (and others) have discussed.

hmm - in firefox it does indeed copy the linenumbers :(

In Opera (my main browser) and IE (which I only use for test purposes) does not.

Wonder if it's possible to get around this in Firefox somehow. I think it's really helpful to have linenumbers in code examples.