coffeescript.js

module.exports = function(hljs) {
  var keywords = {
    keyword:
      // JS keywords
      'in if for while finally new do return else break catch instanceof throw try this ' +
      'switch continue typeof delete debugger class extends super' +
      // Coffee keywords
      'then unless until loop of by when and or is isnt not',
    literal:
      // JS literals
      'true false null undefined ' +
      // Coffee literals
      'yes no on off ',
    reserved: 'case default function var void with const let enum export import native ' +
      '__hasProp __extends __slice __bind __indexOf'
  };

  var JS_IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*';

  var COFFEE_QUOTE_STRING_SUBST_MODE = {
    className: 'subst',
    begin: '#\\{', end: '}',
    keywords: keywords,
    contains: [hljs.C_NUMBER_MODE, hljs.BINARY_NUMBER_MODE]
  };

  var COFFEE_QUOTE_STRING_MODE = {
    className: 'string',
    begin: '"', end: '"',
    relevance: 0,
    contains: [hljs.BACKSLASH_ESCAPE, COFFEE_QUOTE_STRING_SUBST_MODE]
  };

  var COFFEE_HEREDOC_MODE = {
    className: 'string',
    begin: '"""', end: '"""',
    contains: [hljs.BACKSLASH_ESCAPE, COFFEE_QUOTE_STRING_SUBST_MODE]
  };

  var COFFEE_HERECOMMENT_MODE = {
    className: 'comment',
    begin: '###', end: '###'
  };

  var COFFEE_HEREGEX_MODE = {
    className: 'regexp',
    begin: '///', end: '///',
    contains: [hljs.HASH_COMMENT_MODE]
  };

  var COFFEE_EMPTY_REGEX_MODE = {
    className: 'regexp', begin: '//[gim]*'
  };

  var COFFEE_REGEX_MODE = {
    className: 'regexp',
    begin: '/\\S(\\\\.|[^\\n])*/[gim]*' // \S is required to parse x / 2 / 3 as two divisions
  };

  var COFFEE_FUNCTION_DECLARATION_MODE = {
    className: 'function',
    begin: JS_IDENT_RE + '\\s*=\\s*(\\(.+\\))?\\s*[-=]>',
    returnBegin: true,
    contains: [
      {
        className: 'title',
        begin: JS_IDENT_RE
      },
      {
        className: 'params',
        begin: '\\(', end: '\\)'
      }
    ]
  };

  var COFFEE_EMBEDDED_JAVASCRIPT = {
    begin: '`', end: '`',
    excludeBegin: true, excludeEnd: true,
    subLanguage: 'javascript'
  };

  return {
    keywords: keywords,
    contains: [
      // Numbers
      hljs.C_NUMBER_MODE,
      hljs.BINARY_NUMBER_MODE,
      // Strings
      hljs.APOS_STRING_MODE,
      COFFEE_HEREDOC_MODE, // Should be before COFFEE_QUOTE_STRING_MODE for greater priority
      COFFEE_QUOTE_STRING_MODE,
      // Comments
      COFFEE_HERECOMMENT_MODE, // Should be before hljs.HASH_COMMENT_MODE for greater priority
      hljs.HASH_COMMENT_MODE,
      // CoffeeScript specific modes
      COFFEE_HEREGEX_MODE,
      COFFEE_EMPTY_REGEX_MODE,
      COFFEE_REGEX_MODE,
      COFFEE_EMBEDDED_JAVASCRIPT,
      COFFEE_FUNCTION_DECLARATION_MODE
    ]
  };
};