CategoryChili

Syntax highlighter based on jQuery

Chili NEXT on github

I’ve setup a github account for Chili, based on the github account for jQuery.

Anyone interested in forging the NEXT version of Chili is warmly welcome.

You should be able to join and contribute as easily as possible.

I’m still a bit new to git and github, but I hope I have prepared a usable setup for development.

The current source, is almost version 2.2, but heavily refactored, simplified and documented.

There are some minor differences, so that it’s not compatible with version 2.2 anymore.

NEXT version could be 2.3 or 3.0, most likely the latter.

In the github wiki I jotted down a minimal wish list.

Chili 2.2 Released Today

Changes

  • Added support for local line numbers
    • plain
    • starting from an offset
    • automatically segmented
  • Improved the PHP recipe for defaulting to PHP instead of HTML when no <? is found

Links

Chili 2.1 Released Today

UPDATE: Chili 2.2 has been released

Changes

  • Added support for line numbers (boolean option: lineNumbers)
  • Added a selection helper for Internet Explorer and Mozilla
  • Improved the PHP recipe for working fine with Safari too
  • Improved crossbrowser support

Links

Chili 2.0 Released Today

UPDATE: Chili 2.1 has been released

Changes

  • added support for a much better recipe format
  • optimized regular expressions for speed
  • moved project hosting to Google Code: jquery-chili-js
  • removed support for the previous recipe format
  • removed support for metaobjects
  • removed support for nearly obsolete features

Links

A new recipe format

Chili 2.0 supports a new recipe format which is a bit more structured than the old one and will make hard-to-highlight languages a thing of the past. BTW, old recipes won’t be accepted by Chili 2.0. (although a simple manual conversion is possible)

How to convert old recipes to the new format

First of all let’s see an example of how to convert a recipe in the old format to the new one.

What follows is a piece of recipe (for JavaScript) in the old format

{
	  ignoreCase: false
	, steps: {
		  ml_comment: { 
			exp: /\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\//
		}
		, sl_comment: { 
			exp: /\/\/.*/
		}
		//...
	}
}

And here is the same piece in the new format

{
	  _name: 'js'
	, _case: true
	, _main: {
		  ml_comment: { 
			  _match: /\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\//
			, _style: 'color: gray;'
		}
		, sl_comment: { 
			  _match: /\/\/.*/
			, _style: 'color: green;'
		}
		//...
	}
}

As you see, they are very similar. Here are all the differences:

  1. property names beginning with an underscore are reserved words
  2. _name didn’t exist before
  3. _case replaces the old ignoreCase; _case is TRUE if the language is case sensitive, and FALSE otherwise; _case defaults to FALSE
  4. _main replaces the old steps
  5. _match replaces the old exp
  6. _style didn’t exist before

_style makes a big difference with respect to the past: CSS styles can now be embedded into the recipe. Separate stylesheets are no longer supported by the autoloading engine, but you can load them by yourself, if you prefer to keep them apart.

Another important difference about CSS is that now Chili builds the class associated to a step, by prefixing the _name to the step name, separated by __ (a double underscore). So, for example, the class for the multiline comment will be js__ml_comment.

More expressive power with the new recipe format

Besides the minor changes described in the previous section, the new format is much more powerful thanks to the major improvement I’m going to describe here.

The old recipe format supported an optional replacement property of a step, by means of which you could customize how the highlighting was applied to the matched text. Such a feature was useful when you captured subexpressions and wanted to highlight them separately.

Now _replace replaces replacement (sic) and it’s still optional.

As before, if you don’t specify a _replace property, Chili will default to <span class=”$0″>$$</span>, where $0 and $$ refer to the name of the current step and the matched text respectively.

As before, _replace can also be a different string expression, like in the following step, extracted from the MySQL recipe

variable: { 
	  _match: /@([$.\w]+|([`\"\'])(?:(?:[^\2\\\r\n]*?(?:\2\2|\\.))*[^\2\\\r\n]*?)\2)/
	, _replace: '<span class="keyword">@</span><span class="variable">$1</span>'
	, _style: { keyword: 'color: navy;', variable: 'color: blue;' }
}

What to note in the above example:

  1. you can specify a style for each of the involved classes, by using properties of an object
  2. you must use a span for applying style to a text run
A big improvement

In Chili 2.0, _replace can also be a function, like in the following step, extracted from the new HTML recipe

// matches a starting tag of an element (with attrs)
// like "<div ... >" or "<img ... />"
, tag_start: { 
	  _match: /(<\w+)((?:\w|[?%]>|\W)*?)(\/>|>)/ 
	, _replace: function( all, open, content, close ) { 
		  return "<span class='tag_start'>" + this.x( open ) + "</span>" 
			  + this.x( content, '/tag_attrs' ) 
			  + "<span class='tag_start'>" + this.x( close ) + "</span>";
	}
	, _style: "color: navy; font-weight: bold;"
}

What to note in the above example:

  1. when _replace is a function, it receives match and submatches as arguments, and inside
  2. there is a valid this object which contains a magic x method, by which
  3. a string can be escaped for HTML (like open and close), or
  4. a string can be transformed by an expression (like content)

Chili 2.0 recipes are modular

A Chili 2.0 recipe contains blocks (like _main), which contain steps (like tag_start). An expression can be built for referencing each module, be it a recipe, a block, or a step. For example, /tag_attrs is the tag_attrs block in the current recipe.

One method to highlight them all

The JavaScript code inside a _replace function can use the x method of this.

x takes two arguments: a subject to process, and an optional module to use.

x returns the subject escaped for HTML if no module is given, or the module is not available, else it returns the result of applying the module to the subject using Chili 2.0.

If the ChiliBook option recipeLoading is true, any unavailable module will be automatically loaded.

The new HTML recipe

As an example, here is the new HTML recipe

{
	  _name: 'html'
	, _case: false
	, _main: {
		  doctype: { 
			  _match: /<!DOCTYPE\b(?:\w|\W)*?>/ 
			, _style: "color: #CC6600;"
		}
		, ie_style: {
			  _match: /(<!--\[[^\]]*\]>)((?:\w|\W)*?)(<!\[[^\]]*\]-->)/
			, _replace: function( all, open, content, close ) {
				return "<span class='ie_style'>" + this.x( open ) + "</span>" 
					  + this.x( content, '//style' ) 
					  + "<span class='ie_style'>" + this.x( close ) + "</span>";
			}
			, _style: "color: DarkSlateGray; font-weight: bold;"
		}
		, comment: { 
			  _match: /<!--(?:\w|\W)*?-->/ 
			, _style: "color: #4040c2;"
		}
		, script: { 
			  _match: /(<script\s+[^>]*>)((?:\w|\W)*?)(<\/script\s*>)/
			, _replace: function( all, open, content, close ) { 
				  return this.x( open, '//tag_start' ) 
					  + this.x( content, 'js' ) 
					  + this.x( close, '//tag_end' );
			} 
		}
		, style: { 
			  _match: /(<style\s+[^>]*>)((?:\w|\W)*?)(<\/style\s*>)/
			, _replace: function( all, open, content, close ) { 
				  return this.x( open, '//tag_start' ) 
					  + this.x( content, 'css' ) 
					  + this.x( close, '//tag_end' );
			} 
		}
		// matches a starting tag of an element (with attrs)
		// like "<div ... >" or "<img ... />"
		, tag_start: { 
			  _match: /(<\w+)((?:\w|[?%]>|\W)*?)(\/>|>)/ 
			, _replace: function( all, open, content, close ) { 
				  return "<span class='tag_start'>" + this.x( open ) + "</span>" 
					  + this.x( content, '/tag_attrs' ) 
					  + "<span class='tag_start'>" + this.x( close ) + "</span>";
			}
			, _style: "color: navy; font-weight: bold;"
		} 
		// matches an ending tag
		// like "</div>"
		, tag_end: { 
			  _match: /<\/\w+\s*>|\/>/ 
			, _style: "color: navy;"
		}
		, entity: { 
			  _match: /&\w+?;/ 
			, _style: "color: blue;"
		}
	}
	, tag_attrs: {
		// matches a name/value pair
		attr: {
			// before in $1, name in $2, between in $3, value in $4
			  _match: /(\W*?)([\w-]+)(\s*=\s*)((?:\'[^\']*(?:\\.[^\']*)*\')|(?:\"[^\"]*(?:\\.[^\"]*)*\"))/ 
			, _replace: "$1<span class='attr_name'>$2</span>$3<span class='attr_value'>$4</span>"
			, _style: { attr_name:  "color: green;", attr_value: "color: maroon;" }
		}
	}
}

What to note in the above example:

  1. a _replace function can be used for applying a recipe to a text run inside another recipe, like the script an style steps, where highlighting of script and style elements is delegated to js and css recipes respectively
  2. a _replace function can be used for isolating the parsing of a text run, like the tag_attrs step, where highlighting of name/value pairs happens only in the context of tag attributes
Module paths

A module path is an expression that identifies a Chili 2.0 module. A path has three components (though some can be hidden) separated by a / (forward slash), each with a specific meaning: recipe / block / step. (white space added for clarity)

Here is a list of all the combinations in a module path:

  • recipe
    a module path like css refers to the entire css recipe
  • recipe / block
    a module path like css/definition refers to the definition block of the css recipe
  • recipe / block / step
    a module path like css/definition/property refers to the property step of the definition block of the css recipe
  • / block
    a module path like /definition refers to the definition block of the current recipe
  • / block / step
    a module path like /definition/property refers to the property step of the definition block of the current recipe
  • / / step
    a module path like //property refers to the property step of the current block of the current recipe

As you see, leading slashes have a meaning.

Remember
  • a recipe module invocation tries to match all the steps of the _main block
  • a block module invocation tries to match all its steps
  • a step module invocation tries to match just itself

Help request

I think that Chili 2.0 is pretty good at highlighting, but it needs more fine recipes to succeed. For this release I’ve rewritten some from scratch, and converted some others. I’m not a good programmer in languages other than the ones for which I rewrote a recipe. But if you are and have time and will, then you could write a Chili 2.0 recipe for your favorite language, together with a couple of working samples, and send all to me. I’d be very happy to add your contributed recipes to the project as soon as they are available.

Rewritten
  • CSS
  • HTML
  • JavaScript
  • PHP
Converted
  • C++
  • C#
  • Delphi
  • Java
  • LotusScript
  • MySQL

Setup and Examples

Here is the start page for Chili 2.0 where you’ll find setup instructions and some examples.

How to highlight code in WordPress

WordPress Editing Tabs

Although it’s easy to use WP Chili out of the box, WordPress does have some limitations, like the following:

  • you need to change to the Code editor before adding a snippet to your post
  • you need to make your snippet postable yourself, escaping all HTML entities
  • even if snippets are very short, they are intermingled with their explanations and it’s tricky to concentrate on writing the best post with so many distractions in between

Using WP Chili & Enzymes

Now I’ll show you a better approach by using together WP Chili 1.0 and Enzymes 2.2.
All you need to be up and running is the classical roundtrip: download, unzip, upload, and activate.

How to highlight code snippets

You can start by writing a simple enzyme for automating things, like the following hilite custom field:

return 
'<pre><code class="' . $this->substrate . '">'
. htmlspecialchars( $this->pathway ) 
. '</code></pre>';

hilite can be used with the following patterns:

  1. {[ =snippet= | .hilite( =language= ) ]}
  2. {[ .snippet | .hilite( =language= ) ]}

The first pattern comes in handy when you want to highlight some very short and naive snippet
{[ =echo htmlentities( $name );= | .hilite( =php= ) ]} renders

echo htmlentities( $name );

So far, so good, but if the snippet has a character that WP texturizes, then hilite seems to fail. In fact
{[ =$welcome = “Hello “.$name;= | .hilite( =php= ) ]} renders {[ =$welcome = “Hello “.$name;= | .hilite( =php= ) ]}

The above issue is not a hilite‘s bug but it could be fixed by adding new code to it, or with new enzymes along the pathway, like the following requote custom field:

$from = array( '&#8216;', '&#8217;', '&#8220;', '&#8221;' );
$to = array( "'", "'", '"', '"' );
return str_replace( $from, $to, $this->pathway );

which makes that
{[ =$welcome = “Hello “.$name;= | .requote() | .hilite( =php= ) ]}
renders {[ =$welcome = “Hello “.$name;= | .requote() | .hilite( =php= ) ]}

The best option is to add another custom field for hosting your snippet and use the second pattern: in fact hilite‘s snippet has been rendered by {[ .hilite | .hilite( =php= ) ]} and requote‘s one by {[ .requote | .hilite( =php= ) ]}.

How to highlight code files

Sometimes you have a file that you want to show in its entirety, and having to copy it into a custom field is annoying or maybe not an option, if the file is alive, for example.

In such cases you can use an enzyme like the following file custom field:

return file_get_contents( $this->substrate );

It’s use is again very simple and very similar to the above patterns:
{[ .file( =blog/wp-content/plugins/hello.php= ) | .hilite( =php= ) ]} renders

© 2017 Notes Log

Theme by Anders NorenUp ↑