CoffeeScript brush for SyntaxHighlighter
by rod on February 16, 2011
I have finally got around to setting up my blog, I’ve been meaning to do so for a long time as a place to store interesting bits of code and techniques that I haven’t found elsewhere on the web. One of the things I want to share from time to time is the beautiful way that CoffeeScript, Prototype and Scripty2 work together. Unfortunately I couldn’t find a syntax highlighter option for CoffeeScript beyond embedding Gists or similar.
So, I picked up the SyntaxHighlighter Evolved plugin for WordPress and started hacking. Language brushes as they are called are really just JavaScript objects that bundle a bunch of regular expressions and match them to CSS attributes. SyntaxHighlighter does the rest. Most of the brushes included are pretty basic and mainly do keywords, strings and comments and a few other minor things. The aim is basically to show something that’s easier on the eye than plain code. Highlighting just keywords, string and comments is surprisingly effective in doing this.
But we can do a bit better than that. Since it’s JavaScript regular expressions we are a little limited and can’t parse the whole language easily so I’ve stopped quite a bit short and some of it is hit-and-miss, especially variable matching since CoffeeScript is pretty lose.
Firstly here’s some examples, these are taken straight from the CoffeeScript site:
Functions
square = (x) -> x * x cube = (x) -> square(x) * x
fill = (container, liquid = "coffee") ->
"Filling the #{container} with #{liquid}..."
Objects and Arrays
song = ["do", "re", "mi", "fa", "so"]
singers = {Jagger: "Rock", Elvis: "Roll"}
bitlist = [
1, 0, 1
0, 0, 1
1, 1, 0
]
kids =
brother:
name: "Max"
age: 11
sister:
name: "Ida"
age: 9
$('.account').attr class: 'active'
log object.class
Lexical Scoping and Variable Safety
outer = 1 changeNumbers = -> inner = -1 outer = 10 inner = changeNumbers()
If, Else, Unless, and Conditional Assignment
mood = greatlyImproved if singing if happy and knowsIt clapsHands() chaChaCha() else showIt() date = if friday then sue else jill options or= defaults
Splats…
gold = silver = rest = "unknown" awardMedals = (first, second, others...) -> gold = first silver = second rest = others contenders = [ "Michael Phelps" "Liu Xiang" "Yao Ming" "Allyson Felix" "Shawn Johnson" "Roman Sebrle" "Guo Jingjing" "Tyson Gay" "Asafa Powell" "Usain Bolt" ] awardMedals contenders... alert "Gold: " + gold alert "Silver: " + silver alert "The Field: " + rest
Loops and Comprehensions
# Eat lunch. eat food for food in ['toast', 'cheese', 'wine']
countdown = (num for num in [10..1])
yearsOld = max: 10, ida: 9, tim: 11 ages = for child, age of yearsOld child + " is " + age
# Econ 101
if this.studyingEconomics
buy() while supply > demand
sell() until supply > demand
# Nursery Rhyme
num = 6
lyrics = while num -= 1
num + " little monkeys, jumping on the bed.
One fell out and bumped his head."
for filename in list
do (filename) ->
fs.readFile filename, (err, contents) ->
compile filename, contents.toString()
Array Slicing and Splicing with Ranges
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] copy = numbers[0...numbers.length] middle = copy[3..6]
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] numbers[3..6] = [-3, -4, -5, -6]
Everything is an Expression (at least, as much as possible)
grade = (student) ->
if student.excellentWork
"A+"
else if student.okayStuff
if student.triedHard then "B" else "B-"
else
"C"
eldest = if 24 > 21 then "Liz" else "Ike"
six = (one = 1) + (two = 2) + (three = 3)
# The first ten global properties. globals = (name for name of window)[0...10]
alert(
try
nonexistent / undefined
catch error
"And the error is ... " + error
)
Operators and Aliases
launch() if ignition is on volume = 10 if band isnt SpinalTap letTheWildRumpusBegin() unless answer is no if car.speed < limit then accelerate() winner = yes if pick in [47, 92, 13] print inspect "My name is " + @name
The Existential Operator
solipsism = true if mind? and not world? speed ?= 75 footprints = yeti ? "bear"
zip = lottery.drawWinner?().address?.zipcode
Classes, Inheritance, and Super
class Animal
constructor: (@name) ->
move: (meters) ->
alert @name + " moved " + meters + "m."
class Snake extends Animal
move: ->
alert "Slithering..."
super 5
class Horse extends Animal
move: ->
alert "Galloping..."
super 45
sam = new Snake "Sammy the Python"
tom = new Horse "Tommy the Palomino"
sam.move()
tom.move()
String::dasherize = -> this.replace /_/g, "-"
Destructuring Assignment
theBait = 1000 theSwitch = 0 [theBait, theSwitch] = [theSwitch, theBait]
weatherReport = (location) ->
# Make an Ajax request to fetch the weather...
[location, 72, "Mostly Sunny"]
[city, temp, forecast] = weatherReport "Berkeley, CA"
1
1
futurists =
sculptor: "Umberto Boccioni"
painter: "Vladimir Burliuk"
poet:
name: "F.T. Marinetti"
address: [
"Via Roma 42R"
"Bellagio, Italy 22021"
]
{poet: {name, address: [street, city]}} = futurists
tag = "<impossible>"
[open, contents..., close] = tag.split("")
Function binding
Account = (customer, cart) ->
@customer = customer
@cart = cart
$('.shopping_cart').bind 'click', (event) =>
@customer.purchase @cart
Embedded JavaScript
hi = `function() {
return [document.title, "Hello JavaScript"].join(": ");
}`
Switch/When/Else
switch day
when "Mon" then go work
when "Tue" then go relax
when "Thu" then go iceFishing
when "Fri", "Sat"
if day is bingoDay
go bingo
go dancing
when "Sun" then go church
else go work
Try/Catch/Finally
try allHellBreaksLoose() catsAndDogsLivingTogether() catch error print error finally cleanUp()
Chained Comparisons
cholesterol = 127 healthy = 200 > cholesterol > 60
String Interpolation, Heredocs, and Block Comments
author = "Wittgenstein"
quote = "A picture is a fact. -- #{ author }"
sentence = "#{ 22 / 7 } is a decent approximation of π"
mobyDick = "Call me Ishmael. Some years ago -- never mind how long precisely -- having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world..."
html = '''
<strong>
cup of coffeescript
</strong>
'''
### CoffeeScript Compiler v1.0.1 Released under the MIT License ###
Extended Regular Expressions
OPERATOR = /// ^ (
?: [-=]> # function
| [-+*/%<>&|^!?=]= # compound assign / compare
| >>>=? # zero-fill right shift
| ([-+:])\1 # doubles
| ([&|<>])\2=? # logic / shift
| \?\. # soak access
| \.{2,3} # range or splat
) ///
Cake, and Cakefiles
fs = require 'fs'
option '-o', '--output [DIR]', 'directory for compiled code'
task 'build:parser', 'rebuild the Jison parser', (options) ->
require 'jison'
code = require('./lib/grammar').parser.generate()
dir = options.output or 'lib'
fs.writeFile "#{dir}/parser.js", code
And lastly, there’s the brush code itself, which is written in CoffeeScript of course! Variables, this/@ and prototype access isn’t quite to my liking, plus it’s not comprehensive enough. For example, I would have liked variables within function calls to be highlighted but they aren’t, they are only highlighted when they are assigned.
If you have any additions or modifications that improve on this then I’d love to see them!
###
By Rod Vagg <rod@vagg.org> / @rvagg / http://rod.vagg.org
Released under the do-what-you-like-,-attribution-would-be-nice-,-but-please-don't-pass-off-as-your-own-work licence.
###
# Compile with: coffee -cb shBrushCoffeeScript.coffee
SyntaxHighlighter.brushes.CoffeeScript = ->
jsKeywords = 'if else new return try catch finally throw break continue for in while delete instanceof typeof switch super extends class case default do function var void with const let debugger enum export import native __extends __hasProp'
csKeywords = 'then unless and or is isnt not of by where when until'
keywords = jsKeywords + ' ' + csKeywords
@regexList = [
{ regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' },
# pass-through comment block
{ regex: /\#\#\#[\s\S]*?\#\#\#/gm, css: 'comments' },
{ regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString, css: 'string' },
{ regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' },
{ regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' },
# heredocs
{ regex: /\'\'\'[\s\S]*?\'\'\'/gm, css: 'string' },
# extended regular expressions
{ regex: /\/\/\/[\s\S]*?\/\/\//gm, css: 'string' },
{ regex: /\b([\d]+(\.[\d]+)?|0x[a-f0-9]+)\b/gmi, css: 'value' },
# I'm not sure whether these next two should be variables or 'color1' or something else
# @ & this variables
{ regex: /(@[\w._]*|this[\w._]*)/g, css: 'variable bold' },
# prototype references
{ regex: /(([\w._]+)::([\w._]*))/g, css: 'variable bold' },
# variables when used in assignment
{ regex: /([\w._]+)\s*(?=\=)/g, css: 'variable bold' },
# operators and other punctuational syntax
{ regex: /(->|->|=>|=>|===|==|=|>|>|<|<|\.\.\.|&&|&&|\|\||\!\!|\!|\+\+|\+|--|-|\[|\]|\(|\)|\{|\})|\?|\/|\*|\%/g, css: 'keyword' },
{ regex: new RegExp(@getKeywords(keywords), 'gm'), css: 'keyword' },
]
undefined
SyntaxHighlighter.brushes.CoffeeScript:: = new SyntaxHighlighter.Highlighter()
SyntaxHighlighter.brushes.CoffeeScript.aliases = [ 'coffeescript', 'CoffeeScript', 'coffee' ]
Alternatively, the raw code is here and the compiled JavaScript version is here.
If you want to install it directly into the SyntaxHighlighter Evolved plugin then put the JavaScript file into the syntaxhighlighter/third-party-brushes directory. Edit syntaxhighlighter.php and add this line to the section marked “Register some popular third-party brushes”:
wp_register_script( 'syntaxhighlighter-brush-coffeescript', plugins_url('syntaxhighlighter/third-party-brushes/shBrushCoffeeScript.js'), array('syntaxhighlighter-core'), '20110214');
And this line somewhere in the list of languages in the section not far below that which sets up $this->brushes (it should be obvious):
'coffeescript' => 'coffeescript',
Unfortunately you’ll have to do this each time the plugin is updated.
Would love to hear from you if you find this useful!
Update 11-Jul-2011: I neglected to add that I also have the following line in the brushes list section:
'coffee' => 'coffeescript',
I do this so I can simply use [ coffee ][ /coffee ] blocks.

3 comments
Hi,
I’m currently building a syntax highlighter in CoffeeScript, a port of Prim,and you can test the PHP version here: http://goo.gl/Ph5oK
Maybe we can join our forces ?
You can have a taste of the JS version here (only support for CoffeeScript): http://goo.gl/vqXgd
Compared to the PHP version, it’s really fast.
It also handles Urls inside comments, variable interpolation, themes, etc. Atm, there’s only coffeescript support (and php, but I’ve not included it inside the test page), but adding other languages is really easy.
Cheers.
by kib2 on February 17, 2011 at 1:59 am #
[...] plugin nemt tilføjer en CoffeeScript børste af Rod Vagg til Evolved SyntaxHighlighter [...]
by Philipp15b på "WordPress SyntaxHighlighter Evolved: CoffeeScript Brush" - CyberMaster on September 6, 2011 at 9:46 am #
A WordPress plugin from Philipp Schröer, I haven’t looked at the source so can’t verify that all’s well but here it is: http://wordpress.org/extend/plugins/syntaxhighlighter-coffeescript-brush/
by rod on September 6, 2011 at 9:52 am #