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: /(-&gt;|->|=&gt;|=>|===|==|=|>|&gt;|<|&lt;|\.\.\.|&&|&amp;&amp;|\|\||\!\!|\!|\+\+|\+|--|-|\[|\]|\(|\)|\{|\})|\?|\/|\*|\%/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 #

Leave your comment

Required.

Required. Not published.

If you have one.